I am currently a novice in python and I'm trying to make a label switch from one image to another by clicking a next button. Here's my code:
from tkinter import *
def next1():
global slide
slide=1
if slide==1:
bglabel.config(image=bg1)
elif slide==2:
bglabel.config(image=bg2)
slide+=1
window.update()
window=Tk()
window.geometry("1500x750+0+0")
bg1=PhotoImage(file="backslide1.png")
bg2=PhotoImage(file="backslide2.png")
nextbutton=PhotoImage(file="next.png")
bglabel=Label(window, image=bg1)
bglabel.place(x=600,y=200)
nextbutton1=Button(window, image=nextbutton, bd=0, command=next1())
window.bind('<Button-1>', next1())
I sat for a good hour or so trying to tamper with the slide variable (trying to declare it before def, removing global, changing value, changing where slide+=1 is, etc) but one of two things always happens; either it's stuck on bg1 with the button clicking but doing nothing, or jumping straight to bg2. I've also tried splitting next1 into two different def's, one for variable tracking, one for switching bglabel, but still the same output. Please help.
(Also, will that window.bind be trouble as I continue to add buttons? If so please let me know how to do it correctly.)
As you mentioned, one 'error' that occurs is that the image immediately jumps to image bg2. This is the line causing that:
nextbutton1=Button(window, image=nextbutton, bd=0, command=next1())
More specifically, where you declare the command associated with the button:
command=next1()
With the enclosed brackets, you're calling the function next1 i.e. as soon as the button is created, run the specified function.
To solve this, just remove the pair of brackets:
nextbutton1=Button(window, image=nextbutton, bd=0, command=next1)
The same goes for your key binding. This way, the button/key now has a reference to the function - it knows what function to run and will run it when the specified action is performed.
More about the key binding...
When you use bind to assign a key to run a function, whatever function that is to be run needs to be made aware as such. Currently, the next function you are trying to bind is given no indication that it can be called using a keyboard button event. To fix that, we set a default parameter in next specifying the event:
def next1(event=None):
#rest of function code here
window.bind('<Button-1>', lambda event: next(event))
Setting a default parameter, event=None, basically means if no value forevent was passed to the function from whatever called it, set it to None by default (in that sense, you can choose to set it to whatever by default). Using lambda for the key bind in this way allows us to pass parameters to functions. We specify what parameter(s) we want to pass to the function and then specify the function, with the parameter(s) enclosed in brackets.
You need to provide the function, not the result of the function. So no parenthesis. Like this:
nextbutton1=Button(window, image=nextbutton, bd=0, command=next1)
Also remove the window.bind line, and your loop logic is broken. "slide" is always 1 since you set that in the function. Are you trying to cycle between the 2 images with every click? If so use itertools.cycle:
from tkinter import *
from itertools import cycle
def next1():
bglabel.config(image=next(bgimages))
window=Tk()
window.geometry("1500x750+0+0")
bg1=PhotoImage(file="backslide1.png")
bg2=PhotoImage(file="backslide2.png")
bgimages = cycle([bg1, bg2])
nextbutton=PhotoImage(file="next.png")
bglabel=Label(window)
bglabel.place(x=600,y=200)
next1() # set the first image
nextbutton1=Button(window, image=nextbutton, bd=0, command=next1)
nextbutton1.pack()
window.mainloop()
(totally untested since i don't have your images).
Related
This question already has an answer here:
Python tkinter listbox bind on <Button-1> only works on second click
(1 answer)
Closed 1 year ago.
I was creating simple listbox containing numbers from 0 to 9. I wanted to print number when it get clicked so i bind list box with Button-1. Im facing problem that is when ever i select any number and try to get its location using list_box.curselection() it does not print any thing(return empty tuple), if i click on on any other number then it print previous selected number. I want to get current selected number.
from tkinter import *
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0,10):
list_box.insert("end",i)
def def_fun(event):
print(list_box.curselection())
list_box.bind("<Button-1>",def_fun)
root.mainloop()
You don't have to bind to <Button-1> or anything, there is a virtual event with Listbox that you can use here:
def def_fun(event):
print(event.widget.curselection()) # The widget that triggers the event is event.widget
list_box.bind("<<ListboxSelect>>",def_fun) # Gets triggered each time something is selected
Just in case you are wondering why the Button-1 did not work, it is because there is a delay, the delay might be due to binding order, you can read more about it here but here is a gist:
In the default case, your binding on <Key> happens before the class binding, and it is the class binding where the text is actually inserted into the widget. That is why your binding always seems to be one character behind.
Change the binding to releasing mouse button, this will also be more user friendly (for example if they accidentally clicked on a selection they didn't want to select, they can move their mouse to a one they want and only releasing will call the function):
from tkinter import Tk, Listbox
def def_fun(event=None):
print(list_box.curselection())
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0, 10):
list_box.insert("end", i)
list_box.bind("<ButtonRelease-1>", def_fun)
root.mainloop()
Another option if you want to call the function on select is either use #CoolCloud answer or you can also set a delay like this (although it will most certainly work in 99.9% of cases, there might be a case where it doesn't):
list_box.bind("<Button-1>", lambda e: root.after(10, def_fun))
The reason is that .curselection() gets the current selection but Button-1 is triggered before anything gets selected so it will print the previous selection because that is what was selected before and where the current selection is now, and then immediately after this, it will move the current selection to the item you clicked.
Important (because it may cause hard-to-debug issues):
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
Also:
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.
So I have this very simple thing I wrote and it's killing me trying to figure out why it won't work. All it does it print a statement when you click.
So for the first example I had a button and assigned the function printName1 directly to it, which worked perfectly fine.
Then the next thing was to bind it using the .bind() function. So in this case we just have a frame that prints out certain things based on which button you press. But unfortunately whenever I use bind, it throws the error show above. References tkinter\__init__.py for the error, so it's not something directly in my code but maybe it needs to be done differently? Thanks guys.
from tkinter import *
root = Tk()
def printName1():
print('Jack')
def printName2():
print('John')
def printName3():
print('Jill')
frame = Frame(root, width=300, height=250)
frame.bind("<Button-1>", printName1)
frame.bind("<Button-2>", printName2)
frame.bind("<Button-3>", printName3)
frame.pack()
root.mainloop()
EDIT: The error is confusing because it made it seem like there was an extra argument when there should be 0. But actually I needed to add an argument to the functions and that was event. so it should be def printName1(event) and so on. Just figured I would let you guys know what worked for me in case anyone stumbles upon this.
If you refer to the documentation regarding tkinter events and bindings, you will see that when an event is triggered, the associated event object will be passed as the first (and only) argument to the bounded function (being printName1 and friends in your case).
So what you need to do is to modify those printName* functions to accept the event argument.
def printName1(event):
print('Jack')
Then what you desired to achieve should work.
Naturally, you could make the event argument optional as #TigerhawkT3 suggested.
Events, such as from the keyboard/mouse, are all sent to the application with information about the event: which key was it, where was the mouse when you clicked, that sort of thing. This means that any callback bound to such an event needs to take an argument. If you want to also bind it to a Tkinter Button, which doesn't take an event, you can handle that as well. Just define your functions with a default argument:
def printName1(event=None):
...
I have an Entry widget on a simple calculator. The user can choose to enter an equation via the keypad. I was wondering if there was a way to detect a character(from the keypad in my case) being typed into the Entry widget. So, focus is on the widget, user presses '4', it comes up on the widget... can I detect this act, for basic purposes of logging the input?
Every time you press a key inside a Tkinter window, a Tkinter.Event instance is created. All you need to do is access that instance. Here is a simple script that demonstrates just how:
from Tkinter import Tk, Entry
root = Tk()
def click(key):
# print the key that was pressed
print key.char
entry = Entry()
entry.grid()
# Bind entry to any keypress
entry.bind("<Key>", click)
root.mainloop()
key (being a Tkinter.Event instance) contains many different attributes that can be used to get almost any type of data you want on the key that was pressed. I chose to use the .char attribute here, which will have the script print what each keypress is.
Yes. There are a few different ways to do this, in fact.
You can create a StringVar, attach it to the Entry, and trace it for changes; you can bind all of the relevant events; or you can add a validation command that fires at any of several different points in the sequence. They all do slightly different things.
When a user types 4, there's a key event with just the 4 in it (which doesn't let you distinguish whether the user was adding 4 to the end, or in the middle, or replacing a whole selected word, or…), and then a modification event is fired with the old text,* and then the "key" or "all" validation function is called with the (proposed) new text, and the variable is updated with the (accepted) new text (unless the validation function returned false, in which case the invalidcommand is called instead).
I don't know which one of those you want, so let's show all of them, and you can play around with them and pick the one you want.
import Tkinter as tk
root = tk.Tk()
def validate(newtext):
print('validate: {}'.format(newtext))
return True
vcmd = root.register(validate)
def key(event):
print('key: {}'.format(event.char))
def var(*args):
print('var: {} (args {})'.format(svar.get(), args))
svar = tk.StringVar()
svar.trace('w', var)
entry = tk.Entry(root,
textvariable=svar,
validate="key", validatecommand=(vcmd, '%P'))
entry.bind('<Key>', key)
entry.pack()
root.mainloop()
The syntax for variable trace callbacks is a bit complicated, and not that well documented in Tkinter; if you want to know what the first two arguments mean, you need to read the Tcl/Tk docs, and understand how Tkinter maps your particular StringVar to the Tcl name 'PY_VAR0'… Really, it's a lot easier to just build a separate function for each variable and mode you want to trace, and ignore the args.
The syntax for validation functions is even more complicated, and a lot more flexible than I've shown. For example, you can get the inserted text (which can be more than one character, in case of a paste operation), its position, and all kinds of other things… but none of this is described anywhere in the Tkinter docs, so you will need to go the Tcl/Tk docs. The most common thing you want is the proposed new text as the argument, and for that, use (vcmd, '%P').
Anyway, you should definitely play with doing a variety of different things and see what each mechanism gives you. Move the cursor around or select part of the string before typing, paste with the keyboard and with the mouse, drag and drop the selection, hit a variety of special keys, etc.
* I'm going to ignore this step, because it's different in different versions of Tk, and not very useful anyway. In cases where you really need a modified event, it's probably better to use a Text widget and bind <<Modified>>.
If you just need to do simple things without using trace module you can try
def objchangetext(self, textwidget):
print(textwidget.get()) #print text out to terminal
text1 = tk.Entry(tk.Tk())
text1.bind("<KeyRelease>", lambda event, arg=(0): objchangetext(text1))
I am using tkinter for a GUI. I bound an event to an entry like so:
EntryFilePath.bind("<Key>", updateAmountOfPeople)
It works, but the problem is that it only updates when a key other than typing input is being pressed. Backspace triggers it, arrows trigger it, just not letters or numbers. I am looking for this functionality.
Other info that might be important:
PathFileName = StringVar()
EntryFilePath = Entry(topLeftMidFrame, textvariable = PathFileName, width=45)
EntryFilePath.pack(side=TOP, pady=32, padx=10)
How to make it trigger on any key?
EDIT: I found out that this only happens when it just got selected. It needs one of the "other" non [a-Z0-9] keys once, after that it is good to go. This is problematic though, in case people start immediately writing.
EDIT2: It might have to do with it having update delay.
The binding should work for every keypress -- if it's not, you're doing something somewhere else in your code to prevent it from working, or your testing is flawed.
If you want a function to be called whenever the value changes, you might want to consider setting a trace on the variable associated with the entry widget. The trace will fire whenever the value changes, no matter whether it's through keyboard input, pasting with the mouse, etc. It will not call your callback when the user uses the arrow keys or the return key, or any other key that doesn't affect the value.
For example:
def var_callback(*args):
print "the variable has changed:", PathFileName.get()
PathFileName.trace("w", var_callback)
It can be solved by changing
EntryFilePath.bind("<Key>", updateAmountOfPeople)
to
EntryFilePath.bind("<KeyRelease>", updateAmountOfPeople)
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.