Updating Entry and Label widget in Tkinter - python

i have defined a GUI class that creates a tkinter window with a couple of entries. I would like that every time that the user overwrites the Entries and press Enter, there is some operation done in the background. In addition, i would like that the entries are checking regularly certain values and updating them, so the user could see if they changed; In the example below i use a static dictionary, but normally those parameters are obtained from a camera and could fluctuate.
However, i am not even able to get the correct value printed in the label. I am not a tkinter expert so any idea would be appreciated
from tkinter import *
class GUI():
def __init__(self, window, window_title,input_dict):
self.window = window
self.window.title(window_title)
self.window.geometry('400x200')
top_frame = Frame(self.window)
top_frame.pack(side=TOP, pady=5)
Label(top_frame, text="Frame rate (fps)").grid(row=0)
Label(top_frame, text="Exposure time (ms)").grid(row=2)
self.labeling=Label(top_frame, text="Result").grid(row=3)
self.e1_var = StringVar() # or StringVar(top)
self.e1_var.set(str(round(input_dict['frameRate'])))
self.e2_var = StringVar() # or StringVar(top)
# print(type(self.e2_var))
self.e2_var.set(str(round(input_dict['Exp_time'])))
self.fps_entry = Entry(top_frame,textvariable=self.e1_var)
self.exptime_entry = Entry(top_frame,textvariable=self.e2_var)
self.fps_entry.bind("<Return>",self.my_tracer)
self.exptime_entry.bind("<Return>",self.my_tracer)
self.fps_entry.grid(row=0, column=1)
self.exptime_entry.grid(row=2, column=1)
self.window.mainloop()
def my_tracer(self,event):
val1=int(self.e1_var.get())
val2=int(self.e2_var.get())
self.labeling.configure(text=str(val1*val2))
input_dict = {
'frameRate': 50,
'Exp_time': 5000}
video_object=GUI(Tk(),"Test",input_dict)

The error your code produces is AttributeError: 'NoneType' object has no attribute 'configure', right?
Look at this line:
self.labeling=Label(top_frame, text="Result").grid(row=3)
self.labeling will be None because grid() returns None. It is indeed bad practice to 'chain' a geometry manager to the creation of a widget. Change to:
self.labeling=Label(top_frame, text="Result")
self.labeling.grid(row=3)
Now the labels are updating when the user enters a new value.

Related

Storing and using GUI objects with Tkinter

So I have a class that just creates a gui with a text box to type into. I would like to store multiple of these objects and their content that is entered by the user. I have a button (on a seperate gui) that creates the new object and spawns the gui and then stores that into a simple list listOobjects = []. My thought process was that I could simply just call listOobjects[i] when a button was pressed to bring that gui back with its contents still in place. That didn't work so then I thought maybe I could use the listOobjects[i].withdraw() and listOobjects[i].deiconify() to hide and recall what gui I want. Not only did that not work but I also feel that isn't the best course of action with possibly 12 gui's practically 'minimized'. I also looked into pickle to save and recall objects. I haven't tried it yet but was curious of my best course of action here?
Although I don't know what is considered best practice in this case, here is one possible way that avoids keeping track of 'minimized' widgets that aren't being used.
This solution deletes widgets which are not currently being displayed by calling the destroy method, so the program isn't doing extra work maintaining objects that aren't being used.
In order to be able to recall the deleted widgets when the button is pressed, all relevant information from a widget is recorded and stored in a tuple before the widget is deleted. This way, no information is lost, but only simple values are being stored instead of tkinter widgets.
These tuples of values are then used as parameters to instantiate new widgets which exactly replicate the old, deleted ones.
Here is a simple demo that toggles between three different Entry widgets:
import tkinter as tk
window = tk.Tk()
window['width'] = 500
window['height'] = 300
textBox1 = tk.Entry(master=window, bg='darkblue', fg='yellow', text="First",
relief=tk.RAISED, bd=3)
textBox2 = tk.Entry(master=window, bg='purple', fg='white', text="Second",
relief=tk.RAISED, bd=3)
textBox3 = tk.Entry(master=window, bg='darkgreen', fg='yellow', text="Third",
relief=tk.RAISED, bd=3)
textBox1.place(x=50, y=100, width=100, height=30)
textBox2.place(x=200, y=100, width=100, height=30)
textBox3.place(x=350, y=100, width=100, height=30)
# Will store all information necessary to reconstruct and place previously displayed
# text boxes which have been removed.
storage = [None, None, None]
# Store a reference to the text box which is currently displayed
activeTextBox = {'textBox': textBox1, 'index': 0}
# After recording all information to be used in 'storage', call 'destroy()' to delete
# the widget instead of hiding it (for more efficiency)
def sendToStorage(textBox, index):
parameters = (textBox['bg'], textBox['fg'], textBox['relief'], textBox['bd'], textBox.get())
storage[index] = parameters
textBox.destroy()
# Using the stored information, construct a new text box (tk.Entry widget) which is
# identical to the old one that was deleted.
def retrieveFromStorage(index):
txtB = storage[index]
storage[index] = None
activeTextBox['textBox'] = tk.Entry(window, bg=txtB[0], fg=txtB[1], relief=txtB[2], bd=txtB[3])
activeTextBox['textBox'].insert(0, txtB[4])
activeTextBox['textBox'].place(x=50+150*index, y=100, width=100, height=30)
# Put the old text box in storage and retrieve the one after it. Increment the index
# of the text box that is currently active (loop around once you get to the end of 'storage').
def toggleTextBox():
sendToStorage(activeTextBox['textBox'], activeTextBox['index'])
activeTextBox['index'] += 1
if activeTextBox['index'] == len(storage):
activeTextBox['index'] = 0
retrieveFromStorage(activeTextBox['index'])
window.update()
# DEMO: CALL FUNCTION TO STORE AND DELETE ALL BUT 1 TEXT BOX
sendToStorage(textBox2, 1)
sendToStorage(textBox3, 2)
# THIS IS THE BUTTON THAT WILL CYCLE BETWEEN THE TEXT BOXES
toggleButton = tk.Button(master=window, text='TOGGLE ACTIVE TEXT BOX',
command=toggleTextBox)
toggleButton.place(x=100, y=200, width=300, height=50)
window.mainloop()
It will keep track of the text boxes' current text (that the user has entered) as well as their formatting options. Try it out and see if it does what you're looking for!

how to pass button value from custom widget to main application in tkinter when clicked

I have created a custom widget for tkinter that lays out 5 buttons. The widget works beautifully for the most part. The problem is that I cannot figure out how to pass the button that the user presses in the widget to the main application. The custom widget stores the last button pressed in a variable, but I cannot figure out how to make the main application see that it has been changed without resorting to binding a button release event to root. I would like to try to build out this custom widget further, and I want it to work without having to do some messy hacks. Ideally, in the example below, when a button is pressed, the label should change to reflect the button pressed. For example, if the user clicks the "2" button, the label should change to "2 X 2 = 4". How can I pass the text on the button directly to the main application for use? Hopefully, I am making it clear enough. I want to be able to get the value from the widget just like any other tkinter widget using a .get() method. Here is the code that I am using:
import tkinter as tk
from tkinter import ttk
class ButtonBar(tk.Frame):
def __init__(self, parent, width=5, btnLabels=''):
tk.Frame.__init__(self, parent)
self.btnLabels = []
self.btnNames = []
self.setLabels(btnLabels)
self.selButton = None
self.display()
def getPressedBtn(self,t):
"""
This method will return the text on the button.
"""
self.selButton = t
print(t)
def createBtnNames(self):
"""
This method will create the button names for each button. The button
name will be returned when getPressedBtn() is called.
"""
for i in range(0,5):
self.btnNames.append(self.btnLabels[i])
def display(self):
"""
This method is called after all options have been set. It will display
the ButtonBar instance.
"""
self.clear()
for i in range(len(self.btnLabels)):
self.btn = ttk.Button(self, text=self.btnLabels[i], command=lambda t=self.btnNames[i]: self.getPressedBtn(t))
self.btn.grid(row=0, column=i)
def setLabels(self, labelList):
if labelList == '':
self.btnLabels = ['1', '2', '3', '4', '5']
self.createBtnNames()
else:
btnLabelStr = list(map(str, labelList))
labelsLen = len(btnLabelStr)
def clear(self):
"""
This method clears the ButtonBar of its data.
"""
for item in self.winfo_children():
item.destroy()
root = tk.Tk()
def getButtonClicked(event):
global selBtn
print(event)
if example.winfo_exists():
selBtn = example.selButton
answer = int(selBtn) * 2
myLabel.config(text='2 X ' + selBtn + ' = ' + str(answer))
tabLayout = ttk.Notebook(root)
tabLayout.pack(fill='both')
vmTab = tk.Frame(tabLayout)
myLabel = tk.Label(vmTab, text='2 X 0 = 0', width=50, height=10)
myLabel.pack()
vmTab.pack(fill='both')
tabLayout.add(vmTab, text='Volume Movers')
# Create the ButtonBar.
example = ButtonBar(vmTab)
selBtn = None
example.pack()
lbl = tk.Label(root, text='')
root.mainloop()
I have looked at some other posts on stackoverflow. This one creating a custom widget in tkinter was very helpful, but it didn't address the button issue. I though this Subclassing with Tkinter might help. I didn't understand the If I bind the event using root.bind("<ButtonRelease-1>", getButtonClicked), then the widget works fine. Is there any other way to do it though?
I'd say that you have made the code more complex than it should be, you really just need to create the buttons and give them some callback that is passed as an argument. And that callback should take at least one argument which would be the text that would be on the button which will be also passed to that callback.
import tkinter as tk
from tkinter import ttk
class ButtonBar(tk.Frame):
def __init__(self, master, values: list, command=None):
tk.Frame.__init__(self, master)
for col, text in enumerate(values):
btn = ttk.Button(self, text=text)
if command is not None:
btn.config(command=lambda t=text: command(t))
btn.grid(row=0, column=col, sticky='news')
def change_label(val):
res = 2 * int(val)
new_text = f'2 X {val} = {res}'
my_label.config(text=new_text)
root = tk.Tk()
my_label = tk.Label(root, text='2 X 0 = 0')
my_label.pack(pady=100)
texts = ['1', '2', '3', '4', '5']
example = ButtonBar(root, values=texts, command=change_label)
example.pack()
root.mainloop()
You can also base the buttons on a list of values so that you can specify any values and it will create buttons that have that text on them and pressing them will call the given function with an argument of their text. That way you can use it as really any other widget, it would require the master, some values (text) and a command. Then you would just create that callback, which will take that one argument and then change the label accordingly. (I also removed all the notebook stuff, but I am just showing how you can achieve what you asked for)
Also:
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Have two blank lines around function and class declarations. Object method definitions have one blank line around them.

tkinter checkbutton not setting variable

Whatever I do to my checkbutton, it does not seem to set the variable.
Here's the parts of the code that are involved:
class Window:
def __init__(self):
self.manualb = 0 #to set the default value to 0
def setscreen(self):
#screen and other buttons and stuff set here but thats all working fine
manual = tkr.Checkbutton(master=self.root, variable=self.manualb, command=self.setMan, onvalue=0, offvalue=1) #tried with and without onvalue/offvalue, made no difference
manual.grid(row=1, column=6)
def setMan(self):
print(self.manualb)
#does some other unrelated stuff
It just keeps printing 0. Am I doing something wrong? Nothing else does anything to manual.
You're looking for IntVar()
IntVar() has a method called get() which will hold the value of the widget you assign it to.
In this particular instance, it will be either 1 or 0 (On or off).
You can use it something like this:
from tkinter import Button, Entry, Tk, Checkbutton, IntVar
class GUI:
def __init__(self):
self.root = Tk()
# The variable that will hold the value of the checkbox's state
self.value = IntVar()
self.checkbutton = Checkbutton(self.root, variable=self.value, command=self.onClicked)
self.checkbutton.pack()
def onClicked(self):
# calling IntVar.get() returns the state
# of the widget it is associated with
print(self.value.get())
app = GUI()
app.root.mainloop()
This is because you need to use one of tkinter's variable classes.
This would look something like the below:
from tkinter import *
root = Tk()
var = IntVar()
var.trace("w", lambda name, index, mode: print(var.get()))
Checkbutton(root, variable=var).pack()
root.mainloop()
Essentially IntVar() is a "container" (very loosely speaking) which "holds" the value of the widget it's assigned to.

Trying to produce function for a custom number of tkinter Entry fields.

I've been trying to create a piece of code that would take a integer as a argument and create that number of tkinter entry fields. With a submit button at the end that would retrieve the data from the fields add these data to a list then close the window.
I have been able to get it working however I cant find a way to convert this to a callable function; a requirement to use it with the rest of my program.
This is the code I have produced so far, thanks:
import tkinter as tk
b = input("Enter: ")
b = int(b)
root = tk.Tk()
newdict = dict()
outputs = list()
for i in range(b):
newdict["entry" + str(i)] = tk.Entry(root)
newdict["entry" + str(i)].pack()
button1 = tk.Button(root, text="Submit", command=lambda: Get(newdict))
button1.pack()
def Get(newdict):
for j in range(b):
outputs.append(newdict["entry" + str(j)].get())
root.quit()
root.mainloop()
print(outputs)
The basic idea is to create a window, then use the wait_window method to wait for the window to be destroyed. Once it has been destroyed you can return some value.
The problem is that the values you want to fetch must not be attributes of the window, since it will have been destroyed by the time you are ready to fetch them. You need to set up your code to save the values before the window is destroyed.
A simple way is to provide an "OK" button which gets the values and then destroys the window. Another way would be to put a trace on variables associated with each entry, and save the values immediately as they are edited.
Which method you choose depends on what behavior you want when the user clicks the window control to close the window (eg: the red circle on OSX, the [x] button on windows, etc). Do you want to return what they had input, or do you treat that as a cancel action and return nothing?
Here's a simple example using an OK button. This example assumes that you aren't already running a GUI, and that this is to be run as part of a non-GUI application.
import tkinter as tk
class Dialog(object):
def show(self, num_fields):
self.num_fields = num_fields
self.root = tk.Tk()
self.entries = []
for i in range(num_fields):
entry = tk.Entry(self.root)
entry.pack(fill="x")
self.entries.append(entry)
ok = tk.Button(self.root, text="OK", command=self.ok)
ok.pack(side="bottom", anchor="e", pady=(10,0), padx=10)
# wait for the window to be destroyed, then
# return the values. If the user clicks the OK button
# the values will be set; if they cancel the dialog
# this will return None.
self.values = None
self.root.wait_window()
return self.values
def ok(self):
# save all the values, then destroy the window
self.values = []
for i in range(self.num_fields):
self.values.append(self.entries[i].get())
self.root.destroy()
Assuming you're running a non-gui program, here's an example of how you would use this class:
b = input("Enter: ")
b = int(b)
result = Dialog().show(b)
print("result:", result)

How to set the text/value/content of an `Entry` widget using a button in tkinter

I am trying to set the text of an Entry widget using a button in a GUI using the tkinter module.
This GUI is to help me classify thousands of words into five categories. Each of the categories has a button. I was hoping that using a button would significantly speed me up and I want to double check the words every time otherwise I would just use the button and have the GUI process the current word and bring the next word.
The command buttons for some reason are not behaving like I want them to. This is an example:
import tkinter as tk
from tkinter import ttk
win = tk.Tk()
v = tk.StringVar()
def setText(word):
v.set(word)
a = ttk.Button(win, text="plant", command=setText("plant"))
a.pack()
b = ttk.Button(win, text="animal", command=setText("animal"))
b.pack()
c = ttk.Entry(win, textvariable=v)
c.pack()
win.mainloop()
So far, when I am able to compile, the click does nothing.
You might want to use insert method. You can find the documentation for the Tkinter Entry Widget here.
This script inserts a text into Entry. The inserted text can be changed in command parameter of the Button.
from tkinter import *
def set_text(text):
e.delete(0,END)
e.insert(0,text)
return
win = Tk()
e = Entry(win,width=10)
e.pack()
b1 = Button(win,text="animal",command=lambda:set_text("animal"))
b1.pack()
b2 = Button(win,text="plant",command=lambda:set_text("plant"))
b2.pack()
win.mainloop()
If you use a "text variable" tk.StringVar(), you can just set() that.
No need to use the Entry delete and insert. Moreover, those functions don't work when the Entry is disabled or readonly! The text variable method, however, does work under those conditions as well.
import Tkinter as tk
...
entry_text = tk.StringVar()
entry = tk.Entry( master, textvariable=entry_text )
entry_text.set( "Hello World" )
You can choose between the following two methods to set the text of an Entry widget. For the examples, assume imported library import tkinter as tk and root window root = tk.Tk().
Method A: Use delete and insert
Widget Entry provides methods delete and insert which can be used to set its text to a new value. First, you'll have to remove any former, old text from Entry with delete which needs the positions where to start and end the deletion. Since we want to remove the full old text, we start at 0 and end at wherever the end currently is. We can access that value via END. Afterwards the Entry is empty and we can insert new_text at position 0.
entry = tk.Entry(root)
new_text = "Example text"
entry.delete(0, tk.END)
entry.insert(0, new_text)
Method B: Use StringVar
You have to create a new StringVar object called entry_text in the example. Also, your Entry widget has to be created with keyword argument textvariable. Afterwards, every time you change entry_text with set, the text will automatically show up in the Entry widget.
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
new_text = "Example text"
entry_text.set(new_text)
Complete working example which contains both methods to set the text via Button:
This window
is generated by the following complete working example:
import tkinter as tk
def button_1_click():
# define new text (you can modify this to your needs!)
new_text = "Button 1 clicked!"
# delete content from position 0 to end
entry.delete(0, tk.END)
# insert new_text at position 0
entry.insert(0, new_text)
def button_2_click():
# define new text (you can modify this to your needs!)
new_text = "Button 2 clicked!"
# set connected text variable to new_text
entry_text.set(new_text)
root = tk.Tk()
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
button_1 = tk.Button(root, text="Button 1", command=button_1_click)
button_2 = tk.Button(root, text="Button 2", command=button_2_click)
entry.pack(side=tk.TOP)
button_1.pack(side=tk.LEFT)
button_2.pack(side=tk.LEFT)
root.mainloop()
Your problem is that when you do this:
a = Button(win, text="plant", command=setText("plant"))
it tries to evaluate what to set for the command. So when instantiating the Button object, it actually calls setText("plant"). This is wrong, because you don't want to call the setText method yet. Then it takes the return value of this call (which is None), and sets that to the command of the button. That's why clicking the button does nothing, because there is no command set for it.
If you do as Milan Skála suggested and use a lambda expression instead, then your code will work (assuming you fix the indentation and the parentheses).
Instead of command=setText("plant"), which actually calls the function, you can set command=lambda:setText("plant") which specifies something which will call the function later, when you want to call it.
If you don't like lambdas, another (slightly more cumbersome) way would be to define a pair of functions to do what you want:
def set_to_plant():
set_text("plant")
def set_to_animal():
set_text("animal")
and then you can use command=set_to_plant and command=set_to_animal - these will evaluate to the corresponding functions, but are definitely not the same as command=set_to_plant() which would of course evaluate to None again.
One way would be to inherit a new class,EntryWithSet, and defining set method that makes use of delete and insert methods of the Entry class objects:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
class EntryWithSet(tk.Entry):
"""
A subclass to Entry that has a set method for setting its text to
a given string, much like a Variable class.
"""
def __init__(self, master, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
def set(self, text_string):
"""
Sets the object's text to text_string.
"""
self.delete('0', 'end')
self.insert('0', text_string)
def on_button_click():
import random, string
rand_str = ''.join(random.choice(string.ascii_letters) for _ in range(19))
entry.set(rand_str)
if __name__ == '__main__':
root = tk.Tk()
entry = EntryWithSet(root)
entry.pack()
tk.Button(root, text="Set", command=on_button_click).pack()
tk.mainloop()
e= StringVar()
def fileDialog():
filename = filedialog.askopenfilename(initialdir = "/",title = "Select A
File",filetype = (("jpeg","*.jpg"),("png","*.png"),("All Files","*.*")))
e.set(filename)
la = Entry(self,textvariable = e,width = 30).place(x=230,y=330)
butt=Button(self,text="Browse",width=7,command=fileDialog).place(x=430,y=328)

Categories

Resources