I'm trying to bind my mouse double click to a function which for now just prints the current selection in a Tkinter list box. To be clear the function should only print when the user double clicks on one of the items of a Tkinter list box. What event binding should I use?
You can bind to <Double-Button-1>:
widget.bind('<Double-Button-1>', handler)
There is also <Button-1> for normal mouse clicks and <Triple-Button-1> for a triple mouse click.
For more information on bindings in Tkinter, see Events and Bindings.
You have to realize that there is a hierarchy to all widgets, and this means that for each widget you click, multiple bindings are possible. If you don't override the default action, each hierarchy's default handler gets called, starting at the lowest level (such as your Listbox) and going all the way up to the Tk() or Toplevel() widget. For you, since you want to print only when a listbox item is clicked, you can bind to the listbox widget, as follows:
listboxWidget.bind('<Double-Button-1>', listboxWidget_leftclick_handler)
Then, when you enter the def listboxWidget_leftclick_handler(event) function, you don't have to check the event.widget value to see if it's the name of your Listbox widget. But you could also check at a higher level (bind a handler to a higher-level widget) and check event.widget to see which widget was clicked.
Also note that the only way to prevent the entire hierarchy of event handlers from triggering is by using a return 'break' from your custom handler, but you usually only need to do this if later handlers corrupt what your custom handler has done.
Additional info about default handlers
The other part which I left out is that there is also a "default" handler for most events. If you bind your own handler, once it's finished, if you don't return 'break', the default handler will be called next.
For example, say you want to make your own Entry box into a password entry. By default, when you type alphanumeric chars when the Entry has focus (which means it's getting input from the keyboard), the chars will appear in the Entry. You can bind:
myEntry.bind('<KeyPress>', passworder)
where passworder is your custom handler which grabs the event holding your inputted char and then outputs an asterisk into the Entry instead. But, if you don't use a return "break" at the end of your handler, the Entry widget is still going to see that char that you didn't want shown, because once your handler is done inserting the asterisk, the default handler will simply insert the typed char (like it would normally). But, if you do the return 'break', the default handler won't get called, and the typed char(s) won't appear in the Entry.
As an add-on. In order to distinguish action between a single click and a double click, delay the call to mouse action for a brief period to allow for the double click flag to be set. See below example:
from tkinter import *
def mouse_click(event):
''' delay mouse action to allow for double click to occur
'''
aw.after(300, mouse_action, event)
def double_click(event):
''' set the double click status flag
'''
global double_click_flag
double_click_flag = True
def mouse_action(event):
global double_click_flag
if double_click_flag:
print('double mouse click event')
double_click_flag = False
else:
print('single mouse click event')
root = Tk()
aw = Canvas(root, width=200, height=100, bg='grey')
aw.place(x=0, y=0)
double_click_flag = False
aw.bind('<Button-1>', mouse_click) # bind left mouse click
aw.bind('<Double-1>', double_click) # bind double left clicks
aw.mainloop()
Related
I want to perform an action when a mouse hover event occurred on customtkinter ctkbutton hover. is it yet implemented?
Per the CTkButton source code, the on_enter method is bound to the <Enter> event. This method is predominantly focused on updating the button's appearance on hover. If you want to trigger an additional callback on hover, you'll have to add another binding to the button
def callback(event):
# put whatever you want to do 'on hover' into this function
print('Button hovered!')
button = CTkButton(parent, text='Button!')
# use '+' to avoid overwriting the existing binding
button.bind('<Enter>', callback, add='+')
button.pack()
I have an entry field, and as I type into the data field, I want a method, that updates a treeview widget to be ran. Currently, I can type in a search parameter, then press a 'search' button and it will run the method to search through the treeview to find specified clients, but I want to the treeview to be updating whilst typing into the entry, not by a button press
I am unsure as to weather this is possible, if it should be doing by binding keys or if there is a way using the event loop to achieve this?
See this SO post:
TkInter keypress, keyrelease events
Essentially:
from Tkinter import *
def keyup(e):
pass;
# e.char contains the pressed key if you need that info
# use your search function here
Edit (Sorry I forgot this):
You'll need to bind the keyup function to your widget with something like:
frame.bind("<KeyRelease>", keyup) # you can also bind to a search widget
http://code.activestate.com/recipes/580770-combobox-autocomplete/
Working with the class above. As of right now, a drop down list only appears when you start typing in into the entry box, auto completing the word you're typing in.
How can i create a method that makes the full list appear when the entry box is in focus?
You can bind to the <FocusIn> event to call a function when the widget gains focus.
def do_something(event):
...
entry = tk.Entry(...)
entry.bind('<FocusIn>', do_something)
The code below is part of a function that produces a simple dialog with caller selected text and buttons.
The problem lies in the handling of key inputs of 's', 'a', 'd', and 'c. The code operates correctly for mouse clicks and tabbing followed by the space or enter keys. The code is written in Python 3.4 and is being tested for compliance with Windows 7.
My understanding is that Tk handles refocusing for the end user's mouse clicks. The space and enter keys are used after the user has changed focus by tabbing. For all of these interactions the keys are bound to each button by the code:
for action in ('<1>', '<space>', '<Return>'):
b.bind(action, handler)
For 'underline' key input I believe my code needs to handle the refocusing prior to calling the handler. This is the purpose of the refocus routine. The print statement ("refocussing to...") is printed with the correct value for button.winfo_name() whenever 's', 'a', 's', or 'c' is pressed . This is why I believe button.focus_set() is failing.
If it worked this would enable the handler to simply check which button was pressed by looking at event.widget.winfo_name(). As it is, the failure to refocus means that the handler is called with the wrong button in event.widget.winfo_name()
If I move focus manually by tabbing then the focus gives the name returned by event.widget.winfo_name() regardless of which of 's', 'a', 'd', or 'c' is pressed.
After reading other posts on Stack Overflow, I tried adding button.focus_force() after button.focus_set(). This had no effect on the problem.
I have tried passing the button name by changing the signature of the handler to def button_handler(event, *args) and then changing the last line of refocus to handler(event, button.winfo_name()) but *args is empty when called.
def refocus_wrapper(button):
def refocus(event):
print("refocusing to '{}'".format(button.winfo_name()))
button.focus_set()
handler(event)
return refocus
for button_text, underline, button_name in buttons:
b = ttk.Button(button_inner_frame, text=button_text, name=button_name,
underline=underline)
b.pack(padx=2, side='left')
for action in ('<1>', '<space>', '<Return>'):
b.bind(action, handler)
action = '{}'.format(button_text[underline:underline + 1].lower())
dialog.bind(action, refocus_wrapper(b))
if not default or default == button_name:
default = button_name
b.focus_set()
Your initial assumption about needing to set the focus on the button is not correct. The usual method to handle this in Tk is to bind the accelerator key event on the dialog toplevel form and call the buttons invoke method for the event binding.
In Tcl/Tk thats:
toplevel .dlg
pack [button .dlg.b -text "Save" -underline 0 -command {puts "Save pressed"}]
bind .dlg <Alt-s> {.dlg.b invoke}
So bind the key events on whatever toplevel is the parent for your buttons. If that is a dialog then its parent should be the application toplevel widget.
A python equivalent is:
from tkinter import *
main = Tk()
dialog = Toplevel(main)
button = Button(dialog, text="Send", underline="0", command=lambda: print("hello"))
button.pack()
dialog.bind('<Alt-s>', lambda event: button.invoke())
main.mainloop()
The key binding appends an event object to the callback function which we can discard using a lambda to wrap the call the the button's invoke method.
focus_set() was, of course, working perfectly. My expectation that refocusing would rewrite the event object was at fault. The following code (which has been extensively revised to incorporate Brian Oakley's suggestions) produces the expected results.:
def _make_buttons(dialog, buttons, buttonbox, default, handler):
def bcommand_wrapper(button):
def bcommand():
name = button.winfo_name()
dialog.destroy()
handler(name)
return bcommand
for button_text, underline, button_name in buttons:
b = ttk.Button(buttonbox, text=button_text, underline=underline,
name=button_name)
b.configure(command=bcommand_wrapper(b))
b.pack(padx=2, side='left')
action = button_text[underline:underline + 1].lower()
try:
dialog.bind(action, lambda event, b=b: b.invoke())
except tk.TclError:
raise ValueError(
"Invalid underline of '{}' in position {}. Character '{}'.".
format(button_text, underline, action))
b.bind('<Return>', lambda event, b=b: b.invoke())
if not default or default == button_name:
default = button_name
b.focus_set()
I gave up my original idea of returning a Tk event to the caller. This is a dialog so the caller isn't going to need anything more than the name of the button that was clicked.
Note that I am not trapping accelerator keys with the 'Alt' modifier. The 'Alt' key, at least on MS Windows, is a functional key when used with accelerator keys: It causes the display of underlines on menus. Here the underlines are static and so the use of the 'Alt' key would be inappropriate.
Might I suggest using root.bind('c', ) to just have the shortcuts perform what you want?
Just make sure to only bind them when the window pops up, and unbind them when you're done.
I have the following problem when using tkinter to create a very simple window containing a matrix of buttons: When one of the buttons is clicked, the event handler changes the text of that button using the configure method on the button widget. This works. But I also want to change the text in one of the other buttons, which does not work. The method I use is that on creating the button, I store the object returned by the Button method before I use the grid geometry manager to place it. This object looks like ".123456789L" when printed and seems to be a pointer to the widget. I also use configure on this to change the button text. But somehow it seems to be wrong, because it works sometimes, and most of the times not. There's unfortunately no error message, just nothing happens when calling configure. I checked and it seems to be the correct pointer to the widget. Do I have to use a special way to affect a widget other that the one that called the event handler? These are the relevant parts of the code:
# CREATING THE BUTTONS:
buttons={} # global
for i in range(3):
for j in range(3):
button = Tkinter.Button(self,text='foo')
buttons[button]=(i,j)
button.grid(column=j,row=i)
button.bind( "<Button-1>", self.OnButtonClick )
# CHANGING BUTTONS:
def find_button(i,j):
"""Return the pointer to the other button to be changed when a button has been clicked."""
for button,key in buttons.items():
if key==(i,j): return button
def OnButtonClick(self,event):
print "You clicked the button",buttons[event.widget]
i,j=buttons[event.widget]
old_button=find_button(i,j) # This is simplified, I don't actually pass i,j, but other values. But I checked this and it returns the reference to the correct button. But this simplified version works the same way, just assume a different button that the one pressed would be returned.
old_button.configure(text = 'blabla') # THIS DOES NOT WORK
event.widget.configure(text = 'something') # THIS WORKS
I have the same problem and i solve it with:
buttons[button]=(i,j,button)
and in the function OnButtonClicK:
i,j,old_button=buttons[event.widget]
old_button.configure(text = 'blabla') # THIS DOES NOT WORK