The following script creates a tkinter window with a text label, exit button and change-text button:
from tkinter import *
from tkinter import ttk
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
test_label = Label(root, text="none").grid(row=0, column=0, sticky=W)
change_text_btn = Button(root, text="change_text", command=self.set_label_text).grid(row=2, column=0, sticky=W)
exit_btn = Button(root, text="Exit", command=self.client_exit).grid(row=2, column=1, sticky=W)
def set_label_text(self):
test_label.config(text='changed the value')
def client_exit(self):
exit()
if __name__ == '__main__':
root = Tk()
app = Window(root)
root.mainloop()
After click on change_text_btn I get a NameError: name 'test_label' is not defined error. So the problem is that test_label created in init_window() is not avaliable from set_label_text() beacuse of the scope. How do I fix it?
To overcome the issue, you can make test_label an instance variable by prefixing it with self. Besides that, when you chain methods like that, what happens is you assign None to your variable, since grid() returns None - instead, place each method in a separate line (this stands for all of your buttons):
self.test_label = Label(root, text="none")
self.test_label.grid(row=0, column=0, sticky=W)
Of course, you'd need to refer to it with self.test_label later on in your set_label_text function.
Other than that, I suggest you get rid of from tkinter import *, since you don't know what names that imports. It can replace names you imported earlier, and it makes it very difficult to see where names in your program are supposed to come from. Use import tkinter as tk instead.
Related
I am quite new with Tkinter and am trying to create a new window using this script while keeping the current window but i am get the error
_init_() missing 1 required positional argument: 'parent'. I am not really sure what the reason is but I am assuming that the command function for my button isn't working the way I want it.
The script currently looks something like this:
from tkinter import simpledialog
from tkinter import *
class Additional(simpledialog.Dialog):
def body(self, master):
#input fields
Label(master, text="Picture 3 Path:").grid(row=1)
#input fields for tags
#add as needed
self.e1 = Entry(master)
self.e1.grid(row=1, column=1, ipadx=150)
return self.e1 # initial focus
def apply(self):
first = self.e1.get()
self.ttag1 = (first)
class Initial(simpledialog.Dialog):
def body(self, master):
#input fields for username and passwords
Label(master, text="Usernames:").grid(row=1),
self.e1 = Entry(master)
self.b1 = Button(master, text = "Add More", bg= 'grey', command= Additional)
self.b1.grid(row=6, column=2, ipadx=75)
self.e1.grid(row=1, column=1, columnspan=2, ipadx=50)
return self.e1 # initial focus
def apply(self):
first = self.e1.get()
self.tag1 = (first)
root = tk.Tk()
root.withdraw()
d = Initial(root)
toor = tk.Tk()
toor.withdraw()
I have tried changing it up but it seems that it's not working right. Any ideas?
When calling the Additional class through the button command, you are not specifying what the parent root should be, and therefore the class fails to initiate. You can solve this by passing the master using a lambda
self.b1 = Button(master, text="Add More", bg='grey', command=lambda: Additional(master))
I am trying to get the following code to work where event calls a function to clear an entry box. Can someone tell me what I am doing wrong. I am not too familiar with Tkinter.
import tkinter as tk
from tkinter import *
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Create Trusted Facts")
self.pack(fill=BOTH, expand=True)
frame2 = Frame(self)
frame2.pack(fill=X)
reqSumLbl = Label(frame2, text="Request Summary", width=22)
reqSumLbl.pack(side='left', padx=5, pady=5)
reqSumBox = Entry(frame2, width=100, bg="White", fg="lightgrey", borderwidth=1)
reqSumBox.insert(0, "Enter the Request Summary here")
reqSumBox.pack(fill=X, padx=50, pady=5, expand=True)
reqSumBox.bind("<Button-1>", self.clear_reqSumBox)
def clear_reqSumBox(self, reqSumBox):
reqSumBox.delete(0, END)
reqSumBox.config(fg="black")
global SummaryText
SummaryText = reqSumBox.get()
def main():
root = Tk()
root.geometry("500x550+350+50")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
reqSumBox.bind("<Button-1>", self.clear_reqSumBox)
When binding any event to a function, it automatically needs to take in a parameter called event, there are 2 ways to fix your code.
1.
reqSumBox.bind("<Button-1>", lambda event: self.clear_reqSumBox)
Make lambda function which takes in event and calls function.
2.
def reqSumBox(self, reqSumBox, event=None)
Add optional event parameter in reqSumBox function.
I personally use the first one.
First of all, why do you have two imports at the start of your Python script at they're both the same library, choose one it's incorrect.
About your question, it fails because you didn't provide the clicked object, it provided you as the first argument of the bind function the Event that happened.
I recommend you make your object a part of your current working class (Class Example), like that:
import tkinter as tk
from tkinter import *
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Create Trusted Facts")
self.pack(fill=BOTH, expand=True)
frame2 = Frame(self)
frame2.pack(fill=X)
reqSumLbl = Label(frame2, text="Request Summary", width=22)
reqSumLbl.pack(side='left', padx=5, pady=5)
# Check my changes here.
self.reqSumBox = Entry(frame2, width=100, bg="White", fg="lightgrey", borderwidth=1)
self.reqSumBox.insert(0, "Enter the Request Summary here")
self.reqSumBox.pack(fill=X, padx=50, pady=5, expand=True)
self.reqSumBox.bind("<Button-1>", self.clear_reqSumBox)
# Changed the argument name to "event".
def clear_reqSumBox(self, event):
self.reqSumBox.delete(0, END)
self.reqSumBox.config(fg="black")
def main():
root = Tk()
root.geometry("500x550+350+50")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
Check where I comment and analyze this code.
The callback of bind() requires an argument which is the event object. So modify the callback function definition as below:
def clear_reqSumBox(self, event):
# get the widget triggering this event
reqSumBox = event.widget
reqSumBox.delete(0, END)
reqSumBox.config(fg="black")
# after that reqSumBox.get() will return empty string
global SummaryText
# SummaryText = "" will have same result of below line
SummaryText = reqSumBox.get()
However the entry will be cleared whenever you click on the entry. Is it really what you want?
I am working on simple application trying to utilize MVC pattern. The problem I found (which I predict is a really silly one, but I lost my rubber duck) is two tkinter apps are created where I expect just a single one. Moreover, second one is instantiated but is not visible what made me mad initially as I didn't know it exists ;-)
Smells to me like another Tk() instance is being created, but donno when, where and why.
When I replace root = Main() with root = tk.Tk() in AppDelegte everything works like a charm and just one app window is created, as intended.
App is created on following code:
App delegate
from controller.main import Main
root = Main()
root.mainloop()
Main controller
import tkinter as tk
from tkinter import ttk
from view.actual_readings import ActualReadings
class Main(tk.Tk):
actual_readings = ActualReadings(None)
def __init__(self):
super().__init__()
main_frame = ttk.Frame(self, padding="25")
main_frame.grid(column=0, row=0)
self.actual_readings.master = main_frame
self.actual_readings.grid(column=0, row=0)
Readings view
from tkinter import *
from tkinter import ttk
class ActualReadings(ttk.Frame):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
header = ttk.Label(self, text="header text", font="TkFixedFont", padding="75 25 75 10")
current_value_lbl = ttk.Label(self, text="current value",
font="TkFixedFont")
current_value_val = ttk.Label(self, text="here comes reading", font="TkFixedFont")
header.grid(column=0, row=0, columnspan=2)
current_value_lbl.grid(column=0, row=1, sticky=E)
current_value_val.grid(column=1, row=1, sticky=W)
It is because when you create Main (a Tk() instance), its class member actual_readings is created first. Since its parent is None which causes a default instance of Tk() being created. So there will be two Tk() instances.
To fix the issue, you should create actual_readings inside __init__() function:
class Main(tk.Tk):
actual_readings = None
def __init__(self):
super().__init__()
main_frame = ttk.Frame(self, padding='25')
main_frame.grid(row=0, column=0)
if self.actual_readings is None:
self.actual_readings = ActualReadings(main_frame)
self.actual_readings.grid(row=0, column=0)
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'd like to know how I can add or delete widgets from within an imported module. I fail to access them correctly. I know, using OOP would make it easier, but I tried to grasp OOP and while the principles are easy I can't get my head around the details, so since I lack a proper teacher, I need a procedural solution.
This is the main script:
#!/usr/bin/python
try:
# Python2
import Tkinter as tk
except ImportError:
# Python3
import tkinter as tk
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
import target
def myfunction(event):
canvas.configure(scrollregion=canvas.bbox("all"),width=300,height=200)
def test():
target.secondWindow()
root = tk.Tk()
root.geometry("600x350+30+50")
myframe = tk.Frame(root,relief="groove",bd=1)
myframe.place(x=20, y=30, width=560, height=200 )
canvas = tk.Canvas(myframe)
frame = tk.Frame(canvas)
myscrollbar=tk.Scrollbar(myframe, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=myscrollbar.set)
myscrollbar.pack(side="right", fill="y")
canvas.pack(side="left")
canvas.create_window((0,0), window=frame, anchor='nw')
allMissions = {
"1":{"name":"go"},
"2":{"name":"see"},
"3":{"name":"win"},
"4":{"name":"party"}} # this would be a text file
for a in allMissions.keys():
mn = allMissions[a]["name"]
tk.Label(frame, text=mn, justify="left").grid(row=int(a), column=0)
# what's bind really doing?
frame.bind("<Configure>", myfunction)
test = tk.Button(root, command=test, text="TEST")
test.place(x = 20, y = 250, width=580, height=40)
tk.mainloop()
and this is the imported module: target.py
try:
# Python2
import Tkinter as tk
except ImportError:
# Python3
import tkinter as tk
def changeMainWindow():
# here's where I'm stuck
print("What do I have to do to add a new")
print("label in the main window from here?")
print("Or to delete it?")
def secondWindow():
amWin = tk.Toplevel()
amWin.geometry("300x200+720+50")
button = tk.Button(amWin, text="OK", command=changeMainWindow)
button.place(x = 20, y = 80, width=260, height=30)
#amWin.mainloop() comment noticed (:
You do it by passing the memory address of whatever widget to the second program. There is no reason to import Tkinter again as you can just pass a pointer to the existing instance. If you are going to be doing anything more than simple experimenting with Tkinter, then it is well worth the time to learn classes first at one of the online sites like this one http://www.greenteapress.com/thinkpython/html/thinkpython016.html More here https://wiki.python.org/moin/BeginnersGuide/NonProgrammers
You aren't going to get many answers with the way the program is structured because most programmers use the class structure AFAIK, so do not know how to pound the code into a non-class environment, so will not have any answers. If the first program below used classes then the second program's class could be inherited, and the functions would become part of the first program's class and could be accessed in the same way as the existing classes, so no passing of pointers, or any other hack, would be necessary.
## I deleted some code for simplicity
def myfunction(event):
canvas.configure(scrollregion=canvas.bbox("all"),width=300,height=200)
def test():
TG.secondWindow()
root = tk.Tk()
root.geometry("600x350+30+50")
myframe = tk.Frame(root,relief="groove",bd=1)
myframe.place(x=20, y=30, width=560, height=200 )
canvas = tk.Canvas(myframe)
frame = tk.Frame(canvas)
myscrollbar=tk.Scrollbar(myframe, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=myscrollbar.set)
myscrollbar.pack(side="right", fill="y")
canvas.pack(side="left")
canvas.create_window((0,0), window=frame, anchor='nw')
# what's bind really doing?
frame.bind("<Configure>", myfunction)
test = tk.Button(root, command=test, text="TEST", bg="lightblue")
test.place(x = 20, y = 250, width=580, height=40)
tk.Button(root, text="Quit All", command=root.quit,
bg="orange").place(x=20, y=300)
""" instance of the class in the imported program
a pointer to the root window and the Tk instance are passed
"""
TG=target.Target(tk, root)
tk.mainloop()
And target.py. Notice there are no imports.
class Target():
def __init__(self, tk, root):
self.tk=tk
self.root=root
def changeMainWindow(self):
# here's where I'm stuck
self.tk.Label(self.amWin, bg="yellow", text =""""What do I have to do to add a new
label in the main window from here?
Or to delete it?""").place(x=50,y=20)
def secondWindow(self):
self.amWin = self.tk.Toplevel(self.root)
self.amWin.geometry("300x200+720+50")
button = self.tk.Button(self.amWin, text="Add Label",
command=self.changeMainWindow)
button.place(x = 20, y = 90, width=260, height=30).