Tkinter: using return for widgets - python

So, I have been having trouble figuring out what does return do when used for tkinter widgets
here are 3 blocks of code that have the same result, and I wasn't sure what their difference is and whether they differ in performance, and finally, which one is the standard approach
1
import tkinter as tk
class App:
def __init__(self):
window = tk.Tk()
self.label_in_window(window, text = "hello world!")
window.mainloop()
def label_in_window(self, parent, text):
return tk.Label(parent, text = text).pack()
App()
2
import tkinter as tk
class App:
def __init__(self):
window = tk.Tk()
self.label_in_window(window, text = "hello world!")
window.mainloop()
def label_in_window(self, parent, text):
tk.Label(parent, text = text).pack()
return
App()
3
import tkinter as tk
class App:
def __init__(self):
window = tk.Tk()
self.label_in_window(window, text = "hello world!")
window.mainloop()
def label_in_window(self, parent, text):
tk.Label(parent, text = text).pack()
App()

Per code answer:
does not make sense, because you are returning something, but then throwing the returned result away.
you can remove the return statement because all functions by default return None, and a return statement without a value also returns None. This then makes 2. and 3. equal.
If you just want to put the creation of widgets into a separate function, use the 3rd code.
Anyways, all the returns do not make sense in your code, because they will always return None. Guess why ? No ? I'll explain it. It's because of this line:
tk.Label(parent, text = text).pack()
First you create a tk.Label object. Then, you call its pack method which of course returns None. So in the end, you have not a tk.Label, but a None. To change this, you should first assign your tk.Label object to a variable, then call the pack method of the variable, then return the variable:
def label_in_window(self, parent, text):
label = tk.Label(parent, text=text)
label.pack()
return label
But, if you are not planning to use the label later (set a new text, for example), it doesn't make sense to return the label. If you do want to use it later, the standard approach in a class would be to assign the label to an attribute of your class. Also, you should move the window.mainloop out of the class, and functions / methods should be named after what they do, not which result they produce:
import tkinter as tk
class App:
def __init__(self):
self.window = tk.Tk()
self.add_label(self.window, text="hello world!")
def add_label(self, parent, text):
self.label = tk.Label(parent, text=text)
self.label.pack()
app = App()
app.window.mainloop()

In the first example you used:
return tk.Label(parent, text=text).pack()
Which creates a label, calls .pack() on the label. It returns whatever .pack() returns (which is None). For more info please read this. So it's identical to:
tk.Label(parent, text=text).pack()
return None
The second example uses:
tk.Label(parent, text = text).pack()
return
Which creates the label. The return is the same as return None.
In the third example uses:
tk.Label(parent, text=text).pack()
Which creates a label then returns None.
Therefore, all the examples that you have shown are exactly the same. They all create a label and return None.
PS: If you use from dis import dis and then dis(App.label_in_window), you will see the decompiled bytecode, which is what python actually uses to run your code.

Related

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.

String Variable not setting initial value

class Lay():
def __init__(self):
root=Tk()
root.configure(background="black")
var=StringVar()
var.set("OVERVIEW")
Label(root,textvariable=var).grid(row=1,column=1,sticky=W+E+N+S)
Entry(root, textvariable = var).place(rely=1.0,relx=1.0,x=0,y=0,anchor=SE)
root.mainloop()
Hello, when i run this the initial value of the string variable does not appear, but when i type into the entry box, the text i type appears in the label. I'm not quite sure why this occurs, but i get an empty label to begin with, with the entry box. Thank you for any help.
Although, I couldn't reproduce the problem, I refactored your code to initialize tkinter widgets through a class(inspired by the snippet in the docs) and also increased the window size so that the widgets are clearly viewed. If there is anything else in your code that is calling multiple windows as #jasonharper suggested, you should share that.
import tkinter as tk
class Lay(tk.Tk):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.var=tk.StringVar()
self.var.set("OVERVIEW")
self.Widgets()
def Widgets(self):
self.displaylbl = tk.Label(self,textvariable=self.var)
self.displaylbl.grid(row=2,column=1,sticky=tk.W+tk.E+tk.N+tk.S)
self.entry = tk.Entry(self, textvariable = self.var)
self.entry.place(rely=1.0,relx=1.0,x=0,y=0,anchor=tk.SE)
app = Lay()
app.geometry("200x200")
app.mainloop()
Output:

Communicate classes via tkinter's bind method

I'm developing a package with GUI using tkinter. Now there is a problem when communicating classes via tkinter's bind method. A simple code which represents what I want to do is listed below:
import Tkinter as tk
lists = [1,2,3,4,5,6,7]
class selects():
def __init__(self,root):
self.root = root
self.selectwin()
def selectwin(self):
""" listbox and scrollbar for selection """
sb = tk.Scrollbar(self.root)
lb = tk.Listbox(self.root, relief ='sunken', cursor='hand2')
sb.config(command=lb.yview)
sb.pack(side=tk.RIGHT, fill=tk.Y)
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
lb.config(yscrollcommand=sb.set, selectmode='single')
for value in lists: lb.insert(tk.END,value)
lb.bind('<Double-1>',lambda event: self.getvalue())
self.listbox = lb
def getvalue(self):
""" get the selected value """
value = self.listbox.curselection()
if value:
self.root.quit()
text = self.listbox.get(value)
self.selectvalue = int(text)
def returnvalue(self):
return self.selectvalue
class do():
def __init__(self):
root = tk.Tk()
sl = selects(root)
# do something... for example, get the value and print value+2, as coded below
value = sl.returnvalue()
print value+2
root.mainloop()
if __name__ == '__main__':
do()
class selects adopt Listbox widget to select a value in lists and return the selected value for use via attribute returnvalue. However, error is raised when running the above codes:
Traceback (most recent call last):
File "F:\Analysis\Python\fpgui\v2\test2.py", line 47, in <module>
do()
File "F:\Analysis\Python\fpgui\v2\test2.py", line 41, in __init__
value = sl.returnvalue()
File "F:\Analysis\Python\fpgui\v2\test2.py", line 32, in returnvalue
return self.selectvalue
AttributeError: selects instance has no attribute 'selectvalue'
I think this error can be solved by combining classes selects and do together as a single class. But in my package, class selects will be called by several classes, so it is better to make selects as a standalone class. Further, communications between classes like this will be frequently applied in my package. For example, do something after picking some information in matplotlib figure using pick_event, or update a list in one class after inputting texts in another class using Entry widget. So, any suggestion about this? Thanks in advance.
You're calling sl.returnvalue() right after having created sl. However, at this point sl.getvalue() has never been called, which means that sl.selectvalue does not yet exist.
If I understand what you want to do correctly, you should move the call to root.mainloop() to right after the creation of sl (sl = selects(root)). This way, Tk hits the mainloop, which runs until the window is destroyed, which is when the user double-clicks one of the values. Then, sl.getvalue() has been run and the program can continue with calling sl.returnvalue() without errors.
Since you are not actually calling the mainloop in that part of the code, I've altered your code to reflect that and still work as you want it to. A key method in this is wait_window, which halts execution in a local event loop until the window is destroyed. I've used this effbot page on Dialog Windows for reference:
import Tkinter as tk
lists = [1,2,3,4,5,6,7]
class selects():
def __init__(self,root):
self.root = root
self.selectwin()
def selectwin(self):
""" listbox and scrollbar for selection """
sb = tk.Scrollbar(self.root)
lb = tk.Listbox(self.root, relief ='sunken', cursor='hand2')
sb.config(command=lb.yview)
sb.pack(side=tk.RIGHT, fill=tk.Y)
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
lb.config(yscrollcommand=sb.set, selectmode='single')
for value in lists: lb.insert(tk.END,value)
lb.bind('<Double-1>',lambda event: self.getvalue())
self.listbox = lb
def getvalue(self):
""" get the selected value """
value = self.listbox.curselection()
if value:
self.root.quit()
text = self.listbox.get(value)
self.selectvalue = int(text)
self.root.destroy() # destroy the Toplevel window without needing the Tk mainloop
def returnvalue(self):
return self.selectvalue
class do():
def __init__(self, master):
self.top = tk.Toplevel()
self.top.transient(master) # Make Toplevel a subwindow ow the root window
self.top.grab_set() # Make user only able to interacte with the Toplevel as long as its opened
self.sl = selects(self.top)
self.top.protocol("WM_DELETE_WINDOW", self.sl.getvalue) # use the if value: in getvalue to force selection
master.wait_window(self.top) # Wait until the Toplevel closes before continuing
# do something... for example, get the value and print value+2, as coded below
value = self.sl.returnvalue()
print value+2
if __name__ == '__main__':
root = tk.Tk()
d = do(root)
root.mainloop()

How to automatically insert several tkinter items in Tk() window using classes

I apologize in advance if this is a stupid simple question, but i am really bad att python classes and can't seem to get it to work!
Here is my code:
from tkinter import *
a = Tk()
class toolsGUI():
def __init__(self, rootWin):
pass
def frame(self):
frame = Frame(rootWin)
frame.configure(bg = 'red')
frame.grid()
def button(self, binding, text):
btn = Button(rootWin, text=text)
btn.configure(bg = 'orange', fg = 'black')
btn.bind('<'+binding+'>')
btn.grid(row=1, sticky = N+S+E)
I simply want the button() or frame() to understand that rootWin is the same as in __init__, in this case rootWin should be variable a, thus placing the button in the Tk() window. After looking around, I understand that this is not the way to do it. Do anyone have another suggestion that might work?
You're pretty close. You are passing a to the toolsGUI initializer which is the right first step. You simply need to save this as an instance variable, then use the variable whenever you need to reference the root window:
def __init__(self, rootWin):
...
self.rootWin = rootWin
...
def frame(self):
frame = Frame(self.rootWin)
...
An alternative is to have toolsGUI inherit from Frame, in which case you can put all of the widgets in the frame instead of the root window. You then need the extra step of putting this frame inside the root window.
class toolsGUI(Frame):
def __init__(self, rootWin):
Frame.__init__(self, rootWin)
def frame(self):
frame = Frame(self)
...
a = Tk()
t = toolsGUI(a)
t.pack(fill="both", expand=True)
a.mainloop()
As a final bit of advice: don't user variables that are the same name as methods if you can avoid it. "frame" is a poor choice of function names. Instead, call it "create_frame" or something, otherwise it could be confused with class Frame and the local variable frame

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