Can you please tell me if there's a convenient way to have a bind only on "significant" keys with tk/tkinter?
So, the situation is I have a bind on a tk.Entry (because I need a callback to trigger while keys are being pressed), like this:
myEntry.bind('<Key>', ...)
Right now, the bind callback is triggered whatever key is pressed (even if it is the shift!) but I'd want it to trigger only on "significant" keys... By "significant" I mean every key that involve changes on the text in the Entry, so "significant" keys are for sure letters, numbers, symbols and the backspace or delete keys, but NOT arrow keys, home, end, pgUp/Down or also the tab, caps-lock, shift, ctrl, etc... (I'm still thinking if I will need it to trigger on return key or not but that's not a problem, because if I need it too I could add a specific bind on it or otherwise have it ignored in the callback later)
I don't know if perhaps is there something different from <Key> I could bind to get what I need, is it?
Otherwise, if not, I know I can get which key was pressed looking at event keycode... is that the way to go? If yes, can you suggest me some suitable keycode interval please?
Thank you
In a comment, you say you are doing real-time filtering of a listbox. For that, it would arguably be better to set a trace on a variable rather than setting a binding on the entry widget. The trace will only be called when the value changes, so you don't have to distinguish which key triggered the change.
Here's a simple example that illustrates the concept:
import tkinter as tk
widgets = [w.__name__ for w in tk.Widget.__subclasses__()]
root = tk.Tk()
entryvar = tk.StringVar()
entry = tk.Entry(root, textvariable=entryvar)
listbox = tk.Listbox(root)
entry.pack(side="top", fill="x")
listbox.pack(side="bottom", fill="both", expand=True)
listbox.insert("end", *sorted(widgets))
after_id = None
def filter_changed(*args):
global after_id
if after_id:
root.after_cancel(after_id)
# delay actual filtering slightly, in case the user is typing fast.
after_id = root.after(500, filter_listbox)
def filter_listbox(*args):
pattern = entryvar.get()
filtered_widgets = [w for w in widgets if w.startswith(pattern) ]
listbox.delete(0, "end")
listbox.insert("end", *filtered_widgets)
entryvar.trace("wu", filter_changed)
root.mainloop()
Here is my solution, it uses event.char and evaluates against a string with characters (can add more characters if needed):
from tkinter import Tk, Entry
string = r"""0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"""
def key_press(event):
if event.char and event.char in string:
print(event.char)
root = Tk()
entry = Entry(root)
entry.pack()
entry.bind('<Key>', key_press)
root.mainloop()
Related
I am writing an editor (using the tkinter text widget), which replaces tab-characters (inserted by the user) on the fly by 4 blanks.
The replacement is done by a binding to the tabulator-key-event ("Tab"), which inserts 4 blanks and returns with "break". Returning with "break" prevents the tabulator-character from being inserted. This works fine.
Additionally I need a second binding to any key-event ("Key", for syntax highlighting and similar stuff). So I implemented a second binding to "Key". This also works fine.
As I found, the binding of <Tab> has a higher priority as the binding of <key>:
Whenever the tab-key is pressed, only the tab-event gets active but never the key-event.
Why is that?
Is there any priority order defined for events?
This is my example code:
import tkinter as tk
def key_event():
print("Key")
def tab_event(text):
print("Tab")
text.insert("insert", " ")
return("break")
root = tk.Tk()
text = tk.Text(root, height=8, width=20)
text.grid()
text.bind("<Tab>", lambda event : tab_event(text))
text.bind("<Key>", lambda event : key_event())
root.mainloop()
According to the documentation, the more specific binding is chosen over the other. A simple but effective way around this is to use a broad binding like '<Key>' and delegate the event accordingly by it's keysym, that you can access by event.keysym.
As example:
import tkinter as tk
def key_event(event):
if event.keysym == 'Tab':
text.insert("insert", " "*4)
return 'break'
root = tk.Tk()
text = tk.Text(root, height=8, width=20)
text.grid()
text.bind("<Key>", key_event)
root.mainloop()
Okay, so I am making a typing speed test in Tkinter. I want the entry to clear when pressing space. Which it does, however, because space is bound as the key to clear the entry it's as if though Tkinter clears the entry and then adds space to the entry thereafter. Meaning, that after entering the first word, from there on out there is always a space at the beginning of the entry before you start typing the next word.
You can get your desired behaviour by returning "break". This will make sure that the event doesn't propagate to other handlers.
Here is a minimal example:
import tkinter as tk
def handle(event):
entry.delete("0", "end")
return "break"
root = tk.Tk()
entry = tk.Entry(root)
entry.pack()
entry.bind("<space>", handle)
root.mainloop()
Also as #TheLizzard pointed out you could also try: entry.bind("<KeyRelease-space>", handle)
import tkinter as tk
def handle(event):
entry.delete("0", "end")
root = tk.Tk()
entry = tk.Entry(root)
entry.pack()
entry.bind("<KeyRelease-space>", handle)
root.mainloop()
Also, something to note is that the second solution will add a space then delete, whereas the first one simply deletes the text without any added space.
I've been working on text widget using tkinter. My requirement is to restrict the functionality of Copy(ctrl+c), Paste(ctrl+v) and backspace. It's like once entered into the text widget there is no editing like clearing, and adding from somewhere. The user has to type and cannot backspace.
self.inputfeild = tk.Text(self, bg="White")
self.inputfeild.pack(fill="both", expand=True)
This is my Text widget which was declared inside a class.
You can use event_delete method to delete virtual event associated with it.
eg:
inputfield.event_delete('<<Paste>>', '<Control-v>')
inputfield.event_delete('<<Copy>>', '<Control-c>')
Check out more Here
Or you can simply bind that event to an event handler and return 'break' like this:
from tkinter import *
root = Tk()
inputfield = Text(root, bg="White")
inputfield.pack(fill="both", expand=True)
inputfield.bind('<Control-v>', lambda _: 'break')
inputfield.bind('<Control-c>', lambda _: 'break')
inputfield.bind('<BackSpace>', lambda _: 'break')
root.mainloop()
In addition to #JacksonPro 's answer, you could also try this approach,
from tkinter import *
def backspace(event):
if text.tag_ranges(SEL):
text.insert(SEL_FIRST,text.get(SEL_FIRST, SEL_LAST))
else:
last_char=text.get('1.0','end-1c')[-1]
text.insert(END,last_char)
root=Tk()
text=Text(root)
text.pack()
text.bind('<KeyRelease>', lambda event=None:root.clipboard_clear())
text.bind('<KeyPress>', lambda event=None:root.clipboard_clear())
text.bind('<BackSpace>', backspace)
root.mainloop()
This basically will clear your clipboard everytime you perform a KeyPress or a KeyRelease and hence copying/pasting will not be possible. The backspace() function obtains the last character and re-inserts it at the last position wherever backspace is used and indirectly restrics it's function. My previous appreach to backspace() wasn't right since it didn't take selection into account, but now it should work in all the cases, if there is something selected, it will obtain the selected text and insert it at the beginning of the selection (SEL_FIRST) else it will just obtain and reinsert the last charecter.
In python, how can I receive keyboard input. I'm well aware of console input with input("...") but I'm more concerned with receiving keyboard input while the console window is not active. For example if I created an instance of a Tkinter screen how could I check to see if let's say "w" was pressed. Then if the statement returned true i could move an object accordingly.
The way you do this with a GUI toolkit like tkinter is to create a binding. Bindings say "when this widget has focus and the user presses key X, call this function".
There are many ways to accomplish this. You can, for example, create a distinct binding for each character. Or, you could create a single binding that fires for any character. With these bindings, you can have them each call a unique function, or you can have all the bindings call a single function. And finally, you can put the binding on a single widget, or you can put the binding on all widgets. It all depends on exactly what you are trying to accomplish.
In a simple case where you only want to detect four keys, four bindings (one for each key) calling a single function makes perhaps the most sense. For example, in python 2.x it would look something like this:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, width=400, height=400)
self.label = tk.Label(self, text="last key pressed: ", width=20)
self.label.pack(fill="both", padx=100, pady=100)
self.label.bind("<w>", self.on_wasd)
self.label.bind("<a>", self.on_wasd)
self.label.bind("<s>", self.on_wasd)
self.label.bind("<d>", self.on_wasd)
# give keyboard focus to the label by default, and whenever
# the user clicks on it
self.label.focus_set()
self.label.bind("<1>", lambda event: self.label.focus_set())
def on_wasd(self, event):
self.label.configure(text="last key pressed: " + event.keysym);
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
I'm using Tkinter to create a GUI for a simple geometry calculator I'm creating.
Basically, what I have is an Entry box. What I want is for the program/GUI/system to detect when the user of the program hits the 'Enter' or 'return' key WHILE they are in the Entry box. When this is detected, I want the contents of the Entry box to be appended to a list I have defined earlier. I also want a simple label to be created on the GUI that displays the contents of the list (including the appended item(s)). Note that the list begins with nothing in it.
Here is my code so far:
from tkinter import *
#Window setup(ignore this)
app = Tk()
app.title('Geometry Calculator')
app.geometry('384x192+491+216')
app.iconbitmap('Geo.ico')
app.minsize(width=256, height=96)
app.maxsize(width=384, height=192)
app.configure(bg='WhiteSmoke')
#This is the emtry list...
PointList = []
#Here is where I define the variable that I will be appending to the list (which is the object of the Entry box below)
StrPoint = StringVar()
def list_add(event):
#I don't really know how the bind-checking works and how I would implement it; I want to check if the user hits enter while in the Entry box here
if event.char == '':
PointList.append(StrPoint)
e1 = Entry(textvariable=StrPoint).grid(row=0, column=0)
app.bind('<Return>', list_add)
mainloop()
I don't really know the proper way to check for 'Return' and then use it in an if statement.
I hope you understand what I'm trying to get help with, and I've looked all around for an explanation that I could understand with no success.
Instead of binding with the app just bind it with the Entry widget object,i.e,e1
from tkinter import *
#Window setup(ignore this)
app = Tk()
app.title('Geometry Calculator')
app.geometry('384x192+491+216')
app.iconbitmap('Geo.ico')
app.minsize(width=256, height=96)
app.maxsize(width=384, height=192)
app.configure(bg='WhiteSmoke')
#This is the emtry list...
PointList = []
#Here is where I define the variable that I will be appending to the list (which is the object of the Entry box below)
StrPoint = StringVar()
def list_add(event):
print ("hello")
#I don't really know how the bind-checking works and how I would implement it; I want to check if the user hits enter while in the Entry box here
if event.char == '':
PointList.append(StrPoint)
e1 = Entry(textvariable=StrPoint)
e1.grid(row=0, column=0)#use grid in next line,else it would return None
e1.bind('<Return>', list_add)# bind Entry
mainloop()
The solution is to set the binding on the widget itself. That way, the binding will only apply while focus is on that widget. And since you're binding on a specific key, you don't need to check for the value later. You know the user pressed return, because that's the only thing that will cause the binding to fire.
...
e1.bind('<Return>', list_add)
...
You have another problem in that your list_add function needs to call the get method of the variable rather than accessing the variable directly. However, since you aren't using any of the special features of a StringVar, you really don't need it -- it's just one more thing you have to manage.
Here's how to do it without the StringVar:
def list_add(event):
PointLit.append(e1.get())
...
e1 = Entry(app)
e1.grid(row=0, column=0)
e1.bind('<Return>', list_add)
Note that you need to create the widget and lay out the widget in two steps. Doing it the way you did it (e1=Entry(...).grid(...) will cause e1 to be None since that is what .grid(...) returns.