Tkinter keyrelease event inserting new line while keypress doesn't - python

In the below code, If I use KeyRelease to bind events, then on hitting return key results in an unwanted new line in the text widget. However If I use KeyPress to bind events, then no new line is inserted.
Can some one please explain the differnce between the two and why I am observing such an behavior. Any pointers to refernce material would be appreciated.
from Tkinter import *
def getCommand(*args):
global text
text.insert(END, "\n")
text.insert(END, "command>")
return 'break'
def handle_keyrelease(event):
if event.keysym == "Return":
getCommand()
return 'break'
root = Tk()
text = Text(root)
text.pack()
text.insert(END,"command>")
text.focus()
text.bind("<KeyRelease>", handle_keyrelease) # Change the event handler to see the difference
root.mainloop()

When hitting and releasing a key on a text widget, <KeyPress> is raised, then the text widget is updated, then <KeyRelease> is raised. You can easily verify this yourself, using a little demo program handling both events.
When binding to <KeyPress>, you can prevent the event from being propagated to other handlers (and thus the one that updates the text widget) by returning the string"break" from your event handler function.
If you bind to <KeyRelease>, it's already too late to prevent the insertion of the newline, since at that point in time the text has already been updated.
Note that, in your example, you could also handle <KeyPress-Return> or simply <Return> instead of checking for event.keysym == "Return".

Related

tkinter button only visible when a word is in a textbox python

I'm trying to create a button in Tkinter that only appears (becoming visible) when it finds the word "hello" in a textbox.
I could imagine using Threads, and Global variables but i do not know how to code it,
I've imagined something like :
import Tkinter as *
from threading import Thread
window = Tk()
active = True
def check_for_word():
global active
textbox1 = textbox.get("1,0", "end")
while active == True:
if "hello" in textbox1:
button.pack()
else:
button.pack_forget()
save_button = Button(window)
textbox = scrolledtext.ScrolledText(window)
textbox.pack()
threading = Thread (target=check_for_word)
threading.start()
window.mainloop()
this is something I would suspect to work but ends up not, the button either doesn't show at all like the code isn't even running, or the thread doesn't work properly. So am I doing something wrong, if so, can you help me, please? Thank you!
You don't need to make use of threads to do this, you can use tkinter event bindings instead.
def check_for_word():
if "hello" in textbox.get("1.0", "end"):
save_button.pack()
else:
save_button.pack_forget()
save_button = Button(window)
textbox = scrolledtext.ScrolledText(window)
textbox.bind("<KeyRelease>", lambda event:check_for_word())
textbox.pack()
To make a binding, you use widget.bind. In this case, the widget is textbox and it's binding to <KeyRelease>, which is when the user releases a key. It then calls check_for_word when a key is released. (The lambda part is to ignore the event parameter). check_for_word then does what it did before.
You have to put the textbox1 assignment inside the while loop and before the if condition, otherwise it will check the value one time before entering the loop and will keep checking always the same value.
I also want to point out that the in operator is case sensitive and return True if it find even just a substring inside the variable you are checking and not just the precise single word (but I'm not shure if this is intensional).
For the while loop you don't necessarily need a global variable, you could just use while True: if you want it to continuously check the condition (if you want the button to disappear after the user cancel the word).

Tkinter ScrolledText always goes to second line when attached to <Return> and having content deleted

So suppose I have a code like this.
def test(widget):
widget.widget.delete("1.0", tkinter.END)
t=tkinter.Tk()
b=tkinter.scrolledtext.ScrolledText()
b.bind("<Return>", test)
b.pack()
t.mainloop()
This will delete the textbox but the cursor will always be on the second line (and after that it won't go to third line for example, so it's not like every time a new line is created, it will always be at the second line).
It sounds like first this event is triggered, then the key is processed, so when the textbox is deleted, the Enter key makes a second line.
This seems to only happen with this event.
Events bound to a widget are handled before the default bindings. Therefore, your code runs before tkinter inserts the newline.
If you want to prevent the default behavior, return the literal string "break" from the function.
def test(widget):
widget.widget.delete("1.0", tkinter.END)
return "break"
For a detailed explanation of how events are processed see this answer
Returning the string "Break" prevents python from handling the key event itself, so this fixes the problem.

Can you bind a button as well as a key press to a function in tkinter?

I am trying to make a small program where a user can click a button and it prints text, but if the user clicks the space bar it will also print the text. I know how to connect a button and a function using "command=....." not sure how to bind keys though. any help would be appreciated.
import tkinter as tk
root = tk.Tk()
def yes():
print("yes")
okbtn = tk.Button(text='OK', font=("Helvetica",50), bg = "red", command=yes, width=10, height=3)
okbtn.pack()
root.mainloop()
You can bind functions to keys, using the .bind method which takes a key (or combination of modifiers and keys) and a function to execute
So in your example, adding the line below, would bind the yes function to the spacebar
root.bind('<space>', lambda event: yes())
Note that any bound function will take a tkinter event as argument (which contains mouse coordinates, time of execution, reference to master widget, and more) - I have ignored the event argument in this case, by making a dummy lambda. However, it can often be useful
Here is an example of a function where the event is actually being used (prints the mouse position at the time where the function was called)
def motion(event):
print("Mouse position: (%s %s)" % (event.x, event.y))
You can check out this link for more information about even binding in tkinter https://www.pythontutorial.net/tkinter/tkinter-event-binding/

How to remove blank line at end of tkinter textbox?

When I run this code, it adds a blank line to the bottom of my textbox. I want my textbox to just show NEW TEXT after return is pressed, and I cannot figure out how to accomplish it. I searched on this site and could not find an answer, so apologies in advance if this is a duplicate question.
import tkinter as tk
def update(event):
entry.delete('1.0', tk.END)
entry.insert('1.0', 'NEW TEXT')
if entry.get('end-1c', 'end') == '\n':
entry.delete('end-1c', 'end')
root = tk.Tk()
root.config(bg='snow3')
root.geometry('200x200')
entry = tk.Text(root, height=1.3, width=12)
entry.bind('<Return>', update)
entry.configure(wrap='none')
entry.pack()
root.mainloop()
You cannot remove the final newline. That is baked into the text widget.
However, the problem is not the built-in newline. The problem is that the return key is inserting a newline after your function runs. That is because your binding happens before the built-in bindings, which is an important part of the way tkinter handles key bindings (and is a brilliant design!).
In short, this is what happens:
user presses the return key
your function is called from a widget-specific binding
your function deletes everything
your function inserts new text
your function returns
the Text widget class binding inserts a newline
To prevent that last step from happening, you need to return the string "break" from your function. That will prevent the default behavior defined by the Text widget from happening. The end result is that the changes you made to the widget in your function are the only changes made to the widget when the user presses the return key.
def update(event):
entry.delete('1.0', tk.END)
entry.insert('1.0', 'NEW TEXT')
return "break"
For a slightly longer explanation of why this happens, see this answer to the question Basic query regarding bindtags in tkinter.

How to freeze use of key tab in tkinter python

I am trying to make a simple application that scrambles keyboard letters while typing. I am using python along with tkinter. I have a text widget and i need to disable the key tab in my application. I tried it using following code.
text.bind("<Tab>", no_op)
Here no_op is the function given below:
def no_op(self):
return "break"
But I am not getting the expected result. I am posting the whole code below.
import Tkinter as tk
def onKeyPress(event):
first=event.char
second=ord(first)
if second==32:
second=chr(second)
text.insert('end', '%s' % (second ))
elif second==8:
length = len(text.get(1.0, 'end'))
contents = text.get(1.0, 'end')
newcon = contents[:-2]
#text.insert('end', '%s' % (length ))
text.delete(1.0,'end')
text.insert('end', '%s' % (newcon ))
elif(second>=65 and second<=90 or second>=97 and second<=122):
second=chr(second+3)
text.insert('end', '%s' % (second ))
def no_op(self):
return "break"
root = tk.Tk()
root.config(cursor='none')
#root.attributes('-zoomed',True)
text = tk.Text(root, background='white', foreground='black', font=('Comic Sans MS', 12))
text.pack(expand=True,)
text.bind("<Tab>", no_op)
text.bind("<Button-1>", no_op)
text.config(cursor="none")
root.bind('<KeyPress>', onKeyPress)
root.mainloop()
(Note: The problem is that when tab is pressed when some other widget has focus, the text cursor comes in the text area. Then, if I press any letter say,'a' both 'a' and 'd' is inserted to text field. I want to fix that.)
Your problem isn't with the tab key, your problem is focus management. You've made your code work only if the text widget never gets keyboard focus. There are at least two solutions:
continue down the path of preventing the user from focusing on the text widget
allow focus on the text widget, and adjust your bindings accordingly
For the first, instead of trying to change the behavior of the tab (and also the shift-tab), you can merely move the focus whenever the text widget gets it. For example:
text.bind("<FocusIn>", lambda event: root.focus_set())
That will prevent the text widget from ever getting focus, and your code should work.
Another solution is to modify your <KeyPress> binding to be on the text widget rather than the root widget, and then simply reject all handling of key presses. That means to do text.bind('<KeyPress>', ...) rather than root.bind.... You then need to modify onKeyPress to return "break" to prevent the default text widget bindings from happening.

Categories