from tkinter import *
main = Tk()
var = StringVar()
options = OptionMenu(main, var, 'option1')
options.grid()
options['menu'].add_command(label='option2')
main.mainloop()
When you select 'option1' in the menu it shows that string on the widget. If you select 'option2' it does not show that string on the widget. If you set a command for option2 it will run. Why is 'option2' not showing in the widget when it is selected?
python 3.5
UPDATE:
By adding strings through a for loop I run into a problem where the command for each string uses the same variable.
from tkinter import *
def _print(var, string):
print(string)
var.set(string)
lst = ['hello', 'bob', 'testing']
main = Tk()
var = StringVar()
options = OptionMenu(main, var, 'option1')
options.grid()
for string in lst:
options['menu'].add_command(label=string, command=lambda: _print(var, string))
main.mainloop()
I tried using lambda event, i=string: _print(var, i)) in the loop but lamdba doesnt like it. Says its missing 'event'. I've put event in the _print function and the call to _print but I get the same error that lamdba is missing event. Any idea how I can pass the right variable for each command in the loop?
It works when you declared 'option2' in the Optionmenu() widget. More examples can be read from here and here.
from tkinter import *
main = Tk()
var = StringVar()
options = OptionMenu(main, var, 'option1', 'option2')
options.grid()
#options['menu'].add_command(label='option2')
main.mainloop()
I found that the menu option is used for creating items in a Menu() widget but not for a Optionmenu() widget. This is stated here. You can read more about how to declare a .Menu() widget here. All the best to your tkinter developments.
Edit1:
The reason why it was failing was because you did not declare the command option associated with the .add_command method. Having done this alone, you will notice that the Optionmenu still does not get updated with the new label of the added menu item. To resolve this issue, you have to use the .set() method of the Control Variable StringVar to do the updating. See revised script below.
from tkinter import *
def _print1(value):
# By default, callback to OptionMenu has a positional argument for the
# menu item's label.
print(value)
print(var.get())
def _print2():
var.set('option2')
print(var.get())
main = Tk()
var = StringVar()
options = OptionMenu(main, var, 'option1',command=_print1)
options.grid()
options['menu'].add_command(label='option2', command=_print2)
# To add more clickable menu items, the 'add_command' method requires you to
# to declare it's options 'label' and 'command'.
main.mainloop()
Edit2:
Remark, I have added the command aspect to options in Edit1. I
did not address earlier but thought it needed to be shown for
completeness.
Responding to your question in your UPDATE, to do what you want, I
rewrote the script as a class object. Also I used the internal class
_setit found in tkinter, which the OptionMenu widget had used to
do configure the callback used by command. This approach overcame the issue you encountered.
Revised Code:
from tkinter import *
class App(Tk):
def __init__(self, parent=None):
Tk.__init__(self, parent)
self.parent=parent
self.createOM()
self.grid()
def createOM(self):
# Create OptionMenu
omlist=['option1']
self.var = StringVar()
self.options = OptionMenu(self, self.var, *omlist,
command=self._print1)
self.options.grid()
values = ['hello', 'bob', 'testing']
for v in values:
self.options['menu'].add_command(
label=v, command=_setit(self.var, v, self._print2))
# To add more clickable menu items, the 'add_command' method requires you to
# to declare it's options 'label' and 'command'.
def _print1(self, value, *args):
#callback for OptionMenu has these arguments because inherently it
#uses the _setit class to configure the callback with these arguments.
print()
print(value)
#self.var.set('option10') #Uncomment to change OptionMenu display
print(self.var.get())
def _print2(self, value, *args):
print()
print(value)
#self.var.set('option20') #Uncomment to change OptionMenu display
print(self.var.get())
#The following class is extracted from tkinter.
class _setit:
"""Internal class. It wraps the command in the widget OptionMenu."""
def __init__(self, var, value, callback=None):
self.__value = value
self.__var = var
self.__callback = callback
def __call__(self, *args):
self.__var.set(self.__value)
if self.__callback:
self.__callback(self.__value, *args)
if __name__ == "__main__":
app = App()
app.mainloop()
It doesn't select anything because it's not supposed to. You're basically hacking OptionMenu's visual part. The items you pass as values are first added to the menu and then are given the command option to modify the selection. When you call options['menu'].add_command(label='option2') you're basically only modifying the visual part of the widget, 'option2' is not added to values, and thus command option isn't set. See for yourself in the source code here.
Related
I'm using Tkinter for the GUI of a little tool I wrote with Python. Basically I just want a callback-method to be executed as soon as the contents of an entry widget have changed. This can be done with Tkinter's own variable classes (StringVar, BooleanVar, etc. - see documentation for details: http://effbot.org/tkinterbook/variable.htm).
So I couldn't get the mechanism to work and I found a snippet online, where it works perfectly fine. Now I'm trying to figure out why my version does not work.
As you can see in the two code examples the only difference is, that I'm using the event-listening functionality inside a class, whereas the snippet I found online only demonstrates it in a straight top-to-bottom manner.
Here's what I've already tried:
I instantiated the Tk instance directly in the constructor of my GUI class - same behaviour.
I inherited directly from the Tk class (instead of Frame) - same behaviour.
I placed the callback outside of the class - same behaviour.
The only idea I have is that the problem might be scope related, which I tried to verify.
Working code snippet:
from tkinter import *
import tkinter as tk
def text_changed(*args):
print("Text changed.")
top = tk.Tk()
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", text_changed)
entry_widget = tk.Entry(top, textvariable = string_listener)
entry_widget.pack()
top.mainloop()
Not working code snippet
from tkinter import *
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
Like in the working example, my code ought to print Text changed., everytime a character is either deleted from or appended to the string in the extry-widget.
The problem is that string_listener is a local variable, and python is destroying the variable when __init__ finishes running. This doesn't happen in your original code since the variable is created in the global scope.
A simple solution is to save a reference as an attribute of the class:
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
self.string_listener = tk.StringVar()
self.string_listener.set("Init Text")
self.string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=self.string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
note: I also changed StringVar to tk.StringVar so that I could remove the redundant wildcard import of tkinter.
How do I copy Entry widget text and paste into another Entry widget in the same window.
i.e. Let's imagine you are completing a joint credit application but you and the co-applicant have the same mailing address. Instead of re-typing the same address over again, there should be a checkbutton on the application that when it's checked it will auto-populate the co-applicant's mailing address with the main applicant's address. How do I get this done in tkinter? (I'm a tkinter and python rookie)
thanks in advance DP
There can probably a couple more ways of achieving the behavior you want. I think using the textvariable option and Variable Classes fits well here. Together, they let a widget's text to be the same as another at all times. With the Checkbutton the user decides whether or not to do that:
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
def on_selection(copy_widget, paste_widget, condition_widget):
if condition_widget.var.get():
paste_widget['textvariable'] = copy_widget['textvariable']
else:
paste_widget['textvariable'] = ''
def create_entry_widgets(master):
entries = list()
for i in range(2):
entries.append(tk.Entry(master))
entries[-1].pack()
entries[0].var = tk.StringVar()
entries[0]['textvariable'] = entries[0].var
return entries
def create_checkbutton(master, entries):
checkbutton = tk.Checkbutton(master, text="Copy?")
checkbutton.var = tk.BooleanVar(value=False)
checkbutton.config(variable=checkbutton.var, onvalue=True, offvalue=False)
checkbutton['command'] = lambda cw=entries[0], pw=entries[1], \
cdw=checkbutton: on_selection(cw, pw, cdw)
checkbutton.pack()
return checkbutton
def main():
root = tk.Tk()
entries = create_entry_widgets(root)
checkbutton = create_checkbutton(root, entries)
tk.mainloop()
if __name__ == '__main__':
main()
This is code I found when searching to understand and learn about Tkinter, but it gives an error on check box toggle.
from Tkinter import *
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Windows")
Label(text="Contact List").grid(row=0,column=0,columnspan=2)
Text(width=30,height=15).grid(row=1,rowspan=9, column=0,columnspan=2,padx=20)
Button(text="Display Contact").grid(row=10, column=0,columnspan=2,pady=10)
Label(text="Last Name:").grid(row=11, column=0,pady=10)
Entry().grid(row=11,column=1)
Button(text="Search").grid(row=12,column=0,columnspan=2)
Label(text="New Contact").grid(row=0,column=2,columnspan=2)
Label(text="First Name:").grid(row=1,column=2,sticky=E)
Entry().grid(row=1,column=3)
Label(text="Last Name:").grid(row=2,column=2,sticky=E)
Entry().grid(row=2,column=3)
Label(text="Phone #:").grid(row=3,column=2,sticky=E)
Entry().grid(row=3,column=3)
friend_check = IntVar()
Checkbutton(variable=friend_check, command = self.friend_box, onvalue=1, offvalue=0, text = "Friend").grid(row=4,column=3,sticky=W)
#Label(text="Friend").grid(row=4,column=3,padx=20,sticky=W)
Label(text="Email:").grid(row=5,column=2,sticky=E)
Entry().grid(row=5,column=3)
Label(text="Birthday:").grid(row=6,column=2,sticky=E)
Entry().grid(row=6,column=3)
Button(text="Add Contact").grid(row=7,column=3,sticky=E)
def friend_box(self):
if self.friend_check.get() == 1:
print '1'
else:
print '0'
def main():
root = Tk()
root.geometry("600x450+900+300")
root.resizable(0,0)
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
This is the error mentioned above:
AttributeError: Example instance has no attribute 'friend_check'
Exception in Tkinter callback
How can I avoid this error?
You're taking too many shortcuts in your code. Let's look at an example:
Label(text="Contact List").grid(row=0,column=0,columnspan=2)
This creates a Label, but doesn't save a reference to it. It will display in the GUI, but if you ever want to refer back to it, you'll be unable to. This is important when you have something like an Entry widget, to which you're pretty much guaranteed to want to use again (for the get()).
Another issue is that you have the geometry management chained to the widget creation. If you did save a reference to this, it would simply point to None, which is the value returned by geometry management methods.
To fix this, unchain the statements and save a reference:
self.cl_label = Label(text="Contact List")
self.cl_label.grid(row=0,column=0,columnspan=2)
Do this for each widget you create.
For friend_check, you need to make it an instance variable instead of a local variable, as local variables are not usable outside their scope and get discarded when the containing function ends. Do this by prepending self. to the reference name.
self.friend_check = IntVar()
I'm using Python's TkInter module for a GUI. Below is a simple checkbox code.
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar, command=getCheckVal)
btnC.grid()
windowTime.mainloop()
This code works fine. Each time I tick the checkbox, I get 1, else 0.
However, when I run the same code in a function that is called from another TkInter command (when a button is pressed), it stops working. I always get 0 as the value.
class GUIMainClass:
def __init__(self):
'''Create the main window'''
self.window = Tk.Tk()
def askUser(self):
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar,
command=getCheckVal)
btnC.grid()
windowTime.mainloop()
def cmdWindow(self):
frameShow=Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj=GUIMainClass()
GUIObj.cmdWindow()
This is very unusual. What could be going wrong?
EDIT: I've used 2 mainloops because I want a separate window (windowTime) to open up when I click "Show Plots" button. This new window should have the checkbox in it.
Your windowTime, cbVar, etc. variables are defined in the function's local scope. When askUser() completes execution, those values are thrown away. Prepend self. to them to save them as instance variables.
There should only be one mainloop() in your program, to run the main Tkinter root object. Try putting it as the very last line in the program. I recommend doing some reading on Effbot for how to set up a Tkinter application.
I'm not sure what all you're trying to do, but one problem is that the TK.IntVar called cbVar that you create in your askUser() method will be deleted when the function returns, so you need to attach it to something that will still exist after that happens. While you could make it a global variable, a better choice would be to make it an attribute of something more persistent and has a longer "lifespan".
Another likely issue is that generally there should only be one call to mainloop() in a single Tkinter application. It appears what you want to do is display what is commonly known as a Dialog Window, which Tkinter also supports. There's some standard ones built-in, plus some more generic classes to simplify creating custom ones. Here's some documentation I found which describes them in some detail. You may also find it helpful to look at their source code.
In Python 2 it's in the /Lib/lib-tk/tkSimpleDialog.py file and
in Python 3 the code's in a file named /Lib/tkinter/simpledialog.py.
Below is code that takes the latter approach and derives a custom dialog class named GUIButtonDialog from the generic one included the Tkinter library which is simply named Dialog.
try:
import Tkinter as Tk # Python 2
from tkSimpleDialog import Dialog
except ModuleNotFoundError:
import tkinter as Tk # Python 3
from tkinter.simpledialog import Dialog
class GUIButtonDialog(Dialog):
"""Custom one Button dialog box."""
def __init__(self, btnText, parent=None, title=None):
self.btnText = btnText
Dialog.__init__(self, parent, title)
def getCheckVal(self):
print(self.cbVar.get())
def body(self, master):
"""Create dialog body."""
self.cbVar = Tk.IntVar()
self.btnC = Tk.Checkbutton(master, text=self.btnText, variable=self.cbVar,
command=self.getCheckVal)
self.btnC.grid()
return self.btnC # Return the widget to get inital focus.
def buttonbox(self):
# Overridden to suppress default "OK" and "Cancel" buttons.
pass
class GUIMainClass:
def __init__(self):
"""Create the main window."""
self.window = Tk.Tk()
def askUser(self):
"""Display custom dialog window (until user closes it)."""
GUIButtonDialog("Save", parent=self.window)
def cmdWindow(self):
frameShow = Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj = GUIMainClass()
GUIObj.cmdWindow()
In Tkinter for constructing the menubar with the <menu_item>.add_command() we need a string for the accelerator argument which will create the hotkey binding for a command.
I created a method, which is checking if the user's platform is Mac or other, and if it is, then returns the Command key string combined with the other keys.
But it doesn't work -> the menu is building, if I click on the menu-item it is working, but not working with the hot-keys. ALthough I can see the ⌘ + N in the menu..
My first thought is, that the self.hot_key() method is not called while passed as an argument..
import sys
import Tkinter
class app(object):
def __init__(self):
self.gui = Tkinter.Tk()
self.gui.minsize(width=640, height=320)
menu = Tkinter.Menu(self.gui)
filemenu = Tkinter.Menu(menu, tearoff=0)
filemenu.add_command(
label = 'New',
command = self.New,
accelerator = self.hot_key('n')
)
menu.add_cascade(
label = 'File',
menu = filemenu
)
self.gui.config(menu=menu)
self.text = Tkinter.Text(self.gui)
self.text.pack(expand=Tkinter.YES, fill=Tkinter.BOTH)
def hot_key(self, *keys):
super_key = 'Command' if sys.platform == 'darwin' else 'Control'
return '{super}+{keys}'.format(super=super_key, keys='+'.join(keys))
def New(self):
print "I'm working!"
App = app()
App.gui.mainloop()
According to this page,
The "accelerator" option is used to indicate the menu accelerator that
should be associated with this menu. This does not actually create the
accelerator, but only displays what it is next to the menu item. You
still need to create a binding for the accelerator yourself.
So your accelerator keyword argument is working as designed -- the Command-N symbol appears in your menu.
As mgilson suggests in a comment, you can use bind_all to get the keyboard combination to actually do something.
self.gui.bind_all("<Command-n>", lambda event: self.New())