How to stop copy, paste, and backspace in text widget in tkinter? - python

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.

Related

Why has the tkinter key-event <Tab> a higher priority than the tkinter key-event <key>?

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()

Bind only significant keys on tk/tkinter

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()

Why is first letter of tkinter text widget not justifying as expected?

I have an issue where I've created a text widget inside a tkinter Frame that is suppose to center the text on the first line when the user types. It does this, except the first letter they type it justify's it to the left then after a second letter is pressed it justify's to the center as expected.
I would like to know how to fix this so the first line stays centered.
Here is the code I've gotten so far:
import Tkinter as tk
def testing(event=None, text=None):
LineNumber = text.index(tk.INSERT) # returns line number
if "1." in LineNumber:
text.tag_add("center", "1.0", "end")
root = tk.Tk()
F1 = tk.Frame(root, bg="blue", width=300, height=300)
F1.pack()
text = tk.Text(F1, padx=5, pady=10, bg="white")
text.tag_configure("center", justify='center')
text.pack(expand=1, fill="both")
text.bind('<Key>', lambda event: testing(None, text))
root.mainloop()
It is because your custom binding fires before tkinter has a chance to insert the character. When you press a key, the first thing that happens is that your custom binding is triggered, before any other bindings. The character has not been inserted yet.
For a full explanation of how key events are processed, see this answer: https://stackoverflow.com/a/11542200/7432
A simple fix is to bind on <KeyRelease> instead of <Key>. Since the default behavior happens on a press (but after your custom behavior), a binding on the release of the key will be guaranteed to run after the character has been inserted into the widget.

WASD input in python

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()

Validation on Tkinter.Text widget?

What are my options for getting validation with the Tkinter.Text widget? I don't require Text's advanced functionality, just its multiline attribute. Unfortunately, it lacks both textvariable and validate commands, so it seems that I cannot attach some kind of callback that checks things every time the text changes. I'd like to avoid having to bind to <KeyRelease>, as that looks to capture ALL keypresses, including the likes of Shift, Ctrl, etc, keys, and would appear to be a bit of a mess to work right.
I basically just need to check if the Text field is blank or not, and enable/disable an "Ok" button as appropriate (i.e., if no text, then the button is disabled).
In lieu of this, has anyone come across a decent subclass of Entry that adds multiline functionality that is written in Python? There is this, which adds textvariable to Text, but it is written in pure TCL, not Python, and seems like it would be difficult to integrate into my existing Python environment.
The binding to the <KeyRelease> button does not need to be messy, you don't have to check the value of the key pressed but fetch the content of the widget. Keep in mind that it always has a '\n' at the end, so when you retrive the contents don't forget to discard it:
content = text.get(1.0, "end-1c")
Then you just need to change the state of the button based on this value:
import Tkinter as tk
def configure_ok_button(event):
content = event.widget.get(1.0, "end-1c")
state = "active" if content else "disabled"
button.configure(state=state)
root = tk.Tk()
text = tk.Text(root)
button = tk.Button(root, text="Ok", state="disabled")
text.bind("<KeyRelease>", configure_ok_button)
text.pack()
button.pack()
root.mainloop()

Categories

Resources