Python/Tkinter OptionMenu Update creates a "Shadow" of the Menu - python

So I'm still quite new to this (my code should make this obvious) and I am working on a GUI with tkinter.
I am trying to have an OptionMenu which shows Keys from a dict and and upon clicking on a key I would like to see the value.
I want to modify that dict and would like to be able to update said OptionMenu.
So far, so good. Now I've been able to get this to "work" - but when I update the menu (after changing it or not) I get a shadow of the menu itself.
I've conconcted a small test program:
import tkinter as tk
from tkinter import ttk
class MyApp():
def __init__(self,master):
self.master = master
self.myDict = {'Key1':1, 'Key2': 2, 'Key3':3}
self.valueVar = tk.StringVar()
self.valueVar.set("0.00")
self.RCS = tk.Label(master, textvariable=self.valueVar).grid(row=5, column=3)
updateButton = tk.Button(text= "Update List", command = self.update)
updateButton.grid(row=4,column=4)
changeButton = tk.Button(text= "Change list", command = self.changeDict)
changeButton.grid(row=5,column=4)
self.keyVar = tk.StringVar(master)
self.om = ttk.OptionMenu(self.master, self.keyVar, "Select Key ", *self.myDict, command = self.setKey )
self.om.configure(width=20)
self.om.grid(row=4, column=3)
def setKey(self,Surface):
self.valueVar.set(self.myDict[Surface])
def update(self):
menu = self.om["menu"]
menu.delete(0,"end")
menu.destroy
menu = ttk.OptionMenu(self.master, self.keyVar, "Select Key", *self.myDict, command = self.setKey )
menu.grid(row=4, column=3)
def changeDict(self):
self.myDict = {'Key4':4, 'Key5': 5, 'Key6':6}
root = tk.Tk()
app = MyApp(root)
root.mainloop()
What do I have to change? Why?
Usually I work with Matlab. I guess it shows.
Much appreciated!

As far as I understand this mini-program you which for it to display an optionmenu with the keys of a dict and then when you press "change dict" and then update it should switch the optionmenu to the other set of items? In this case, you were calling the destroy on the wrong widget. The only issue was with the update function which should be changed to:
def update(self):
#menu = self.om["menu"]
#menu.delete(0,"end")
#The above two menu items are not neede dif you are just going to destroy the widget
self.om.destroy()
self.om = ttk.OptionMenu(self.master, self.keyVar, "Select Key", *self.myDict, command = self.setKey )
self.om.configure(width=20)
self.om.grid(row=4, column=3)
This will do what I think you want it to do. Just so you know, the optionmenu widget is actually a combination of a button and a menu widget. So when you do menu = self.om["menu"], you are actually getting the menu object of the optionmenu widget and then destroying that. Then you are replacing that variable with an optionmenu and losing the original menu whilst not destroying the original optionmenu (self.om). This is why you got the shadow. Some other notes though:
There is no need for the user to have to press 2 buttons when switching lists - call the update function from the changedict function so that it automatically populates.
You can iterate over the menu items as you began to do but you need to decide on one or the other. I can't comment on the efficiency of completely destroying and enabling the widget or changing the menu items of the current widget as I am still rather experienced.
Hopefully that helps!

Related

How do I get an attribute from a Combobox and put it into a variable?

I'm making a project in Tkinter Python and I want users to select an attribute from a Combobox widget and press a button and that attribute will be stored in a variable. I've searched all over the web, but I can't make heads or tails of the code and have no idea how to store this attribute. Can someone tell me how to do this
I've tried the .get thing... (module? widget?) but that is not working and as I said, the internet ain't no help.
This is my basic code with the window and the Combobox:
from tkinter import *
from tkinter import ttk
master = Tk()
ver = ttk.Combobox(master, state="readonly", values=["test1", "test2"]).pack()
Button(master, text="Run").pack()
master.mainloop()
I want to be able to store the selected item in the Combobox and put it in a variable.
pack returns None if you want to assign to a variable, you must do it on a separate line.
If you want action, Button requires a command key word arg to which you assign a callback.
After you have fixed the mistakes, you can use the get method on the Combobox:
import tkinter as tk
from tkinter import ttk
def print_selected():
print(combo.get())
master = tk.Tk()
combo = ttk.Combobox(master, state="readonly", values=["test1", "test2"])
combo.pack()
tk.Button(master, text="Run", command=print_selected).pack()
master.mainloop()

How to create entry inputs in a toplevel window

i have a problem and can't get my head around it. How to create a child window on pressing a button using tkinter in python were I can entry values like for example:
import tkinter
root = Tk()
Button(root, text='Bring up Message', command=Window).pack()
root.mainloop()
def messageWindow():
win = Toplevel()
-------->calculate------
Label(win, text=message).pack()
Button(win, text='OK', command=win.destroy).pack()
and on the message window i would like to have two entry fields were I can enter a and b and afterwards it should calc a+b and give me the result.
Thank you.
First, you should use from tkinter import * since there isn't a tkinter. preceding the module's classes used in your script.
Also, is your "Bring up Message" button supposed to call the messageWindow() function? Right now it's calling an undefined function Window. If so, you should change the Button's command and move your messageWindow() function above the line where you created the button or else it will call the function before it is defined and generate an error.
The syntax of an Entry widget in Tkinter goes as follows:
entry = Entry(root, *options)
entry.pack()
You need to pack() the entry widget after you define it. You won't be able to retrieve the input inside it if you pack() it on the same line as you define it as it will become a NoneType object.
You will need at least two Entry widgets, one to enter input a and one to enter input b.
You can also add a third Entry to print the result of the sum of a and b to, though you can use a label or just print it to the console.
entry_a = Entry(win)
entry_a.pack()
entry_b = Entry(win)
entry_b.pack()
# Optional answer entry
entry_ans = Entry(win)
entry_ans.pack()
You should then create a function (still within the messageWindow() function) that will retrieve the input from the two entries and add them, as well as another Button to call that function. I implemented some additional error-checking in the form of a try-except for when the entries are blank or contain something other than integers:
def add():
try:
a = int(entry_a.get())
b = int(entry_b.get())
ab_sum = a + b
# Optional printing to answer entry
entry_ans.delete(0, 'end')
entry_ans.insert(0, ab_sum)
except:
pass
Button(win, text="Add", command=add).pack()
"How to create entry inputs in a toplevel window"
import tkinter as tk
...
toplevel = tk.Toplevel(...)
tk.Entry(toplevel)
"How to create a child window on pressing a button..."
import tkinter as tk
...
def create_child_window(widget):
tk.Toplevel(widget)
...
root = tk.Tk()
tk.Button(root, command=lambda w = root: create_child_window(w))

Create cascading Listbox in Tkinter

The question is simple. I would like to create a drop menu of list of elements calling back the item selected using Tkinter. Is that possible?
#It is possible! It's actually called an OptionMenu in Tkinter.
window = Tk()
clicked = StringVar()
clicked.set("Choose Option")
drop_menu = OptionMenu(window, clicked, "option1", "option2", "option3", "option4")
drop_menu.pack()
window.mainloop()

How to update the command of an OptionMenu

I am trying to set or update the command of an OptionMenu after its instantiation.
The widget.configure(command=foo) statement works for Button and CheckButton, but not for OptionMenu.
The following code raises this error: _tkinter.TclError: unknown option "-command"
from Tkinter import Tk, OptionMenu, StringVar
root = Tk()
var = StringVar()
def foo(val):
print val, var.get()
widget = OptionMenu(root, var, "one", 'two')
widget.configure(command=foo)
widget.pack()
root.mainloop()
Good question! Its a good thing I never had to do this in any one of my projects before because (unless someone proves me wrong here) you can't set/update the command of a OptionMenu widget once its already defined.
If Tkinter wanted you to be able to do that, it definitely would've included it to be edited by .configure()
There is a handy function called .keys() which you can call with a widget object to see all available traits that can be used with .configure().
Button example:
from tkinter import *
master = Tk()
def callback():
print ("click!")
b = Button(master, text="OK", command=callback)
print (b.keys()) #Printing .keys()
b.pack()
mainloop()
Which results in :
Notice how in this huge list of keys, 'command' is on the second line? That is because a button's command CAN be used in .configure()
OptionMenu example:
from tkinter import *
root = Tk()
var = StringVar()
def foo(val):
print ("HI")
widget = OptionMenu(root, var, "one", 'two')
print(widget.keys())
widget.pack()
root.mainloop()
Which results in:
Notice how there is no 'command' on line 2 this time. This is because you cant configure command with an OptionMenu widget.
Hopefully this problem doesn't hinder your program too much and I hope my answer helped you understand better!
I think what you're really asking is how to associate a command to an Optionmenu, rather than update a command (there is no command, so there's nothing to update).
If you want a function to be called every time a value is selected from an Optionmenu, you can add a trace on the related variable. The trace will call a function whenever that variable changes, whether through the Optionmenu or any other means.
For example:
...
var = tk.StringVar()
def foo(*args):
print "the value changed...", var.get()
var.trace("w", foo)
...
When the function is called it will pass three arguments, which you can safely ignore in this case.
For more information on variable traces see http://effbot.org/tkinterbook/variable.htm
You might also want to consider switching to the ttk combobox. It supports binding to <<ComboboxSelected>>, which is every so slightly less clunky than doing a variable trace.
It is possible to change the commands associated with OptionMenu widets if you're careful (as #Bryan Oakley commented). Below is an example of doing it.
The tricky part is you have to reconfigure all the menu items, not just one of them. This requires some extra bookkeeping (and some processing overhead, but it's unnoticeable).
Initially the menu has three items, each with a different function to be called when selected, one of which changes the menu. If the latter is selected the menu is changed to only have two menu items both of which call the same function.
from tkinter import *
root = Tk()
var = StringVar()
var.set('Select')
def foo(value):
var.set(value)
print("foo1" + value)
def foo2(value):
var.set(value)
print("foo2 " + value)
def foo3(value):
var.set(value)
print("foo3 " + value)
def change_menu(value):
var.set('Select')
print('changing optionmenu commands')
populate_menu(optionmenu, one=foo3, two=foo3)
def populate_menu(optionmenu, **cmds):
menu = optionmenu['menu']
menu.delete(0, "end")
for name, func in cmds.items():
menu.add_command(label=name, command=
lambda name=name, func=func: func(name))
optionmenu = OptionMenu(root, var, ()) # no choices supplied here
optionmenu.pack()
Label(root, textvariable=var).pack()
populate_menu(optionmenu, one=foo, two=foo2, change=change_menu)
root.mainloop()

Keep a menu open in Tkinter

I want to keep a menu cascade open, after a command button within the cascade is clicked. So it basically only closes when the user clicks anywhere else (like it would normally too). Can't seem to find a proper option or a method to open said menu in the callback. The invoke() function only works on buttons wihtin the cascade right? How would you go about that?
Yes, I know this was asked a long time ago, but I was curious if there was any way to accomplish this with tkinter, so I fiddled about for a while and figured out how to do it. I was unable to come up with a way to properly place the persistent menu where it was when it originally opened, but I have managed to make it persist in any location you request (I use upper-left corner of root window). And yes, I know this isn't a nice proper class based implementation, but I was just going for as simple a test as I could write without obscuring it with too many extraneous details.
try:
from tkinter import *
from tkinter.ttk import *
except:
from Tkinter import *
from ttk import *
root = Tk()
var = StringVar()
def menu_click(menu, item):
global root
var.set(item)
menu.post(root.winfo_rootx(), root.winfo_rooty())
root.option_add('*tearOff', False) # remove tearoff from all menus
Label(root, textvariable=var).pack() # just to give menu clicks some feedback
root.geometry('400x300')
menubar = Menu(root)
root['menu'] = menubar
menu_test = Menu(menubar)
menubar.add_cascade(menu=menu_test, label='Test')
menu_test.add_command(label='One', command=lambda: menu_click(menu_test, 'One'))
menu_test.add_command(label='Two', command=lambda: menu_click(menu_test, 'Two'))
menu_test.add_command(label='Three', command=lambda: menu_click(menu_test, 'Three'))
menu_cas = Menu(menu_test)
menu_test.add_cascade(menu=menu_cas, label='Four')
menu_cas.add_command(label='One', command=lambda: menu_click(menu_cas, 'Fourty One'))
menu_cas.add_command(label='Two', command=lambda: menu_click(menu_cas, 'Fourty Two'))
menu_cas.add_command(label='Three', command=lambda: menu_click(menu_cas, 'Fourty Three'))
root.mainloop()

Categories