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()
Related
Is it possible to register multiple listeners for an event?
Imagine a click on a Treeview. There might be a standard binding on the <<TreeviewSelect>>, e.g.
tree.bind('<<TreeviewSelect>>', some_function)
resulting in an Entry getting changed. But what if additional Entrys are added later, each to be filled with another part of the data in the Treeview?
As far as I know, there can be only one binding per event-type per widget, i.e. not multiple <<TreeviewSelect>> functions getting bound. The following
tree.bind('<<TreeviewSelect>>', function1)
tree.bind('<<TreeviewSelect>>', function2)
...
would result in the last function being the only one getting called. Is there a way to register multiple function calls?
To run both functions on that event, you could create function3:
def function3(event=None):
function1(event)
function2(event)
and then bind the <<TreeviewSelect>> event to function3.
tree.bind("<<TreeviewSelect>>", function3)
Edit: another way.
You could also add the add=True parameter, like this:
tree.bind("<<TreeviewSelect>>", function1)
tree.bind("<<TreeviewSelect>>", function2, add=True)
This code snippet demonstrates how to use event_add to any widget for multiple access to a selected function.
Once the event is created using the <<name>> tag it can be bound to a function using the bind method.
import tkinter as tk
root = tk.Tk()
tree = ttk.Treeview(root)
tree.event_add("<<TreeViewSelect>>", "<Button-1>", "<Button-3>")
def me(event=None):
print(event.widget)
tree.bind("<<TreeViewSelect>>", me)
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.
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 would like to be able to double click on test,
in a Tkinter Text widget, and have it select test (and exclude the comma).
Here is what I've tried:
import Tkinter as tk
def selection_mod(event=None):
result = aText.selection_get().find(',')
if result > 0:
try:
aText.tag_add("sel", "sel.first", "sel.last-1c")
except tk.TclError:
pass
lord = tk.Tk()
aText = tk.Text(lord, font=("Georgia", "12"))
aText.grid()
aText.bind("<Double-Button-1>", selection_mod)
lord.mainloop()
The first issue is that <Double-Button-1> seems to trigger the handler before the selection is made, producing:
TclError: PRIMARY selection doesn't exist or form "STRING" not defined
The second issue is that even when using a binding that works,
my selection tag doesn't seem to do anything.
It doesn't even raise an error, and I've tried without the except tk.TclError:.
Your binding is happening before the default bindings occur. Thus, the selection doesn't yet exist when your binding fires. Because your binding tries to get the selection, it fails with the error that you see.
You will need to arrange for your binding to happen after the class bindings. A cheap hack is to use after to execute your code once the default bindings have a chance to work. Or, you can use the bindtag feature to make sure your binding fires after the default bindings.
The second problem is that you don't clear the old selection before setting the new. You'll want to do tag_remove to first remove the existing selection. Otherwise, the comma (if it was somehow selected) will remain selected since all you're doing is re-applying the tag to text that already has the tag.
However, double-click doesn't normally capture the comma so I don't quite understand then point of your code. At least, when I test it on OSX it doesn't include the comma.
Here is what I came up with thanks to Bryan's answer:
import Tkinter as tki # tkinter in Python 3
def selection_mod(event=None):
result = txt.selection_get().find(',')
if result > 0:
fir, sec = txt.tag_ranges("sel")
txt.tag_remove("sel", "sel.first", "sel.last")
txt.tag_add("sel", fir, str(sec)+"-1c")
root = tki.Tk()
txt = tki.Text(root, font=("Georgia", "12"))
txt.grid()
txt.bind("<Double-Button-1>", lambda x: root.after(20, selection_mod))
root.mainloop()
It's worth noting that I'm using Windows 7, and according to Bryan,
OSX doesn't include the comma when you double click a word.
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.