Say if i wish to bind my space bar and the key "w" to any random function in my code, how would i do this? Should i be using if "w" and if "" then perform or can you bind multiple keys to one function?
#if statement way. idk how to do this tho
if "w" == Pressed:
if "<space>" == Pressed:
#perform function
#or
self._master.bind("<space>", "w", lambda e: function)
In tkinter you can put string with all keys "<space>w" and you can do: press space, (release space or not), press w and it will run function.
import tkinter as tk
def test(event):
print('test')
root = tk.Tk()
root.bind('<space>w', test)
root.mainloop()
By just adding the and, I was able to bind this Text entry box to both the tab and enter keys. I suppose if you wanted each key to run a different function, you could.
Text.bind("<Tab>", AddText) and Text.bind("<Return>", AddText)
Related
I'm trying to overwrite an existing keyboard function on the enter key with a custom hotkey. The problem is, I cannot stop the default action from occurring also. Worse yet, it occurs after the custom action, so I don't have the chance to retroactively correct it as well.
Here are the relevant parts of the code:
from tkinter import *
from tkinter import ttk
from keyboard import *
root = Tk()
root.geometry('400x300')
'''This is the problematic function called by the hotkey'''
def entr_key(text):
'''Enter key results in printing of the line to the right of cursor'''
curs_pos = text.index(INSERT)
right_hand = text.get(curs_pos,END)
right_hand = right_hand.split('\n')[:1] #lines -> strs in a list, selects the first
print (right_hand)
return
'''THIS IS THE MAIN WIDGET FOR THIS FRAME'''
text_view = ttk.Frame(root, padding=10)
text_view.grid()
text_box = Text(text_view, width=45, wrap=WORD)
text_box.grid(column=0, row=1)
text_box.insert('1.0', 'This is a Text widget demo,\nThis text is aimed at testing the enter key function')
add_hotkey("enter", lambda: entr_key(text_box))
root.mainloop()
(I've changed up some variable names to be more understandable, apologies if I missed any but it's not the source of the problem!)
I've also (unsuccesfully) tried other ways of doing this, eg:
while True:
if is_pressed('enter'):
entr_key(text_box)
'''AND ALSO'''
on_press_key("enter", lambda x=None: entr_key(text_box))
Just to be clear, I don't want the default action of enter key moving the text to a new line.
I need either a way to "break" the key event so that only the custom action takes place, or a way for the custom action to occur after default action, so I can retroactively edit it out
EDIT!!!
I've found a workaround: at the start of entr_key() I call time.sleep(0.01). During this, the default action of the enter key occurs first, and I can retroactively edit it out when the custom function resumes. The delay is slight enough to not be noticeable at all.
But still, if anyone knows how to prevent the default action from occurring completely, I would really appreciate it.
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'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.
I'm developing a GUI using python 3.6 but I need the user to double-click on a tkinter Entry widget to allow input (to prevent modification of fields by accident), instead of just press any key to input text.
Is there any way to prevent the user input by keystroke until double
click on Entry?
I've tried by first to override events with the methods below to unbind keystrokes but none of them worked, so re-definition of new bind (double click) is not yet implemented.
Entry.unbind_all('<Key>')
Entry.unbind_all('<KeyPress>')
Entry.unbind_all('<KeyRelease>')
One simple way of doing preventing the user input by keystrokes until a double-click, is simply manipulating the state of an Entry with double click and focus out events. As in by default every widget is read-only, when double-clicked a single one gets enabled, and when lost focus it's read-only again:
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
def on_double_click(widget):
widget['state'] = 'normal'
def on_lose_focus(widget):
widget['state'] = 'readonly'
def main():
root = tk.Tk()
entries = list()
for i in range(3):
entries.append(tk.Entry(root, state='readonly'))
entries[-1].bind('<Double-Button-1>',
lambda e, w=entries[-1]: on_double_click(w))
entries[-1].bind('<FocusOut>',
lambda e, w=entries[-1]: on_lose_focus(w))
entries[-1].pack()
tk.mainloop()
if __name__ == '__main__':
main()
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.