Close two windows with one click in tkinter - python

I am trying to get a button in one function to close the window generated from another function. Here is a shortened version of the code. Basically I want the top window generated from add_drink to be closed when the close_button is clicked at the save_drink stage. How can I do this?
def save_drink(added_drink):
drinks_list = []
newtop = Toplevel(root)
newtop.geometry("200x200")
newtop.title("Drink Added")
label = Label(newtop, text= "{} Added".format((added_drink.get())), font=('Mistral 10')).pack()
close_button = Button(newtop, text="Close", command=newtop.destroy)
close_button.pack()
drinks_list.append(added_drink.get())
def add_drink():
top = Toplevel(root)
top.geometry("750x250")
top.title("Record Drink")
label = Label(top, text= "What drink did you have?", font=('Mistral 18')).pack()
added_drink = Entry(top, font=6)
added_drink.pack()
added_drink_button = Button(top, text='Add Drink', font=3,
command=lambda: save_drink(added_drink)).pack()

You could try passing top from add_drink as a parameter to save_drink
added_drink_button = Button(
top,
text='Add Drink',
font=3,
command=lambda top=top: save_drink(added_drink, top) # add top to the lambda
).pack()
then modify save_drink to accept the top parameter and dispose of it accordingly
in a new function that handles closing both Toplevel widgets
def save_drink(added_drink, top): # add 'top' here
drinks_list = []
newtop = Toplevel(root)
newtop.geometry("200x200")
newtop.title("Drink Added")
label = Label(
newtop,
# I suspect there's some extra parentheses around 'format'
text= "{} Added".format((added_drink.get())), font=('Mistral 10')
).pack()
close_button = Button(
newtop,
text="Close",
# call the 'close' function using a lambda to pass args
command=lambda t=top, n=newtop: close(t, n)
)
close_button.pack()
drinks_list.append(added_drink.get())
This function will close both windows when called by close_button
def close(top, newtop): # pass both windows as parameters
top.destroy()
newtop.destroy()
This should work, though I suspect it might be easier to wrap both the add_drink and save_drink functions in a simple class so they can share information more easily.

Related

Tkinter - Python, how do I cause a button click to assign a value to a variable?

Using Tkinter and Python. Already created a window for the buttons to be placed on. I want there to be four buttons to appear, and I want to be able to click one of the four buttons, and be able for it to set the selection variable = "whatever I clicked", so that I can then use this variable later to call an API. When I run the program and click on the "General knowledge" button and print the selection, it does correctly print "General knowledge", but then when I try to return this selection variable it just doesn't work and I don't know why.
def select1():
selection = "General Knowledge"
print(selection)
def select2():
selection = "Science"
def select3():
selection = "Entertainment"
def select4():
selection = "Miscellaneous"
button1 = tk.Button(text = "General Knowledge", command = select1)
button1.place(x=100, y=100)
button2 = tk.Button(text = "Science", command = select2)
button2.place(x=100, y=140)
button3 = tk.Button(text = "Entertainment", command = select3)
button3.place(x=100, y=180)
button4 = tk.Button(text = "Miscellaneous", command = select4)
button4.place(x=100, y=220)
There are several ways to accomplish your goal.
One way is to write a single function that will take a value to assign to your variable. This way you can have as many buttons as you like and only a single function.
Not if you are using functions you have to either pass the variable to the function or let the function know it is in the global namespace.
import tkinter as tk
root = tk.Tk()
selection = ''
def assign_value(value):
global selection
selection = value
lbl["text"] = value
print(selection)
lbl = tk.Label(root, text='Selection Goes Here')
lbl.grid(row=0, column=0)
tk.Button(text="General Knowledge", command=lambda: assign_value("General Knowledge")).grid(row=1, column=0)
tk.Button(text="Science", command=lambda: assign_value("Science")).grid(row=2, column=0)
tk.Button(text="Entertainment", command=lambda: assign_value("Entertainment")).grid(row=3, column=0)
tk.Button(text="Miscellaneous", command=lambda: assign_value("Miscellaneous")).grid(row=4, column=0)
root.mainloop()
Or you can assign the value directly from the button.
import tkinter as tk
root = tk.Tk()
selection = tk.StringVar()
selection.set('Selection Goes Here')
lbl = tk.Label(root, textvariable=selection)
lbl.grid(row=0, column=0)
tk.Button(text="General Knowledge", command=lambda: selection.set("General Knowledge")).grid(row=1, column=0)
tk.Button(text="Science", command=lambda: selection.set("Science")).grid(row=2, column=0)
tk.Button(text="Entertainment", command=lambda: selection.set("Entertainment")).grid(row=3, column=0)
tk.Button(text="Miscellaneous", command=lambda: selection.set("Miscellaneous")).grid(row=4, column=0)
root.mainloop()
I am sure if I spent more time on this I could think up something else but the idea is basically write your code in a more DRY (Don't Repeat Yourself) fashion and make sure you are assigning the value to the variable in the global namespace or else it will not work as you expect.

Tkinter is returning same value for every radio button?

I am popping up a custom dialog box using Tkinter.
I am opening it from another Tkinter window.
root = Tk()
class ListDialog:
def __init__(self, names, prompt):
self.names = names
self.sub_root = Tk()
self.sub_root.title("Intovex")
self.sub_root.iconbitmap("Icon.ico")
self.myfont = Font(root=self.sub_root, family="Arial", size=8)
self.sub_root.maxsize(320, 240)
self.sub_root.wm_attributes("-topmost", True)
self.sub_root.wm_attributes("-toolwindow", True)
self.var = IntVar()
label = Label(self.sub_root, text=prompt)
label.pack(fill=X)
c=1
print(names)
for i in names:
print(i)
r = Radiobutton(self.sub_root, text=i, variable=self.var, value=c, command=self.end)
r.pack(anchor=W)
c+=1
self.var.set(1)
button = Button(self.sub_root, command=self.endit, text="OK", bg = "#448DE0", fg="White", bd=0, width=12, pady=4, padx=4, height=1,font=self.myfont, highlightcolor="#A3C7F0")
button.pack(side=BOTTOM)
self.choice = names[0]
def end(self):
ch = self.var.get()
print(str(ch))
self.choice = self.names[ch - 1]
def endit(self):
self.sub_root.destroy()
def ask(self):
self.sub_root.mainloop()
var2 = StringVar()
def get_choice():
list = ListDialog(["Test1", "Test2"], "Testing")
list.ask()
var2.set(str(list.choice))
label = Label(root, text="", textvariable=var2)
button = Button(root, text="Test", command=get_choice)
label.pack()
button.pack()
root.mainloop()
However, it works when it is run alone by directly instantiating the class and invoking ask() method.
You may have seen that I have print statements everywhere in the code(it is for debugging) and I found where isn't it working
The print statement to print the names list parameter is printing the whole list correctly.
Inside the for-loop also it prints the elements in the names list correctly
When I click on a radio button it invokes the end() method. There it always prints the default value.
root = Tk()
class ListDialog:
def __init__(self, names, prompt):
self.names = names
self.sub_root = Tk()
self.sub_root.title("Intovex")
self.sub_root.iconbitmap("Icon.ico")
self.myfont = Font(root=self.sub_root, family="Arial", size=8)
self.sub_root.maxsize(320, 240)
self.sub_root.wm_attributes("-topmost", True)
self.sub_root.wm_attributes("-toolwindow", True)
self.var = IntVar()
label = Label(self.sub_root, text=prompt)
label.pack(fill=X)
c=1
print(names)
for i in names:
print(i)
r = Radiobutton(self.sub_root, text=i, variable=self.var, value=c, command=self.end)
r.pack(anchor=W)
c+=1
self.var.set(1)
button = Button(self.sub_root, command=self.endit, text="OK", bg = "#448DE0", fg="White", bd=0, width=12, pady=4, padx=4, height=1,font=self.myfont, highlightcolor="#A3C7F0")
button.pack(side=BOTTOM)
self.choice = names[0]
def end(self):
ch = self.var.get()
print(str(ch))
self.choice = self.names[ch - 1]
def endit(self):
self.sub_root.destroy()
def ask(self):
self.sub_root.mainloop()
list = ListDialog(["Test1", "Test2"], "Testing")
list.ask()
print(list.choice)
But it works if I open it as a TopLevel widget. But then the main window doesn't wait till the popup windows returns the value(choice).
The problem with the code in the first snippet is because you're calling Tk() more that once within the tkinter application — it confuses the interface code and can cause a variety of problems, as you're finding out.
If you replace the call inside the __init__() method of the ListDialog class, with one to tk.Toplevel() instead, then your code will start working.
I also streamlined the for loop that creates the Radiobuttons by changing it so to use the built-in enumerate() function to automatically keep a count of the names. In conjunction with that, I made the initial value of the IntVar zero which is not one of the values that selecting one the radiobuttons will assign it. This is so when the ListDialog is first displayed, none of them will be selected. It also ensures that the end() callback function will get called whenever the user presses one of them, so your app will always be informed when that happens.
Note that, although I didn't change it, you shouldn't name a variable list because that hides the name of the built-in class by that name. In general you should avoid naming anything something that conflicts with an existing standard Python name.
from tkinter import *
from tkinter.font import Font
root = Tk()
class ListDialog:
def __init__(self, names, prompt):
self.names = names
# self.sub_root = Tk() # Wrong - don't call Tk() more than once.
root.withdraw() # Hide root window.
self.sub_root = Toplevel() # Create another top-level window.
self.sub_root.title("Intovex")
# self.sub_root.iconbitmap("Icon.ico") # I don't have this file...
self.myfont = Font(root=self.sub_root, family="Arial", size=8)
self.sub_root.maxsize(320, 240)
self.sub_root.wm_attributes("-topmost", True)
self.sub_root.wm_attributes("-toolwindow", True)
self.var = IntVar(value=0) # Define and init value to one *not* produced by btns.
label = Label(self.sub_root, text=prompt)
label.pack(fill=X)
print(names)
for c, name in enumerate(names, start=1):
print(c)
r = Radiobutton(self.sub_root, text=c, variable=self.var, value=c,
command=self.end)
r.pack(anchor=W)
button = Button(self.sub_root, command=self.endit, text="OK", bg = "#448DE0",
fg="White", bd=0, width=12, pady=4, padx=4, height=1,
font=self.myfont, highlightcolor="#A3C7F0")
button.pack(side=BOTTOM)
self.choice = names[0]
def end(self):
ch = self.var.get()
print(str(ch))
self.choice = self.names[ch - 1]
def endit(self):
self.sub_root.destroy()
root.deiconify() # Reshow root window.
def ask(self):
self.sub_root.mainloop()
var2 = StringVar()
def get_choice():
list = ListDialog(["Test1", "Test2"], "Testing")
list.ask()
var2.set(str(list.choice))
label = Label(root, text="", textvariable=var2)
button = Button(root, text="Test", command=get_choice)
label.pack()
button.pack()
root.mainloop()

How to get widget name in event?

from tkinter import *
main = Tk()
def flipper(event):
# I'd like to do this:
#if widgetname == switcher:
#do stuff
#if widgetname == switcher1:
#do stuff
return
switcher = Label(main, bg='white', text="click here", font="-weight bold")
switcher.grid()
switcher.bind("<Button-1>", flipper)
switcher1 = Label(main, bg='white', text="click here", font="-weight bold")
switcher1.grid()
switcher1.bind("<Button-1>", flipper)
switcher2 = Label(main, bg='white', text="click here", font="-weight bold")
switcher2.grid()
switcher2.bind("<Button-1>", flipper)
switcher3 = Label(main, bg='white', text="click here", font="-weight bold")
switcher3.grid()
switcher3.bind("<Button-1>", flipper)
switcher4 = Label(main, bg='white', text="click here", font="-weight bold")
switcher4.grid()
switcher4.bind("<Button-1>", flipper)
switcher5 = Label(main, bg='white', text="click here", font="-weight bold")
switcher5.grid()
switcher5.bind("<Button-1>", flipper)
main.mainloop()
In my event function I'd like to do different things based on the label that is clicked. What im stumped on is that I can only get the identifier number of the widget that is clicked, not the name. If I could get the identifier of all my widgets then I could do:
def flipper(event):
if event.widget == switcher.identifier():
do stuff
but I can't find how to get the id of a specified widget either...
How can I get the name of a widget by its identifier (event.widget())?
Or how can I get the identifier of a specified widget name?
If neither are possible, then I'd have to make a different function and bind for each label which is a lot of work that hopefully is not necessary.
Edit:
from tkinter import *
main = Tk()
def flipper(event, switch):
if switch.widget == 's1':
print("got it")
switcher = Label(main, bg='white', text="click here", font="-weight bold")
switcher.grid()
switcher.bind("<Button-1>", flipper)
switcher.widget = 's1'
main.mainloop()
You can't get the variable name that the widget is assigned to, that would be relatively useless. A widget could be assigned to more than one variable, or none at all.
Getting the label text
You have access to the actual widget, and you can use that to get the text that is on the label. Your example shows that all labels are the same, so this might not be useful to you:
def flipper(event):
print("label text:", event.widget.cget("text"))
Using a custom widget name
You can also give a widget a name. You can't get back precisely the name, but you can come very close. For example, if you create a label like this:
switcher = Label(main, name="switcher", bg='white', text="click here", font="-weight bold")
You can get the string representation of the widget by splitting on "." and taking the last value:
def flipper(event):
print("widget name:", str(event.widget).split(".")[-1])
Passing a name via the binding
Finally, you can set up your bindings such that the name is sent to the function:
switcher.bind("<Button-1>", lambda event: flipper(event, "switcher"))
switcher1.bind("<Button-1>", lambda event: flipper(event, "switcher1"))
You can use event.widget to get standard parameters from clicked widget
example:
import tkinter as tk
def callback(event):
print(event.widget['text'])
main = tk.Tk()
switcher = tk.Label(main, text="click here")
switcher.grid()
switcher.bind("<Button-1>", callback)
main.mainloop()
You can assign own variables to widgets
switcher.extra = "Hello"
and then get it
event.widget.extra
example:
import tkinter as tk
def callback(event):
print(event.widget['text'])
print(event.widget.extra)
main = tk.Tk()
switcher = tk.Label(main, text="click here")
switcher.grid()
switcher.bind("<Button-1>", callback)
switcher.extra = "Hello"
main.mainloop()
You can use lambda to bind function with arguments
bind("<Button-1>", lambda event:callback(event, "Hello"))
example:
import tkinter as tk
def callback(event, extra):
print(event.widget['text'])
print(extra)
main = tk.Tk()
switcher = tk.Label(main, text="click here")
switcher.grid()
switcher.bind("<Button-1>", lambda event:callback(event, "Hello"))
main.mainloop()
I had the same issue I found easy way was to use bind method.
apparent name property is private but can be accessed via _name
This is useful if you plan to generate widgets dynamically at runtime
# Import Module
from tkinter import *
# create root window
root = Tk()
# root window title and dimension
root.title("Welcome to Test window")
# Set geometry (widthxheight)
root.geometry('350x200')
#adding a label to the root window
lbl = Label(root, text = "Press a button")
lbl.grid()
#define mouse up event
def mous_up(ev:Event):
#get calling widget from event
sender:Button = ev.widget
#set label text
lbl.configure(text = sender._name + " up")
#read foreground color from button
#If red make green, else make red
if sender.cget('fg') == "red":
#normal color
sender.configure(fg='lime')
#mouse over color
sender.configure(activeforeground='green')
else:
#normal color
sender.configure(fg="red")
#mouse over color
sender.configure(activeforeground='darkred')
#define mouse down event
def mous_down(ev:Event):
lbl.configure(text = str(ev.widget._name) + " down")
# button widget with red color text
# inside
btn = Button(root, text = "Click me" ,
fg = "red",name = "button-A")
#bind mouse up and mouse down events
btn.bind('<ButtonRelease-1>',mous_up)
btn.bind('<Button-1>',mous_down)
# set Button grid
btn.grid(column=0, row=1)
#Create another button
btn = Button(root, text = "Click me2" ,
fg = "red",name="button2")
#bind mouse up and mouse down events
btn.bind('<ButtonRelease-1>',mous_up)
btn.bind('<Button-1>',mous_down)
#absolute placement of button instead of
#using grid system
btn.place(x=50,y=100)
# all widgets will be here
# Execute Tkinter
root.mainloop()
Quick and dirty - you could have the function check a switcher attribute.
def flipper(event, switch):
if switch.widget == 's1':
do_stuff
return stuff
if switch.widget == 's2':
do_stuff
return stuff
switcher1.widget = 's1'
switcher2.widget = 's2'
I know this is an old post, but I had the same problem and I thought I should share a solution in case anyone is interested. You can give your widget a name by creating a subclass of the widget. E.g. "Button" is a widget. You can make a child widget "MyButton" which inherits from button and then add an instance variable to it (e.g. name, uniqueID etc.)
Here is a code snippet
class MyButton(Button):
def __init__(self, master = None, textVal = "", wName = ""):
Button.__init__(self, master, text = textVal)
self.widgetID = wName #unique identifier for each button.
When you want to create a new button widget, use
b = MyButton(.....),
instead of
b = Button(.......)
This way, you have all the functionality of a button, plus the unique identifier.

tkinter dynamic OptionMenu command not working

I'm using python 2.7.9 and my current issue is that for some reason, my OptionMenu's command isn't working. Below is sample code of what I mean.
from Tkinter import *
root = Tk()
var = StringVar()
var.set("Choose a name...")
names = []
# Appends names to names list and updates OptionMenu
def createName(n):
names.append(n)
personName.delete(0, "end")
menu = nameMenu['menu']
menu.delete(0, "end")
for name in names:
menu.add_command(label=name, command=lambda name=name: var.set(name))
# what to run when a name is selected
def selection():
print "Running" # For testing purposes to see when/if selection runs
print var.get()
# Option Menu for names
nameMenu = OptionMenu(root, var, (), command=lambda: selection())
nameMenu.grid(row=0, column=0, columnspan=2)
nameMenu.config(width=20)
# Entry for user to submit name
Label(root, text="Name").grid(row=1, column=0)
personName = Entry(root, width=17)
personName.grid(row=1, column=1)
# Add person Button
Button(root, text="Add Person", width=20, command=
lambda: createName(personName.get())).grid(row=5, column=0, columnspan=2)
mainloop()
The purpose of this theoretical program is just to add a name to the OptionMenu, and then when you select the name, it will print it. I can add names to the OptionMenu just fine, but when it comes time for the OptionMenu to run the selection() function, it won't.
Now my best guess as to what's wrong is simply that the createName() function that the button is calling is also using the OptionMenu's command up due to the line
menu.add_command(label=name, command=lambda name=name: var.set(name))
Is there anyway around this? Is it possible for an OptionMenu to have multiple commands?
You're on the right track... But instead of changing the StringVar you can pass the name into your selection() function like this:
from Tkinter import *
root = Tk()
var = StringVar()
var.set("Choose a name...")
names = []
# Appends names to names list and updates OptionMenu
def createName(n):
names.append(n)
personName.delete(0, "end")
menu = nameMenu['menu']
menu.delete(0, "end")
for name in names:
menu.add_command(label=name, command=lambda name=name: selection(name))
# what to run when a name is selected
def selection(name):
var.set(name)
print "Running" # For testing purposes to see when/if selection runs
print name
# Option Menu for names
nameMenu = OptionMenu(root, var, ())
nameMenu.grid(row=0, column=0, columnspan=2)
nameMenu.config(width=20)
# Entry for user to submit name
Label(root, text="Name").grid(row=1, column=0)
personName = Entry(root, width=17)
personName.grid(row=1, column=1)
# Add person Button
Button(root, text="Add Person", width= 20, command=lambda: createName(personName.get())).grid(row=5, column=0, columnspan=2)
mainloop()

Tkinter. Press Enter in Entry box. Append to Text box. How?

I am making a chat program and decided to use Tkinter for the interface.
What I wanna do is a breeze in C# but Tkinter is new to me.
Basically I have a form with a Entry control and a Text control.
I want to know how to append text from the Entry control to the Text control after the user presses Enter.
Here's my code so far:
from tkinter import *
class Application:
def hello(self):
msg = tkinter.messagebox.askquestion('title','question')
def __init__(self, form):
form.resizable(0,0)
form.minsize(200, 200)
form.title('Top Level')
# Global Padding pady and padx
pad_x = 5
pad_y = 5
# create a toplevel menu
menubar = Menu(form)
#command= parameter missing.
menubar.add_command(label="Menu1")
#command= parameter missing.
menubar.add_command(label="Menu2")
#command= parameter missing.
menubar.add_command(label="Menu3")
# display the menu
form.config(menu=menubar)
# Create controls
label1 = Label(form, text="Label1")
textbox1 = Entry(form)
#command= parameter missing.
button1 = Button(form, text='Button1')
scrollbar1 = Scrollbar(form)
textarea1 = Text(form, width=20, height=10)
textarea1.config(yscrollcommand=scrollbar1.set)
scrollbar1.config(command=textarea1.yview)
textarea1.grid(row=0, column=1, padx=pad_x, pady=pad_y, sticky=W)
scrollbar1.grid(row=0, column=2, padx=pad_x, pady=pad_y, sticky=W)
textbox1.grid(row=1, column=1, padx=pad_x, pady=pad_y, sticky=W)
button1.grid(row=1, column=2, padx=pad_x, pady=pad_y, sticky=W)
form.mainloop()
root = Tk()
Application(root)
So you're using a tkinter.Text box, which supports the .insert method. Let's use it!
def __init__(self,form):
# Lots of your code is duplicated here, so I'm just highlighting the main parts
button1 = Button(form, text='Button1', command = self.addchat)
self.textbox = textbox1 # to make it accessible outside your __init__
self.textarea = textarea1 # see above
form.bind("<Return>", lambda x: self.addchat())
# this is the magic that makes your enter key do something
def addchat(self):
txt = self.textbox.get()
# gets everything in your textbox
self.textarea.insert(END,"\n"+txt)
# tosses txt into textarea on a new line after the end
self.textbox.delete(0,END) # deletes your textbox text

Categories