Python 3.3 tkinter's Entry widget update on key - python

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)

Related

How to change label (image form) with next button python

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).

Tkinter, Entry widget, is detecting input text possible?

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))

Tkinter: a simple way to avoid race conditions between an event and value update?

The issue
My issue is pretty simple, but I couldn't figure out how to cope with it easily even after some googling.
So I have a checkbutton:
self.but_val = IntVar()
self.but = Checkbutton(frame, text="text", variable=self.but_val)
This checkbutton triggers updates of some file path on the GUI:
self.but.bind('<ButtonRelease-1>',
lambda e: self.update_files_path(e), add='+')
In update_files_path(event), I need to get the value of the checkbutton to select the file paths to be displayed:
if self.but_val.get() == 0:
[...]
else:
[...]
The issue I have is that I get the value of the button before the clic.
And since the processing of file paths depends on different button values, I can't just use the opposite value.
My current work around
At the moment I have a function that is triggered before the clic and that save the state of the GUI:
self.but.bind('<ButtonPress-1>', lambda e,
self.save_design_opts_state(self.buttons_to_backup,
self.before_clic_vars_state), add='+')
Then in update_files_path(event) I call a function that infers the GUI state after the clic:
gui_state = self.get_gui_state(event)
This function is very annoying to implement, because I need to do a lot of things:
1- Check that the clic is really made on a button (to avoid a clic that starts on a button and end elsewhere!)
2- Get the value of the of all required buttons depending of their type
Is there an easier way to deal with this?
Thank you for your help!
Don't set your own bindings. Use the command option of the checkbutton. This option lets you specify a command to be run after the value has changed. There are other ways, but this is by far the simplest, most common way to solve your problem.

Function when entered in text box?

I'm using Tkinter for a small Python application. It has a set of ratio buttons, a text box, and a button. Is there a way to make it so the user can simply press Enter/Return on a keyboard and run the same function the button runs? The text box stays selected even when a radio is changed, so that won't cause any problems.
You should be able to bind an event handler to either the text box widget or the whole application that will be called when the event happens. Assuming you have a function to handle the event, something along the lines of:
widget.bind('<Return>', event_handler)
You can also bind a handler function at the application level by calling the bind_all() method of any widget, e.g.:
self.bind_all('<Return>', self.event_handler)
Note the key name is Return not Enter. See Key Names for a list of them all. You can also prefix the key name with a modifier like Shift- and Control- if desired.
There's a decent online reference for tkinter 8.4 here.

Binding <Key> to an Entry in Tkinter

When I bind the event <Key> to an entry and read the content, the change somehow lags behind. I want to "dynamically update" another entry that shows the result of a calculation of the contents of various entries as soon as entry 1 is changed. But somehow the change is not recognized instantly, only the foregoing one. Don't know if the problem is clear, so let's say it like this:
If I make n changes, the changes up to n-1 are recognized. For example, if the number 1000 is in the entry and I press backspace twice, entry_1.get() would yield 100 instead of 10. Hope you understand what i mean now :)
Code snippet (simplified):
self.entry_1.bind('<Key>',lambda d: self.update())
def update(self):
success=True
try:
float(self.entry_1.get())
float(self.entry_2.get())
except ValueError: success=False
if success:
self.entry_3.delete(0,"end")
x=(float(self.entry_1.get())*float(self.entry_2.get())
self.entry_3.insert("end", "%g" %x)
What might be the reason for that?
The reason is due to the order that events are processed. That order is defined by the "binding tag" (or bindtag) of the widget. By default the order is widget, class, toplevel, "all". For example, if you have a binding on the widget, and on the class, and on the toplevel window that contains the widget, and on the special case "all", the bindings will fire in that order.
I gave a lengthy writeup of this problem in this answer to the question How to bind self events in Tkinter Text widget after it will binded by Text widget?

Categories