I have a ComboBox in TKinter from Which i can select an option from a drop down menu.
This works with either mouse click or ENTER key.
I want to do the same but with RIGHT key.
box1 = ttk.Combobox(root, values=different_values)
def boxenter():
#quasi ENTER key functionlity
box1.bind("<Right>",boxenter)
You need to bind on the Listbox widget created internally and this internal Listbox widget can be accessed by calling TCL command:
box1 = ttk.Combobox(root, values=different_values)
def boxenter(event):
# simulate "Enter" key
box1.event_generate('<Return>')
# get the popdown listbox created internally by TCL interpreter
popdown = box1.tk.eval(f'ttk::combobox::PopdownWindow {box1}') + '.f.l'
# call the undocumented _bind()
box1._bind(('bind', popdown), '<Right>', boxenter, None)
Related
Is there Anyway to Bind the Key "Enter" to the button of a tkinter entry? For example I was to query a value from a database and entered the Value to the textbox, but i have to press the button on the screen rather than pressing the enter button on the keyboard. Is there anyway to do it?
Solution
For binding the "enter" key, just use the win.bind('<Return>', command). Here is an example for your case (using label):
#Import the tkinter library
from tkinter import *
#Create an instance of tkinter Tk
win = Tk()
#Set the geometry
win.geometry("650x250")
#Event Handler function
def handler(e):
label= Label(win, text= "You Pressed Enter")
label.pack()
#Create a Label
Label(win, text= "Press Enter on the Keyboard", font= ('Helvetica bold', 14)).pack(pady=20)
#Bind the Enter Key to Call an event
win.bind('<Return>', handler)
win.mainloop()
Other Key Bindings
Most keys have their own name as the name used while binding. But some keys have different key-binds. A list of key-binds and events in tkinter can be found here (These can be used using the tkinter function tkinter.bind('<bind-code>', handler))
I'm trying to create a dynamically-updating searchbox and instead of polling the the entry widget every x milliseconds, I am trying to use keyboard bindings to get the contents of the entry widget whenever a key on the keyboard is pressed:
from tkinter import *
root = Tk()
def callback():
result = searchbox.get()
print(result)
#do stuff with result here
searchbox = Entry(root)
searchbox.pack()
searchbox.bind("<Key>", lambda event: callback())
root.mainloop()
my problem is that searchbox.get() is always executed before the key the user pressed is added to the searchbox, meaning the result is the content of the searchbox BEFORE the key was pressed and not after.
For example if I were to type 'hello' in the searchbox, I would see the following printed:
>>>
>>> h
>>> he
>>> hel
>>> hell
Thanks in advance.
There's no need to use bindings. You can associate a StringVar with the widget, and put a trace on the StringVar. The trace will call your function whenever the value changes, no matter if it changes from the keyboard or the mouse or anything else.
Here's your code, modified to use a variable with a trace:
from tkinter import *
root = Tk()
def callback(*args):
result = searchbox.get()
print(result)
#do stuff with result here
var = StringVar()
searchbox = Entry(root, textvariable=var)
searchbox.pack()
var.trace("w", callback)
root.mainloop()
For information about the arguments passed to the callback see What are the arguments to Tkinter variable trace method callbacks?
For a better understanding of why your bindings seem to always be one character behind, see the accepted answer to Basic query regarding bindtags in tkinter
So I have Entries which have some values assigned to them from a CFG File. I want to modify the CFG file when the Entry is updated, live, without a submit button;
Using <Key> binding will work but will take only the previous value, not the current one, as the last key pressed is not taken into consideration as a value, but as a key-press.
For example:
class EntryBox:
def __init__(self, value, option, section, grid_row, master_e):
self.section = section
self.option = option
self.box = Entry(master_e)
self.box.grid(column=0, row=grid_row)
self.serial_no = grid_row
self.box.insert(0, value)
self.box.bind("<Key>", lambda event: update_cfg(event, self, self.get_value()))
def get_value(self):
return self.box.get()
def update_cfg(evt, entry_box,new_value):
global config_file
config_file.set(entry_box.section, entry_box.option, new_value)
print "Config file modified. "+entry_box.section+" "+entry_box.option+" "+new_value
If the value in the entry is 05R when I click on the entry and press 6, it will print Config file modified. CURRENT_MEASUREMENT_EXAMPLE_HP shunt_resistance 05R; after I press 7, it will print Config file modified. CURRENT_MEASUREMENT_EXAMPLE_HP shunt_resistance 0R56 and so on, always with one keypress behind. The only way to live update it after the value has been changed is to press the TAB or arrow buttons.
You can use either
FocusOut
tab or enter Key
KeyRelease
bindings to achieve that.
Also validation functions can help as they have previous and new values available. Please read the docs for more information on that matter.
It is IMHO the most "pythonic" / "tkinter" way of achieving what is a "check and submit" functionality.
Edit
As stated by OP, binding focusout could lead to problems here an example how it does indeed work:
import Tkinter as tk
import sys
def focus_out_event(event):
print >> sys.stderr, "Focus-Out event called with args: %s"%event
print >> sys.stderr, "Entry Widget Content: %s"%event.widget.get()
def key_release_event(event):
print >> sys.stderr, "Key-Release event called with args: %s"%event
print >> sys.stderr, "Entry Widget Content: %s"%event.widget.get()
if __name__ == "__main__":
root = tk.Tk()
entry1 = tk.Entry(root)
entry1.bind("", key_release_event)
entry1.bind("", focus_out_event)
entry1.grid()
entry2 = tk.Entry(root)
entry2.bind("", key_release_event)
entry2.bind("", focus_out_event)
entry2.grid()
root.mainloop()
Test:
- enter text ("asd") to entry1
- click into entry2
The last line of output is from changing to screenshot (event that fired a focusout)
You have this key-press in event.char so you can add it to text.
I decided that <Key> was not the right option in my case and instead used <FocusOut>. This way, if you either change the value using the mouse or keyboard TAB, on focus out it will update it.
I'm developing a GUI application that models an essay. Among other things, the user can create a new topic and then populate that topic with notes. At the moment, I have two ways of creating new topics: through a dropdown option in the menu (the menu command) and through a button on the main screen (the button command). The button starts life with the text "New Topic". When the user presses the button, the program makes a new topic, asks the user to name the topic using tkSimpleDialog.askstring, and then sets the button's text to be the name of the topic and the number of notes in that topic. The button's command then changes to be adding a note to that topic.
While developing the program, I first verified that the menu command worked. It calls askstring successfully, creating a new popup window that handles input in the way I wanted. However, as soon as I added the button command, the call to askstring failed, even when called via the menu command. The window that should have the askstring dialog is whited out and the program hangs. If I comment out the button command, it works again. If I comment out the menu command, it hangs.
Here's the code where I add the command to the menu:
TopicBtn.menu.add_command(label="New Topic", underline=0,
command=self.newTopic)
Here's the code for newTopic():
def newTopic(self, button=None):
""" Create a new topic. If a Button object is passed, associate that Button
with the new topic. Otherwise, create a new Button for the topic. """
topicPrompt = "What would you like to call your new topic?"
topicName = tkSimpleDialog.askstring("New Topic", topicPrompt)
if topicName in self.topics.keys():
print "Error: topic already exists"
else:
newTopic = {}
newTopic["name"] = topicName
newTopic["notes"] = []
newTopic["button"] = self.newTopicButton(newTopic, button)
self.topics[topicName] = newTopic
self.addToTopicLists(newTopic)
Here's the code for newTopicButton():
def newTopicButton(self, topic, button=None):
""" If a Button object is passed, change its text to display the topic name.
Otherwise, create and grid a new Button with the topic name. """
if button is None:
button = Button(self.topicFrame)
index = len(self.topics)
button.grid(row=index/self.TOPICS_PER_ROW, column=(index %
self.TOPICS_PER_ROW), sticky=NSEW, padx=10, pady=10)
else:
button.unbind("<Button-1>")
buttonText = "%s\n0 notes" % topic["name"]
button.config(text=buttonText)
button.config(command=(lambda s=self, t=topic: s.addNoteToTopic(t)))
return button
And, finally, here's the code for the button command:
for col in range(self.TOPICS_PER_ROW):
button = Button(self.topicFrame, text="New Topic")
button.bind("<Button-1>", (lambda e, s=self: s.newTopic(e.widget)))
button.grid(row=0, column=col, sticky=NSEW, padx=10, pady=10)
Anybody have any idea why binding the lambda expression to the button makes askstring hang?
Edit: Thanks for the comments. Here's a minimal example that exhibits the behavior:
from Tkinter import *
import tkSimpleDialog
class Min():
def __init__(self, master=None):
root = master
frame = Frame(root)
frame.pack()
button = Button(frame, text="askstring")
button.bind("<Button-1>", (lambda e, s=self: s.newLabel()))
button.grid()
def newLabel(self):
label = tkSimpleDialog.askstring("New Label", "What should the label be?")
print label
root = Tk()
m = Min(root)
root.mainloop()
Note that switching from button.bind("<Button-1>", (lambda e, s=self: s.newLabel())) to button = Button(frame, text="askstring", command=(lambda s=self: s.newLabel())) fixes the bug (but doesn't give me a reference to the button that was pressed). I think the problem has something to do with capturing the event as one of the inputs to the lambda.
The problem you encountered here is due to the call to wait_window in the dialog you are using (you never call it yourself, but the code that implement the dialog does). For instance, the following code replicates the problem after (likely) two button clicks:
import Tkinter
def test(event=None):
tl = Tkinter.Toplevel()
tl.wait_window(tl)
root = Tkinter.Tk()
btn = Tkinter.Button(text=u'hi')
btn.bind('<Button-1>', test)
btn.pack(padx=10, pady=10)
root.mainloop()
This call to wait_window effectively does what the update command does, and is a typical example of why calling update is a bad thing to do. It enters in conflict with the <Button-1> event being handled, and hangs. The problem is that you will have to live with wait_window being used, since it belongs to the dialog's code. Apparently, if you bind to <ButtonRelease-1> then this conflict never happens. You could also use the command parameter in the button, which works fine too.
Lastly, I suggest the following to create the buttons in a cleaner manner based on what you want to achieve:
for i in range(X):
btn = Tkinter.Button(text=u'%d' % i)
btn['command'] = lambda button=btn: some_callback(button)
I figured out a workaround. From the minimum-example testing, it appears that the problem comes from making a separate call to bind and thereby accepting the event as an input to the lambda. If anyone can explain why that might be happening, I'll accept their answer over mine, but I'll accept this one for now.
The workaround is not to use a separate bind function but to create an array of buttons and then pass the correct entry in the array as the parameter to the lambda function (you can't pass the button itself, since it's being created in the line that has the lambda function).
Here's the code:
from Tkinter import *
import tkSimpleDialog
class Min():
def __init__(self, master=None):
root = master
frame = Frame(root)
frame.pack()
buttons = [None] * 2
for i in range (2):
buttons[i] = Button(frame, text="askstring",
command=(lambda s=self, var=i: s.newLabel(buttons[var])))
buttons[i].grid()
def newLabel(self, button):
label = tkSimpleDialog.askstring("New Label", "What should the label be?")
button.config(text=label)
print label
root = Tk()
m = Min(root)
root.mainloop()
I have added a button as:
def addButtons(self):
self.buttonBox = Pmw.ButtonBox(self.dataFrame,
labelpos = 'nw',
label_text = '',
frame_borderwidth = 1,
frame_relief = 'groove')
self.buttonBox.pack()
self.buttonBox.place(relx=.75, rely=.750, anchor=W)
# Add some buttons to the ButtonBox.
self.buttonBox.add('Login', command = self.login)
# Set the default button (the one executed when <Return> is hit).
self.buttonBox.setdefault('Login')
self.dataFrame.bind('<Return>', self._processReturnKey)
self.dataFrame.focus_set()
Now, I want to disable login button when user press it. How can i do that? I have found answer for simple button, but this button is in button box.
According to the pmw.ButtonBox documentation, the add method returns a reference to the button that is created. So, just save the reference that it returns and then configure that button like you would any other button.