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()
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()
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()
I have been searching for a way to set the tab order in a tkinter application, that I have been working on. Currently the default order seems to be working from top-down, but it requires using CTRL + Tab to cycle through the controls.
Is there any way to customize the order and, more so, change the CTRL + Tab to just Tab?
Tab order is based on the stacking order, which in turn defaults to the order that widgets are created. You can adjust the stacking order (and thus the tab order) using the methods tkraise (or lift) and lower.
This should be working out of the box for you without the need to press CTRL + Tab. Be aware, however, that tab inserts a literal tab in text widgets rather than moving focus to another control. That default behavior can be changed of course.
Here's an example showing how to reverse the tab order. When running the example, pressing tab in the first entry should take you to the last. Pressing tab again takes you to the second, then the first, lather, rinse, repeat
Note that the native Tk commands are raise and lower, but since raise is a reserved word in Python it had to be renamed in tkinter.
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
e1 = tk.Entry(self)
e2 = tk.Entry(self)
e3 = tk.Entry(self)
e1.insert(0,"1")
e2.insert(0,"2")
e3.insert(0,"3")
e1.pack()
e2.pack()
e3.pack()
# reverse the stacking order to show how
# it affects tab order
new_order = (e3, e2, e1)
for widget in new_order:
widget.lift()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Since you mention you have to do CTRL + Tab, I'm guessing you're trying to have the tab key change focus from a text widget. Normally a tab key inserts a literal tab. If you want it to change the focus, just add a binding to the <Tab> event.
Tkinter has a function that will return the name of the next widget that should get focus. Unfortunately, for older versions of Tkinter that function is buggy. However, it's easy to work around that. Here's a couple of methods you can add to the above code:
def _focusNext(self, widget):
'''Return the next widget in tab order'''
widget = self.tk.call('tk_focusNext', widget._w)
if not widget: return None
return self.nametowidget(widget.string)
def OnTextTab(self, event):
'''Move focus to next widget'''
widget = event.widget
next = self._focusNext(widget)
next.focus()
return "break"
I've been searching for ways to skip some widgets while tabbing and found in tkinter's tk_focusNext function description the following: "A widget is omitted if it has the takefocus resource set to 0."
you can set takefocus on widget initialization as an argument.
By default, tkinter's Checkbutton widget responds to clicks anywhere in the widget, rather than just in the check box field.
For example, consider the following (Python 2) code:
import Tkinter as tk
main = tk.Tk()
check = tk.Checkbutton(main, text="Click").pack()
main.mainloop()
Running this code results in a small window, with a single check box, accompanied by the word "Click". If you click on the check box, it is toggled.
However, this also happens if you click on the text of the widget, rather than the check box.
Is this preventable? I could make a separate widget to hold the text, but that still results in a small area around the check box that responds to clicks.
Two solutions come to mind:
Do as you suggest and create a separate widget for the checkbutton and for the label.
replace the bindings on the checkbutton with your own, and examine the x/y coordinates of the click and only accept the click if it happens in a small region of the widget.
This program creates a checkbutton and overrides the default event on it by binding a method to the checkbutton. When the button is clicked, the method checks a defined limit to allow the normal operation or to override. OP wanted to make sure that when text of the checkbutton is clicked, no default action is taken. That is essentially what this does.
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.checkvar = IntVar()
check = tk.Checkbutton(parent, text='Click', variable=self.checkvar)
check.bind('<Button-1>', self.checkCheck)
check.pack()
print(dir(check))
def checkCheck(self, event):
# Set limit based on your font and preference
limit = 18
print(event.x, event.y, self.checkvar.get())
if event.x > limit or event.y > limit:
self.checkvar.set(not self.checkvar.get())
else:
print("Normal behavior")
if __name__ == "__main__":
window = tk.Tk()
app = App(window)
window.mainloop()
How would that be done?
I have been unable to find it on here or with Google.
#Refrences
from tkinter import *
class Interface:
def __init__(self,stage):
topLeft = Frame(stage,bg='black')
test = Entry(topLeft,bg='#2a2a2a',fg='white',insertontime=0)
test.config(insertbackground='white', exportselection=0)
test.grid()
topLeft.grid(row=0,column=0)
def launch():
window = Tk()
lobby = Interface(window)
window.mainloop()
launch()
I assume you wish for users to not be able to edit the Entry box? If so, you must use the config parameter of (state="disabled"), eg.
test.config(insertbackground='white', exportselection=0, state="disabled")
be wary though, I could not find a way to keep the background of your entry box black. Hope this helps
You can set the text highlight color to match the background of entry widget. Note that the text in the widget can still be selected but the user will not see it visually which will give the illusion that selection is disabled.
test = Entry(topLeft,bg='#2a2a2a',fg='white',insertontime=0)
test.configure(selectbackground=test.cget('bg'), inactiveselectbackground=test.cget('bg'))
When we select a text we tkinter trigger(fire) 3 events, which are ButtonPress, Motion and ButtonRelease all these 3 events call a event handler fucntion.
The function run select_range(start, end) method which highlight selected text.
To disable this we have to hanlde ButtonPress and Motion events together and call select_clear method on the widget.
If you handle the events and call select_clear method it work but not properly, the text will be highlighted and when another event occured highligh color will be cleared.
This happend because of execution order of events. That's mean you have to tell to tk handle your event after default event handler. We can change order of events execution with bindtags and bin_class methods
example:
from tkinter import *
from tkinter import ttk
def on_select(event):
event.widget.select_clear() # clear selected text.
root = Tk()
name_entry = ttk.Entry(root)
name_entry.pack()
# with PostEvent tag, on_select the event hanlde after default event handler
name_entry.bindtags((str(name_entry), "TEntry", "PostEvent", ".", "all"))
name_entry.bind_class("PostEvent", "<ButtonPress-1><Motion>", on_select)
root.mainloop()
A useful solution to Tkinter not having tons of built-in widgets (like, say, JavaFX does), is that you can easily make your own if you don't mind them being not-quite what you wanted :) Here's a rough-around-the-edges example of using a canvas to emulate an entry field that can't be selected. All I've given is the insert functionality (and delete too, I suppose, if you were clever about it), but you may want more. (Another plus is that, because it's a canvas item, you can do nifty formatting with it).
Am I right that by a not-selectable entry widget, you mean a canvas with text written on it? If you want to disable text-highlighting in ALL widgets, including the top-level frame, you could highjack the Button-1 event, deselect everything, and then consume the event whenever text is selected.
from tkinter import *
class NewEntry(Canvas):
def __init__( self, master, width, height ):
Canvas.__init__( self, master, width=width,height=height )
self.width = width
self.height = height
self.text = ''
def insert( self, index, text ):
self.text =''.join([self.text[i] for i in range(index)] + [text] + [self.text[i] for i in range(index,len(self.text))])
self.delete(ALL)
self.create_text(self.width//2,self.height//2,text=self.text)
root = Tk()
entry = NewEntry(root,100,100)
entry.insert(0,'hello world')
entry.insert(5,'world')
entry.pack()
root.mainloop()