The program works as intended when I simply use tkinter's widgets. When I use ttk's widgets the program repeats itself twice. I tried almost everything in my knowledge to fix this, I believe that *args have something to do with it. Is there anyway to prevent my function _up_options from running twice?
from tkinter import *
from tkinter import ttk
root = Tk()
first = StringVar(root)
second = StringVar(root)
Ore = {'Options': [''], 'Yes': ['One'], 'No': ['Two']}
entry1 = ttk.OptionMenu(root, first, *Ore.keys())
entry2 = ttk.OptionMenu(root, second, '')
entry1.pack()
entry2.pack()
def _up_options(*args):
print('update_options')
ores = Ore[first.get()]
second.set(ores[0])
menu = entry2['menu']
menu.delete(0, 'end')
for line in ores:
print('for')
menu.add_command(label=line, command=lambda choice=line: second.set(choice))
first.trace('w', _up_options)
root.mainloop()
PS, I used *args in my function to work. If anyone can explain this, I would be very grateful
I think I figured this out. The problem is that the variable actually is set twice by the ttk OptionMenu.
Take a look at this piece of code from the tkinter OptionMenu:
for v in values:
menu.add_command(label=v, command=_setit(variable, v, callback))
This adds a button to the menu for each value, with a _setit command. When the _setit is called it sets the variable and another callback if provided:
def __call__(self, *args):
self.__var.set(self.__value)
if self.__callback:
self.__callback(self.__value, *args)
Now look at this piece of code from the ttk OptionMenu:
for val in values:
menu.add_radiobutton(label=val,
command=tkinter._setit(self._variable, val, self._callback),
variable=self._variable)
Instead of a command this adds a radiobutton to the menu. All radiobuttons are "grouped" by linking them to the same variable. Because the radiobuttons have a variable, when one of them is clicked, the variable is set to the value of the button. Next to this, the same command is added as in the tkinter OptionMenu. As said, this sets the variable and then fires another command of provided. As you can see, now the variable is updated twice, once because it is linked to the radiobutton and once more because it is set in the _setit function. Because you trace the changing of the variable and the variable is set twice, your code also runs twice.
Because the variable is set twice from within the ttk code, I guess there's not much you can do about that. If you don't change the variable from any other part of your code than from the OptionMenu though, you could choose to not trace the variable, but instead add your function as command to the OptionMenu:
entry1 = ttk.OptionMenu(root, first, *Ore.keys(), command=_up_options)
P.S. this was introduced with this commit after this bugreport.
I guess when adding the variable=self._variable the command should have been changed to just command=self._callback.
You can understand the problem in the error message:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\user\AppData\Local\Programs\Python\Python36\lib\tkinter__init__.py", line 1699, in call
return self.func(*args)
TypeError: _up_options() takes 0 positional arguments but 3 were given
Initially, you don't use _up_options When you change the Options you call _up_options to trace the first StringVar and change it to the value of the next object in the dictionary.
Now when you do that you are running on all the objects in the dictionary, therefore, you need the *args so the lambda function will run on all args given!
As for your problem:
When I use ttk's widgets the program repeats itself twice.
EDIT
See #fhdrsdg's answer!
The solution is just to change command=tkinter._setit(self._variable, val, self._callback) to command=self._callback.
Hope you find this helpful!
Instead of tracing the StringVar, add a callback as command argument for OptionMenu constructor.
I created a subclass of ttk.OptionMenu to solve this (as well as to provide slightly simpler usage of the widget and a more useful callback). I think this is a more stable approach than modifying the original class directly or just overriding the original method because it guarantees compatibility with potential changes to the built-in/original widget in future Tkinter versions.
class Dropdown( ttk.OptionMenu ):
def __init__( self, parent, options, default='', variable=None, command=None, **kwargs ):
self.command = command
if not default:
default = options[0]
if not variable:
variable = Tk.StringVar()
if command:
assert callable( command ), 'The given command is not callable! {}'.format( command )
ttk.OptionMenu.__init__( self, parent, variable, default, *options, command=self.callBack, **kwargs )
else:
ttk.OptionMenu.__init__( self, parent, variable, default, *options, **kwargs )
def callBack( self, newValue ):
self.command( self, newValue )
You can then use it like this:
def callback( widget, newValue ):
print 'callback called with', newValue
print 'called by', widget
options = [ 'One', 'Two', 'Three' ]
dropdown = Dropdown( parent, options, command=callback )
dropdown.pack()
Besides avoiding the double-trace issue, other notable differences from the original ttk.OptionMenu includes not needing to supply a Tkinter variable or default value if you don't need them (the default item will be the first item in the options list if not provided), and being able to get the widget that called the callback function when it fires. The latter is very helpful if you have many dropdown widgets sharing the same callback and you need to know which one is being used within the call.
Soon after writing this, I also found another solution using lambda: Passing OptionMenu into a callback (or retrieving a reference to the used widget)
I thought I might still share this Dropdown widget anyway since it can make the higher-level code simpler, and it provides a good base if you have some other custom methods to add in.
Related
I'm trying to write a function to disable a widget in a tkinter program depending on the value of a checkbutton. I want this function to be general: That is, I can pass it the widget and associated check variable and it will disable the widget (if the variable is checked the right way).
Here is an abstracted version of my code
import tkinter
class App:
def __init__(self,root):
widg = tkinter.Scale(root,from_=0,to=100)
checkvar = tkinter.IntVar()
checker = tkinter.Checkbutton(root,variable=checkvar,command=self.check(var,checkvar))
widg.grid()
checker.grid()
widg.configure(state=tkinter.DISABLED)
widg.configure(state=tkinter.NORMAL)
def check(self,widget,var):
if var.get()==1:
widget.configure(state=tkinter.DISABLED)
elif var.get()==0:
widget.configure(state=tkinter.NORMAL)
m = tkinter.Tk()
f=App(m)
It is intended to function such that clicking the checkbutton triggers the callback - check - with the parameters of the widget and the check variable. Then it will evaluate whether the widget should be on or off and change its state accordingly. There are no errors but the state doesn't change. What am I missing here?
Thanks
The command argument simply takes the uncalled function so passing arguments to it takes some workaround.
So it expects self.check rather than self.check() since the widget will call the function later.
I've found that using partial is a workaround for passing the arguments.
import tkinter
from functools import partial
class App:
def __init__(self,root):
widg = tkinter.Scale(root,from_=0,to=100)
checkvar = tkinter.IntVar()
checker = tkinter.Checkbutton(root,variable=checkvar,command=partial(self.check, widg, checkvar))
widg.grid()
checker.grid()
widg.configure(state=tkinter.DISABLED)
widg.configure(state=tkinter.NORMAL)
def check(self,widget,var):
if var.get()==1:
widget.configure(state=tkinter.DISABLED)
elif var.get()==0:
widget.configure(state=tkinter.NORMAL)
m = tkinter.Tk()
f=App(m)
m.mainloop()
Credit to my friend JB for helping me with this.
As user Axe319 suggested, the problem is just that tkinter doesn't expect to pass any parameters. It is possible to work around this by using a lambda function. The line that defines the checker variable, in my original post line 8, can be rewritten as such:
self.checker = tkinter.Checkbutton(root,variable=checkvar,command=lambda:self.check(self.widg,checkvar))
and it will work as intended.
When I call a function from a tkinter widget event I need to add a second parameter in the definition line of the function. Like onChange(self,event) or onChange(self,test). "event" and "test" being the second parameter.
Like:
def onChange(self, test):
print('Content is: ', test.widget.get())
#this also works, but only when having "test" or any other other second parameter name in the def line:
print('Content is: ', self.var.get())
My problem as a beginner in Python/Tkinter is that I do not understand this second parameter and why it is needed. Do anyone have a good explaination of this and/or references to other sources to improve my understanding?
I also would like to understand why this function without the second parameter does not work.
def onChange(self):
print('Content is: ', self.var.get())
A small sample program:
import tkinter as tk
from tkinter import ttk
class GUI:
def __init__(self):
self.window = tk.Tk()
self.create_widgets()
# The following function works with "test" or any other second parameter name in the def line:
def onChange(self, test):
print('Content is: ', test.widget.get())
#this also works, but only when having "test" or any other other second parameter name in the def line:
print('Content is: ', self.var.get())
## The following function does not work:
# def onChange(self):
# print('Content is: ', self.var.get())
def create_widgets(self):
# Makes an Entry widget:
self.string_entry = ttk.Entry(self.window,width=30)
self.string_entry.grid(row=0,column=0)
self.var = tk.StringVar()
self.var.set('Change me and press enter!')
self.string_entry["textvariable"]=self.var
self.string_entry.bind('<Key-Return>', self.onChange)
program = GUI()
program.window.mainloop()
You are allready adding variables that are "extra".
the self variable that you add in the call signature is also a variable that gets filled by the system (python )when calling a class method.
In the same way when an callback is being called by tkinter by the an event, tkinter adds extra variables, in this case the event.
The event variable contains extra information on why the callback is called, this to enable the same callback to be able to handle several different events.
The page http://effbot.org/ is pretty old and is mainly describing Python2 code but it is in most cases update enough to learn about tkinter and how it works.
The Tk framework that calls your function, passes a variable to your callback function (as well as the self variable all methods are called with). When your second method is called it can't accept the second argument so throws a TypeError.
There is more information about the bind methods here.
If you don't care about the argument, you can use the varargs syntax to ignore anything that's passed in.
def onChange(self, *_):
print('Content is: ', self.var.get())
I am writing a program in Python 3.6 using Tkinter where a customer has multiple(11) entry fields. I want these entry fields to only accept integers and also be able to define the maximum amount of characters.
I already have a function that does this. But this function only works for one entry field. I have tried entering variables with calling the function so it changes another entry field for example. I was not able to do this.
This is the function I have that works with 1 entry field.
def limitMuntgeld(self, *args):
value = self.invoerM.get()
if len(value) > 5:
self.invoerM.set(value[:5])
if value.lower() in "abcdefghijklmnopqrstuvwxyz-=[];/":
self.invoerM.set(value[:0])
This is the example entry field code that works with the function
self.invoerMuntgeld = Entry(self, font=('Arial', 14), textvariable=self.invoerM)
This is combined with a trace on the entry field posted below.
self.invoerM = StringVar()
self.invoerM.trace('w', self.limitMuntgeld)
I have also tried it with vcmd and validatecommand. However, no good results.
My endresult would be one function working with all entry fields. If anyone has any suggestions, I am all ears!
The proper way to do entry validation is with the validatecommand option rather than using trace. With the validation feature built into the widget you don't need a reference to the widget itself (though you can use it if you want).
When the validatecommand is run, you can have it pass in what the new value will be if the input is valid. You only need to check this value and then return True or False, without having to know which widget it applies to.
For example:
import tkinter as tk
def validate_input(new_value):
valid = new_value .isdigit() and len(new_value) <= 5
return valid
root = tk.Tk()
validate = root.register(validate_input)
for i in range(10):
entry = tk.Entry(root, validate="key", validatecommand=(validate, "%P"))
entry.pack(side="top", fill="x")
root.mainloop()
For information about what %P represents, and what else can be used as arguments to the command, see this question: Interactively validating Entry widget content in tkinter
I'm learning Python & Tkinter and following a tutorial. I'm making a program to do what I'm used to do in scripting languages such as Powershell, get a user value, and then do something with it, for starter.
I can't find any example code that looks like mine. So I'm asking for your help.
Basically, this program creates a frame with a Quit Button, an Entry widget where the user enters a value, & a OK button. Then, he presses the OK button, and the value should be printed in the python terminal.
I'm getting an error but I don't understand why. I'm following a tutorial so I'm doing it the way they show, by defining a class to create the frame, putting the buttons & text box inside it.
The function to get the user input text from the entry widget is set below, where I would put my other functions, if let's say, I would like to add functions for buttons to do a print. I managed to make the print work if the value to print is set before, but I can't get the value from the Entry Widget.
from tkinter import *
class Window:
def __init__(self, master):
self.master = master
master.title("Get a value and print it")
self.info = Label(master, text="Please write something then click ENTER")
self.info.pack()
self.ebox= Entry()
self.ebox.pack()
self.viewnumber = Button(master, text="OK", activebackground="red", command=get_int)
self.viewnumber.pack()
self.quit = Button(master, text="Quit", activebackground="blue", command=quit)
self.quit.pack()
def get_int():
intnum = ebox.get
print(intnum)
def quit():
root.quit()
root = Tk()
root.geometry("400x400")
my_gui = Window(root)
root.mainloop()
I am getting this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\USERNAME\AppData\Local\Programs\Python\Python35-32\lib\tkint
er\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:\Users\USERNAME\Desktop\pyth222184.py", line 20, in get_int
intnum = ebox.get
NameError: name 'ebox' is not defined
Thank you for your help.
Actually the problem is that ebox can't be accessed from the function. You need to use self.ebox, so that it actually references the instance variable ebox.
To see more about that, visit here.
Also, you are using self.ebox.get instead of self.ebox.get(). self.ebox.get on its own would return the raw function object of self.entry.get. To actually call the function and get the contents, use self.ebox.get().
But in order for that to work, self has to be defined. To do this, add self as the first argument in every method. When you call instance.method() Python will automatically pass instance in as self. Essentially, self lets you access the current instance.
That's right. The get_int in Class is a method, and you can not call that like a function. You'd have to call that like a method something like this
elf.viewnumber = Button(master, text="OK",bg="red",command=lambda:my_gui.get_int() )
self.viewnumber.pack()
This is an example of code that would work.
Hi I've been struggling to get this to work, each time i change something I receive another error. I've been trying to create an entry box with a function and then get the variable from the entry box into a label, created by a button press. When I tried to do this often this error came up.
TypeError: get() missing 1 required positional argument: 'self'
I then put self in in the method brackets.
command = lambda: x.myFunc(self.my_variable.get(self))
Then another error, which I'm not sure how to sort out.
AttributeError: 'My_Class' object has no attribute '_tk'
Here's the full code, I'm new to classes and self, so any corrections are welcome.
from tkinter import *
import time
class My_Class:
def start(self):
self.root=Tk()
self.my_variable=StringVar
self.entry_box=Entry(self.root, textvariable=self.my_variable)
self.entry_box.pack()
self.button=Button(self.root,text="Pass variable now",
command=lambda:x.myFunc(self.my_variable.get(self)))
self.button.pack()
def myFunc(self,my_variable):
self.lab=Label(self.root,text=self.my_variable)
self.lab.pack()
x=My_Class()
x.start()
This is the correct way to create a StringVar object:
text = StringVar() # note additional ()
Can you explain me what x is in the following statement:
lambda: x.myFunc(self.my_variable.get(self))
x is not visible inside the class, because it's declared outside the class.
myFunc is not indented correctly: you should indent it like the __init__ method.
I really recommend you to watch some tutorials on OOP before proceeding. You are basically trying to guess how OOP works.
If you make myFunc A method if the class (which you might be trying to do; it's hard to know because your indentation is wrong), you don't have to pass anything to myFunc. That function has access to everything in the class, so it can get what it needs, when it needs it. That lets you eliminate the use of lambda, which helps reduce complexity.
Also, you normally don't need a StringVar at all, it's just one more thing to keep track of. However, if you really need the label and entry to show exactly the same data, have them share the same textvariable and the text is updated automatically without you having to call a function, or get the value from the widget, or set the value n the label.
Here's an example without using StringVar:
class My_Class:
def start(self):
...
self.entry_box = Entry(self.root)
self.button = Button(..., command = self.myFunc)
...
def myFunc(self):
s = self.entry_box.get()
self.lab = Label(..., text = s)
...