I have a small GUI app in Python that uses Tk. It has several filters --- a text entries (they set filter values) with checkboxes (which set filter on/off). I create filters as a class inhetrited from ttk's Labelframe:
from tkinter.ttk import Labelframe
import tkinter as tk
class FilterWidget(Labelframe):
def __init__(self, parent, label):
Labelframe.__init__(self, parent, text=label)
self.grid()
self._entry = tk.Entry(self)
self._entry.grid(row=0, column=0)
self._checkbox = tk.Checkbutton(self, command=lambda: print(self))
self._checkbox.grid(row=0, column=1)
Then I create several instances of this class and place them in GUI:
root = tk.Tk()
source_filter = FilterWidget(root, "Source")
source_filter.grid(row=0, column=0)
level_filter = FilterWidget(root, "Severity")
level_filter.grid(row=0, column=1)
root.mainloop()
The widgets are created and correctly displayed. However, when one of the checkboxes is clicked and changes state, the other changes state as well!
When diffrent checkboxes are clicked, print command outputs .!filterwidget and .!filterwidget2, so those are separate objects. It seems that they are somehow implicitly syncronized, but I have no idea how did this happend.
So, the question is: how to remove this dependancy and make checkboxes independant of each other?
As the docs mention,
To use a Checkbutton, you must create a Tkinter variable. To inspect
the button state, query the variable.
Here's your code updated to use an IntVar to store the Checkbutton state.
from tkinter.ttk import Labelframe
import tkinter as tk
class FilterWidget(Labelframe):
def __init__(self, parent, label):
Labelframe.__init__(self, parent, text=label)
self._entry = tk.Entry(self)
self._entry.grid(row=0, column=0)
self._var = tk.IntVar()
callback = lambda: print(self._entry.get(), self._var.get())
checkbox = tk.Checkbutton(self, variable=self._var, command=callback)
checkbox.grid(row=0, column=1)
root = tk.Tk()
source_filter = FilterWidget(root, "Source")
source_filter.grid(row=0, column=0)
level_filter = FilterWidget(root, "Severity")
level_filter.grid(row=0, column=1)
root.mainloop()
Related
Is there any way in Python/tkinter to access child elements referring by their variable names, but from an other function?
For example in VBA, it is possible to directly refer to an element of an other window by its name.
For example if I have two windows, UserForm1 and UserForm2 I can change the text value of Label1 of UserForm2 by clicking a button on UserForm1.
Private Sub CommandButton1_Click()
UserForm2.Label1.Caption = "Changed"
End Sub
In tkinter I have found the winfo_children() to access child elements. Is there any way to access them by their names?
See my sample code below:
import tkinter
from tkinter import *
#----------------------------------------------------------------------
def new(parent_window):
""""""
parent_window.withdraw()
global main_window
global new_window
new_window = tkinter.Tk()
new_window.title("My App - New")
label1 = tkinter.Label(new_window, text="NEW")
label1.grid(row=0,column=0,columnspan=3,pady=10,padx=10, sticky="nsw")
b1 = tkinter.Button(new_window, text="Change It", command=lambda: showdashboard(new_window))
b1.grid(row=4,column=1,padx=20,pady=10,sticky="nwse")
b2 = tkinter.Button(new_window, text="Quit", command=lambda: quit())
b2.grid(row=5,column=1,padx=20,pady=10,sticky="nwse")
#----------------------------------------------------------------------
def dashboard(parent_window):
""""""
parent_window.withdraw()
global main_window
global dashboard_window
dashboard_window = tkinter.Tk()
dashboard_window.title("My App - Dashboard")
label1 = tkinter.Label(dashboard_window, text="Dashboard")
label1.grid(row=0,column=0,columnspan=3,pady=10,padx=10, sticky="nsw")
b1 = tkinter.Button(dashboard_window, text="New", command=lambda: new(dashboard_window))
b1.grid(row=4,column=1,padx=20,pady=10,sticky="nwse")
#----------------------------------------------------------------------
def showdashboard(parent_window):
""""""
parent_window.withdraw()
dashboard_window.update()
dashboard_window.deiconify()
#This way it works <<<<<<<<<<<<<<------!!!!!!!
byID=dashboard_window.winfo_children()
byID[0].config(text="change the value")
#But I am looking for somethin like this <<<<<<<<<<<<<<------????
dashboard_window.label1.config(text="changed the value")
#----------------------------------------------------------------------
main_window=tkinter.Tk()
main_window.title("MyApp")
label = tkinter.Label(main_window, text="My App")
label.grid(row=0,column=0,pady=10,padx=10,sticky="nwse")
b1 = tkinter.Button(main_window, text="Dashboard", command=lambda:dashboard(main_window))
b1.grid(row=1,column=0,padx=20,pady=10,sticky="nwse")
main_window.mainloop()
winfo_children() returns an instance of the class associated with the type of widget along with the name that tkinter assigned to the actual tk object.
This means that yes, we can refer to the name of widget, although I'm not sure what advantage this would really give you other than not needing to assign the label to a variable.
from tkinter import *
root = Tk()
Label(root, text="Label1").pack()
label2 = Label(root, name="name", text="Label2")
label2.pack()
print(root.winfo_children())
print(root.nametowidget('.!label'))
print(str(label2))
Button(root, text="Delete label2", command=lambda: root.nametowidget(".name").destroy()).pack()
The above will result in two Label widgets and a Button widget appearing in the window. The first Label is not stored in a variable and yet we can quite happily call it inside the print statement. The second is stored in a variable but you can see that in the command of the Button we don't refer to the variable but the name attribute of the Label.
Bryan Oakley has a fantastic answer here explaining this.
I'm not sure what you mean by names in:
"Is there any way in Python/tkinter to access child elements referring by their names?"
You can access widgets simply by their object references:
# Procedural
import tkinter as tk
def change():
object_reference['text'] = "Changed!"
root = tk.Tk()
object_reference = tk.Label(root, text="This is a label for root-window")
object_reference.pack()
another_window = tk.Toplevel(root)
btn_in_other_window = tk.Button(another_window, text="Change")
btn_in_other_window['command'] = change
btn_in_other_window.pack()
root.mainloop()
or if they were to be defined with more of an object-oriented approach, you can make use of the .(dot) notation:
#oop
import tkinter as tk
class App1(tk.Toplevel):
def __init__(self, master):
super().__init__()
self.label = tk.Label(self, text="This is App1's label")
self.label.pack()
class App2(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.button = tk.Button(self, text="This is a App2's button")
self.button.pack()
def change(label):
label['text'] = "Changed!"
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
app1 = App1(root)
app2 = App2(root)
app2.button['command'] = lambda label=app1.label: change(label)
root.mainloop()
As an alternative, it is possible to get a list of the children widgets of the element window with:
root.update()
lista = list(root.children.values())
Then it is possible to refer to the list elements and do whatever with the widget itself. For example to get the width of the first widget do print(lista[0].winfo_width()).
Warning: I am not sure that the list contains the elements in the same order that the widgets appear in the script, although for me it worked in this order. Hopping someone will write in the comments.
I cannot make the checkbox to give a "1" value when its marked and the button is clicked. I don't know if the problem is within the checkbutton code or I am transferring the data between functions wrong
import tkinter as tk
from tkinter import ttk as ttk
var_koszula1=2
class Aplikacja(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.grid()
self.var_koszula1=tk.IntVar(self)
tk.Checkbutton(self, text='MARK', variable=var_koszula1).grid()
print(self.var_koszula1.get())
tk.Button(self, height=1, width=15, text=("Click"), command= self.create_window_edit).grid()
def create_window_edit(self):
t = tk.Toplevel(self)
tk.Label(t, text=("Done")).grid()
print(self.var_koszula1.get())
root= tk.Tk()
root.title("Szaffa")
app= Aplikacja(root)
root.mainloop()
You cannot use a normal variable with the variable attribute. It must be one of the special tkinter variables StringVar, IntVar, DoubleVar or BooleanVar.
You probably just need to change this:
tk.Checkbutton(..., variable=var_koszula1).grid()
To this:
tk.Checkbutton(..., variable=self.var_koszula1).grid()
I'm fairly new to python, and am struggling with a problem with tkinter. I am attempting to create an application which has a main menu, with buttons to open child windows which will contain distinct functions. I can create child windows, but I'm having trouble getting the values entered in Entry widgets in the child window. I wrote a really simplified version (python 3):
from tkinter import (Frame, Label, Entry, StringVar, Toplevel, Tk, Button)
class main(Frame):
def __init__(self):
Frame.__init__(self)
self.master.minsize(width=250, height=250)
self.master.title("main window")
self.grid()
newWindowButton = Button(self,
text="open new window",
command=self.newWindowOpen)
newWindowButton.grid(row=0, column=0)
def newWindowOpen(self):
childWindow = Toplevel()
childWindow.wm_title("child window")
childWindow.itemLabel = Label(childWindow, text="Test Value")
childWindow.itemVar = StringVar()
childWindow.itemEntry = Entry(childWindow,
textvariable=childWindow.itemVar)
childWindow.itemLabel.grid(row=0, column=0)
childWindow.itemEntry.grid(row=0, column=1)
childWindow.submitButton = Button(childWindow,
text="submit",
command=self.submitTest)
childWindow.submitButton.grid(row=1, column=0)
def submitTest(self):
value = self.itemVar.get()
print(value)
root = Tk()
main_menu = main()
main_menu.mainloop()
This won't work, as submitTest is trying to get the value of 'main.itemVar', which of course doesn't exist.
I'm guessing the method submitTest is outside the scope of the childWindow widget, but I'm not sure how to pass the itemVar down appropriately. What's the correct way to do this? Would I be better served creating the childWindow as it's own class, and creating new objects via the main menu?
For background, the child windows will be interacting with a database (sqlite), and all select / update functions will be self-contained in the child windows. The main menu's only function is to open the different parts of the application, it will never need to access the results of a child window directly.
As I understand it is possible in your application to create more that one childwindow. So you can't create one self.itemVar. You can create dict with instance of childwindow in key and instance of itemVar in value. And you can pass instance on childWindow in submitTest. And then you can access to corresponding itemVar
from tkinter import (Frame, Label, Entry, StringVar, Toplevel, Tk, Button)
class main(Frame):
def __init__(self):
Frame.__init__(self)
self.master.minsize(width=250, height=250)
self.master.title("main window")
self.grid()
self.children_dict = dict()
newWindowButton = Button(self,
text="open new window",
command=self.newWindowOpen)
newWindowButton.grid(row=0, column=0)
def newWindowOpen(self):
childWindow = Toplevel()
childWindow.wm_title("child window")
childWindow.itemLabel = Label(childWindow, text="Test Value")
childWindow.itemVar = StringVar()
childWindow.itemEntry = Entry(childWindow,
textvariable=childWindow.itemVar)
self.children_dict[childWindow] = childWindow.itemVar
childWindow.itemLabel.grid(row=0, column=0)
childWindow.itemEntry.grid(row=0, column=1)
childWindow.submitButton = Button(childWindow,
text="submit",
command=lambda: self.submitTest(childWindow))
childWindow.submitButton.grid(row=1, column=0)
def submitTest(self, childWindow):
value = self.children_dict[childWindow].get()
print(value)
root = Tk()
main_menu = main()
main_menu.mainloop()
I'm running Python 2.7.9 on a Mac. I've been unable to figure out why it is when I run my programs that only the Entry Widgets highlight each time I hit the Tab key to move to the next widget. Following is some test code. When I run the script and hit the Tab key, the first entry field is highlighted. The next time I hit the Tab key, the second entry field is highlighted. However, when I hit the tab key to move to the Button Widget, the Button is receiving the focus but there is not highlight to visually indicate to the user the focus.
The OptionMenu widget is skipped altogether, which is also a mystery. Both the radiobutton and the checkbox receives focus, just like the button widget, and again no highlight is present.
I've tried a variety of .config() arrangements to no avail. What am I missing?
from tkinter import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.grid()
#Tests to make sure that Button receives focus.
def yup(self):
print "yup"
entry1 = Entry(frame)
entry1.pack()
entry2 = Entry(frame)
entry2.pack()
button1 = Button(frame, text="Test")
button1.pack()
button1.bind('<Return>', yup)
var1 = IntVar()
c = Checkbutton(frame, text="Expand", variable=var1)
c.pack()
var2 = StringVar()
radio = Radiobutton(frame, text="Test", variable=var2, value=1)
radio.pack()
var3 = StringVar()
optionmenu1 = OptionMenu(frame, var3, "one", "two", "three")
optionmenu1.pack()
root = Tk()
root.geometry('400x400+0+0')
app = App(root)
root.mainloop()
It sounds like you need to configure OS X for "Full Keyboard Access" to allow Tab to focus on all UI controls (versus just text boxes and lists).
In Yosemite (10.10), this setting can be found under System Preferences > Keyboard > Shortcuts, and can be toggled with Control+F7. Note that has nothing to do with Python, and will occur system-wide.
EDIT
So after doing some more testing, there appears to be some issues with the actual highlighting of certain widgets using tk on a Mac. Below is a condensed version of your original sample with some minor modifications for simplicity.
import Tkinter as tk
import ttk # more on this in a minute
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
entry1 = tk.Entry(self)
entry1.pack()
entry2 = tk.Entry(self)
entry2.pack()
button1 = tk.Button(self, text="Test", command=self.yup)
button1.pack()
def yup(self):
print("yup")
# ...
root = tk.Tk()
app = App(root).pack()
root.mainloop()
With full keyboard access enabled as previously mentioned, I can verify that the button does indeed receive focus: on the third tab, after the first two entry widgets, hitting <space> "clicks" the button and prints to stdout. However there is no visual indication that the button is selected.
Changing the button from a tk.Button to a ttk.Button "fixes" this, and does indeed show the normal "selection" frame around the button when tabbing through the UI.
button1 = tk.Button(self, text="Test", command=self.yup)
# change this to:
button1 = ttk.Button(self, text="Test", command=self.yup)
I have no idea why this is, and I don't know what the consensus about tkinter.ttk is, but I prefer ttk to "plain" tk as it seems to produce widgets which appear more "native" to OS X in my experience. Note I also removed the bind statement, and am reporting my result using the OS X default of space to activate UI elements with full keyboard access enabled.
More on ttk here. Note also that not all tk widgets have a ttk implementation, and that there are also some ttk widgets which do not exist in tk.
Lastly below find the "ttk" version of the original snippet.
import Tkinter as tk
import ttk
class App(ttk.Frame):
def __init__(self, master):
ttk.Frame.__init__(self, master)
self.master = master
entry1 = ttk.Entry(self)
entry1.pack()
entry2 = ttk.Entry(self)
entry2.pack()
button1 = ttk.Button(self, text="Test", command=self.yup)
button1.pack()
def yup(self):
print("yup")
# ...
root = tk.Tk()
app = App(root).pack()
root.mainloop()
Hope this helps.
Try changing the background and highlightbackground colors as below but the problem is possibly because of the way the program is indented --> run the second code block.
top=Tk()
## active background
Button(top, text="Quit", bg="lightblue", activebackground="orange",
command=top.quit).grid(row=1)
top.mainloop()
##--------------- note the 3 lines that have been changed ---------
class App:
## function was not indented
def __init__(self, master):
frame = Frame(master)
frame.grid()
entry1 = Entry(frame)
entry1.pack()
entry1.focus_set()
entry2 = Entry(frame)
entry2.pack()
button1 = Button(frame, text="Test")
button1.pack()
## function called incorrectly
button1.bind('<Return>', self.yup)
var1 = IntVar()
c = Checkbutton(frame, text="Expand", variable=var1)
c.pack()
var2 = StringVar()
radio = Radiobutton(frame, text="Test", variable=var2, value=1)
radio.pack()
var3 = StringVar()
optionmenu1 = OptionMenu(frame, var3, "one", "two", "three")
optionmenu1.pack()
Button(frame, text="Quit", bg="orange", command=master.quit).pack()
## function indented too far
#Tests to make sure that Button receives focus.
def yup(self, args):
print "yup"
root = Tk()
root.geometry('400x400+0+0')
app = App(root)
root.mainloop()
Widget tk.Entry from example_script.py do not save value 'textvariable' field.
example_script.py:
import Tkinter as tk
class App(tk.Frame):
def __init__(self, master, text):
tk.Frame.__init__(self, master)
textVar = tk.StringVar()
textVar.set(text)
entryVar = tk.Entry(self, textvariable=textVar).pack()
self.pack()
def main():
root = tk.Tk()
text = ['text1', 'text2', 'text3']
for i in text:
App(root, i)
root.mainloop()
main_script.py:
import Tkinter import example_script as ex
if __name__ == '__main__':
root = Tkinter.Tk()
Tkinter.Button(root, text='press', command=lambda: ex.main()).pack()
root.mainloop()
If I change row 'entryVar = tk.Entry(self, textvariable=textVar).pack()' to
entryVar = tk.Entry(self)
entryVar.pack()
entryVar.insert(0, text)
field's value is updated. Why?
How will be correct open new window from imported script? Tkinter.Toplevel() is not suitable. Now I use subprocess.Popen.
When you do entryVar = tk.Entry(self).pack(), entryVar will be set to None because that is what pack() returns. When you call pack on a separate line, entryVar gets set to what you think it does.
You cannot create two instances of the Tk class in one program. Tkinter is not designed to work that way.