I have a dropdown menu and a button.I am trying to change the text on the button according to the choice in the dropdown menu.I used trace,but gives me this error :
TypeError: change_button_text() takes no arguments (3 given)
This is an example:
from Tkinter import*
import Tkinter as tk
import os
def change_button_text():
buttontext.set(widget1.get())
app=Tk()
app.title("Example")
app.geometry('200x200+200+200')
widget1 = StringVar()
widget1.set('Numbers')
files =["one",'two','three']
widget1DropDown = OptionMenu(app, widget1, *files)
widget1DropDown.config(bg = 'white',foreground='black',font=("Times",16,"italic"))
widget1DropDown["menu"].config(bg = 'white',font=("Times",12,"italic"))
widget1DropDown.pack()
widget1.trace("w", change_button_text)
buttontext=StringVar()
buttontext.set('Zero')
button1=Button(app,textvariable=buttontext,font=("Times", 16),width=15,borderwidth=5)
button1.pack(side=LEFT, padx=5,pady=8)
app.mainloop()
Any ideas?Thanks.
Change your function definition of change_button_text to accept parameters. Callback functions called from the trace function will always receive three arguments: the name of the variable, the index and the mode. None of these are really interesting, but your function needs to match this signature for the callback to work.
To fix it, change your callback function to look like this:
def change_button_text(name, index, mode):
buttontext.set(widget1.get())
If you'd prefer it, you can also put a lambda in the trace call to keep the function definition clean (along the lines of, "why define the variables there if you don't use them"):
widget1.trace("w", lambda n, i, m: change_button_text())
Your callback can remain as is in this case.
Related
Just learning Python and TKInter and have come across this error in my code. Don't know what I'm missing and hoping someone can help. I've included the button code and the function to show you what I have.
def change_font(self):
self.label_name['font'] = "Sawasdee"
self.button1 = Button(self.myframe2, text="Change font")
self.button1.bind("<Button-1>", self.change_font)
When you bind a function to an event, tkinter will call that function with an argument which represents the event which triggered the function to be called. That is why the error says it expected one argument (self) but got two (self, event).
You need to account for that event parameter even if you don't need it. The easiest way is to make it an optional named parameter:
def change_font(self, event=None):
self.label_name["font"] = "Sawasdee"
It's usually incorrect to use bind on a button. The Button widget accepts an attribute named command which can be used to tie the button to a function. In this case, the function will not get the event parameter:
def change_font(self):
self.label_name["font"] = "Sawasdee"
self.button1 = Button(self.myframe2, text="ChangeFont", command=change_font)
The advantage to using command is that it automatically supports not just clicking with the mouse, but also interacting with the button using the keyboard.
Newbie programmer here. I am building a tk based desktop app and ran into an issue:
I have a main window with several stuff in it including two tabs:
global nBook
nBook = ttk.Notebook(self, name="book")
nBook.place(x=300,y=400)
frameOne = ttk.Frame(nBook, width=100, height=100)
frameTwo = ttk.Frame(nBook, width=100, height=100)
nBook.add(frameOne, text='T1')
nBook.add(frameTwo, text='T2')
frameOne.bind("<<NotebookTabChanged>>", self.routine())
frameTwo.bind("<<NotebookTabChanged>>", self.routine())
routine() is a function that SHOULD perform a check every time T2 is selected
def routine(self):
if str(nBook.index(nBook.select())) == "2":
# Do stuff
else:
pass
Problem is that it doesn't do anything when the tab is changed except for calling the routine function as soon as I open the app and never again. I just can't figure out what I'm doing wrong.
Could anyone point out the mistake(s) I'm making?
EDIT: Same issue if I try
nBook.bind("<<NotebookTabChanged>>", self.xbRoutine())
The error comes from the event binding statements: when using self.routine() the callback is called when the bind statement is executed, not when the event is triggered. To get the correct behavior, the second argument of bind should be the name of a function not a call to this function, so simply remove the parentheses.
Another error: when using bind, the callback function is expected to have a first argument (traditionnaly called event) storing the event parameters. So you should define your callback as:
def routine(self, event):
...
I had the same problem. The answer given by #sciroccorics is not complete.
What you bind is not the tab itself, but the notebook.
So, it should be
nBook.bind("<<NotebookTabChanged>>", self.xbRoutine)
Alternatively you could use lambda.
In your case this will look something like this:
frameOne.bind("<<NotebookTabChanged>>", lambda _: self.routine())
Don't forget the _, otherwise you will get a TypeError, since the event is passed as an argument.
lamba is really helpful if your function requires one or more arguments.
I'd like to assign a key combination to do the same as an existing standard binding.
Specifically, for a tkinter.Listbox, make Ctrl-A select all (same as Ctrl-/) and also do the same for Ctrl+<the keys corresponding to those two in my national keyboard layout>.
Since the subroutines doing that exist at Tk level and not on Python level, I cannot just .bind() to them as I would do for a Python function.
If you want to duplicate any existing binding, the first thing you need to do is understand what the original sequence is bound to. In this case, the binding <Control-/> is bound to the binding tag "Listbox" (the name of the internal widget class)
The first step is to get the existing binding by making a raw call to the Tcl interpreter:
func = root.call("bind", "Listbox", "<Control-/>")
The second step is to associate that original function to the new key combination:
root.call("bind", "Listbox", "<Control-A>", func)
Note: the above will associate the binding with all Listbox widgets rather than just a specific listbox.
You could find out from the source code what tcl command is used for the specific binding.
For Listbox's specific case, Control-/ event first activates a virtual event, <<SelectAll>>. Which then calls the Tcl command for listbox'es Tcl procedure tk::ListboxSelectAll.
Let's assign Control-A to mimic Control-/.
Generate <<SelectAll>> event, so that it calls whatever it's supposed to call:
lb.bind('<Control-Key-a>', lambda event: lb.event_generate('<<SelectAll>>'))
Or you could go directly call what <<SelectAll>> eventually calls:
lb.bind('<Control-Key-a>', lambda event: lb.tk.call('tk::ListboxSelectAll', lb))
You may want to bind for all Listbox objects:
lb.bind_class(lb.winfo_class(), '<Control-Key-a>',
lambda event: lb.tk.call('tk::ListboxSelectAll', lb))
A complete example:
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
if __name__ == '__main__':
root = tk.Tk()
lb = tk.Listbox(root)
for i in range(4):
lb.insert('end', i)
lb.bind_class(lb.winfo_class(), '<Control-Key-a>',
lambda event: lb.tk.call('tk::ListboxSelectAll', lb))
# assign anything but "single" or "browse"
# so that the effect is obvious
lb['selectmode'] = 'asd'
lb.pack()
tk.mainloop()
Finally, note that you may want to bind to <Control-Key-A>, too, so the binding works even with Caps Lock on.
This will effectively bind Ctrl-Shift-A, too, which you may or may not want. Conversely, with Caps Lock on, Tk would interpret Ctrl-Shift-A as Ctrl-A.
I've asked a very similar question myself.
You can simply generate the event that you want to mimic.
Generating Control-/ event so that it handles everything from that point onward:
lb.bind('<Control-Key-a>',
lambda event:lb.event_generate('<Control-Key-slash>'))
A complete example:
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
if __name__ == '__main__':
root = tk.Tk()
lb = tk.Listbox(root)
for i in range(4):
lb.insert('end', i)
lb.bind('<Control-Key-a>',
lambda event:lb.event_generate('<Control-Key-slash>'))
# assign anything but "single" or "browse"
# so that the effect is obvious
lb['selectmode'] = 'asd'
lb.pack()
tk.mainloop()
I have a Tkinter-using program which uses a number of StringVars to track data over multiple windows. I had been using
myStringVar.trace('w', lambda *args: update())
to detect when the user changed one of these variables, either by interacting with Entries or by choosing options from an OptionMenu, where 'update' was a function that changed other parts of the GUI as appropriate and 'root' was . However, I found out that update() was called by myStringVar.trace() at a particular line of my code:
name = OptionMenu(self, self.source, self.source.get(), *root.namelist)
where self.source is the StringVariable in question and root.namelist is an ordinary List. I found this out by sandwiching the above line between two trace statments and adding a trace statement to my update function. I also confirmed that replacing the above line with
name = Entry(self, textvariable=self.source)
would not have the same result.
Trying to copy the form of my original code as much as I could, I wrote the following test code:
from tkinter import *
root = Tk()
root.myStringVar = StringVar(root)
root.myStringVar.set('hello')
class myFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.source = root.myStringVar
self.update()
self.pack()
def update(self):
for widget in self.winfo_children(): widget.destroy()
name = OptionMenu(self, self.source, self.source.get(), '1','2','3')
name.pack()
root.myframe = myFrame(root)
root.myStringVar.get()
def speak(root):
print(root.myStringVar.get())
root.myframe.update()
root.myStringVar.trace('w', lambda *args: speak(root))
mainloop()
However, when I ran this code it did not print anything unless I changed the option in the OptionMenu, as you would expect.
The only difference between the two cases I can think of is that in my test code the frame in which the OptionMenu was placed was static while in my actual code it is dynamically generated, but I don't see how this could affect the way in which the creation of an OptionMenu is handled.
To answer your specific question "TKinter- is StringVar.trace called when the StringVar is added to a widget?", the answer is "no". The trace is not called when you add a stringvar to a widget. The trace -- assuming it is a write trace -- is only called when the value of the variable changes.
The problem is likely due to the fact you're creating a method named update. This is a method that already exists on widgets, and is possibly called internally by various tkinter functions. Try renaming your function to something else (eg: update_widget).
maybe i didn't catch you point. It works to me, maybe you forget a print:
[...]
root.myframe = myFrame(root)
#this print myStringVar content even when the value doesn't change (each time you call it):
print("main:", root.myStringVar.get()) #<-- this is the missing print, i guess
def foo(*args): #custom useless function
"""Prints "hello" each time OptionMenu selection change"""
print("foo: hello")
def showargs(*args): #show params, useless too
"""Prints args content each time OptionMenu change"""
print("showargs:", args)
def showcontent(*args): #finally this print myStringVar content
"""Prints myStringVar content each time OptionMenu change"""
print("showcontent: ",root.myStringVar.get())
root.myStringVar.trace('w', foo)
root.myStringVar.trace('w', showargs)
root.myStringVar.trace('w', showcontent)
#output:
#main: hello
#after user select "1" from OptionMenu:
#showcontent: 1
#showargs: ('PY_VAR0', '', 'w')
#foo: hello
I added three debug methods to test myStringVar content:
foo is useless, but is fired each time user change OptionMenu value,
showargs shows the *args param content,
showcontent shows myStringVar content.
While if you're stuck because about at line 19 you'd aspect a result, you forget to print it to console or show somehow to gui(I added a print) and it works.
So you can pass myStringVar and get its contents and/or show any changes to it.
This question already has answers here:
Creating functions (or lambdas) in a loop (or comprehension)
(6 answers)
Closed 6 months ago.
I have made a simple "program launcher" in Python. I have a tab delimited text file, with, at the moment, just:
notepad c:\windows\notepad.exe
write c:\windows\write.exe
The program reads the textfile and creates an array of objects. Each object has a name property (e.g. notepad) and a route property (e.g. C:\windows\notepad.exe). Then, for each object, a button should be made with the correct name on the button, and clicking the button should execute the correct program using the route.
The program very nearly works. Indeed, the array of objects is formed correctly, because the for loop correctly prints out two different program names, and two different routes. The problem is that both buttons, although labeled correctly, launch the write program ! I believe the problem is arising somewhere in the callback, but my Python knowledge just isn't developed enough to solve this! As you can see from my code below, I have tried an "inline" callback, and with a "runprog" function defined. They both give the same outcome.
Your help would be appreciated.
import Tkinter as tk
import subprocess
class MyClass:
def __init__(self, thename,theroute):
self.thename=thename
self.theroute=theroute
myprogs = []
myfile = open('progs.txt', 'r')
for line in myfile:
segmentedLine = line.split("\t")
myprogs.append(MyClass(segmentedLine[0],segmentedLine[1]))
myfile.close()
def runprog(progroute):
print(progroute)
subprocess.call([progroute])
root = tk.Tk()
button_list=[]
for prog in myprogs:
print(prog.thename)
print(prog.theroute)
button_list.append(tk.Button(root, text=prog.thename, bg='red', command=lambda: runprog(prog.theroute)))
# button_list.append(tk.Button(root, text=prog.thename, bg='red', command= lambda: subprocess.call(prog.theroute)))
# show buttons
for button in button_list:
button.pack(side='left', padx=10)
root.mainloop()
Change your command to look like this:
tk.Button(..., command=lambda route=prog.theroute: runprog(route))
Notice how the lambda has a keyword argument where you set the default value to the route you want to associate with this button. By giving the keyword arg a default value, you are "binding" this value to this specific lambda.
Another option is to use functools.partial, which many people find a little less intimidating than lambda. With this, your button would look like this:
import functools
...
tk.Button(..., command=functools.partial(runprog,route)
A third option is to move the "runprog" function to the class instead of in the main part of your program. In that case the problem becomes much simpler because each button is tied specifically to a unique object.
tk.Button(..., command=prog.runprog)
Just change this line:
button_list.append(tk.Button(root, text=prog.thename, bg='red', command=lambda: runprog(prog.theroute)))
to:
button_list.append(tk.Button(root, text=prog.thename, bg='red',
command= (lambda route:(lambda: runprog(route))) (prog.theroute)))
Reasoning: when you create a lambda function (or any other function within a function), it does have access (in Python 2, read-only access) to the variables in the outer function scope. However, it does access the "live" variable in that scope - when the lambda is called, the value retrieved from "prog" will be whatever "prog" means at that time, which in this case will be the last "prog" on your list (since the user will only click a button long after the whole interface is built)
This change introduces an intermediary scope - another function body into which the current "prog" value is passed - and prog.theroute is assigned to the "route" variable in the moment the expression is run. That is done once for each program in the list. The inner lambda which is the actual callback does use the "route" variable in the intermediate scope - which holds a different value for each pass of the loop.