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.
Related
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.
I've recently made a text editor with tkinter for python.
I need a way to disable tab from being able to be used normally, so it doesn't indent.
Does anyone have any idea as to how I would achieve this?
Thank you for your time.
It really depends on exactly what you did. Without more information I'm going to assume that you have a text widget somewhere and that you want to disable tab from indenting there.
Example:
from tkinter import Tk, Text
def no_tab(event):
return 'break'
root = Tk()
text_widget = Text()
text_widget.pack()
text_widget.bind('<Tab>', no_tab)
root.mainloop()
In this example we bind the <Tab> key to the function no_tab. So everytime tab is pressed within the text widget the no_tab function is called. The no_tab function returns the magic string 'break' which means that the action of the key won't be preformed and thus disabling the indentation that the tab key would have created otherwise.
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.
I want to use tkinter text widget as a readonly widget. It should act as a transcript area. My idea is to keep this transcript in a file and whenever the user writes anything, just remove all the contents of the widget, and rewrite it again.
The code will look like:
transcript_entry = SimpleEditor() # SimpleEditor is inherited from ScrolledText
transcript_entry.text.delete("1.0", END)
# this is just a test string, it should be the contents of the transcript file
transcript_entry.text.insert("1.0", "This is test transcript")
transcript_entry.text.bind("<KeyPress>", transcript_entry.readonly)
And readonly function will look like:
def readonly(self, event):
self.text.delete("1.0", END)
# this is just a test string, it should be the contents of the transcript file
self.text.insert("1.0", "This is test transcript")
The bug here is that the last character entered by the user is added to the transcript. I suspect the reason is that the readonly function is called, then the user input is wrote to the widget. How to reverse this order & let the readonly function be called after the user input is wrote to the widget?
Any hints?
The reason that the last character is inserted is because the default bindings (which causes the insert) happens after custom bindings you put on the widget. So your bindings fire first and then the default binding inserts the characters. There are other questions and answers here that discuss this in more depth. For example, see https://stackoverflow.com/a/11542200/
However, there is a better way to accomplish what you are trying to do. If you want to create a readonly text widget, you can set the state attribute to "disabled". This will prevent all inserts and deletes (and means you need to revert the state whenever you want to programmatically enter data).
On some platforms it will seem like you can't highlight and copy text, but that is only because the widget won't by default get focus on a mouse click. By adding a binding to set the focus, the user can highlight and copy text but they won't be able to cut or insert.
Here's an example using python 2.x; for 3.x you just have to change the imports:
import Tkinter as tk
from ScrolledText import ScrolledText
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
t = ScrolledText(self, wrap="word")
t.insert("end", "Hello\nworld")
t.configure(state="disabled")
t.pack(side="top", fill="both", expand=True)
# make sure the widget gets focus when clicked
# on, to enable highlighting and copying to the
# clipboard.
t.bind("<1>", lambda event: t.focus_set())
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Please do not delete and reinsert your text :
It is huge performance issue.
It will remove any tags and marks set on the text
This will be visible to the user, and users don't like flickering interfaces
This is not necessary, Tkinter is customizable enough to just not allow the user change the content.
The best way I found to create a read only Text is to disable all the bindings leading to a text change.
My solution is to create a new Widget binding map containing only "read only commands". Then, just reconfigure your widget to use the new RO binding map instead of the default one :
from Tkinter import *
# This is the list of all default command in the "Text" tag that modify the text
commandsToRemove = (
"<Control-Key-h>",
"<Meta-Key-Delete>",
"<Meta-Key-BackSpace>",
"<Meta-Key-d>",
"<Meta-Key-b>",
"<<Redo>>",
"<<Undo>>",
"<Control-Key-t>",
"<Control-Key-o>",
"<Control-Key-k>",
"<Control-Key-d>",
"<Key>",
"<Key-Insert>",
"<<PasteSelection>>",
"<<Clear>>",
"<<Paste>>",
"<<Cut>>",
"<Key-BackSpace>",
"<Key-Delete>",
"<Key-Return>",
"<Control-Key-i>",
"<Key-Tab>",
"<Shift-Key-Tab>"
)
class ROText(Text):
tagInit = False
def init_tag(self):
"""
Just go through all binding for the Text widget.
If the command is allowed, recopy it in the ROText binding table.
"""
for key in self.bind_class("Text"):
if key not in commandsToRemove:
command = self.bind_class("Text", key)
self.bind_class("ROText", key, command)
ROText.tagInit = True
def __init__(self, *args, **kwords):
Text.__init__(self, *args, **kwords)
if not ROText.tagInit:
self.init_tag()
# Create a new binding table list, replace the default Text binding table by the ROText one
bindTags = tuple(tag if tag!="Text" else "ROText" for tag in self.bindtags())
self.bindtags(bindTags)
text = ROText()
text.insert("1.0", """A long text with several
lines
in it""")
text.pack()
text.mainloop()
Note that just the bindings are changed. All the Text command (as insert, delete, ...) are still usable.
I recently worked a different, slightly simpler solution. Rather than changing all the bindings, one can add a function to delete all input characters as soon as they are written:
def read_only(self, event):
if event.char is not '': # delete only if the key pressed
# corresponds to an actual character
self.text.delete('insert-1c')
and just bind it to any event:
root.bind('<Key>', self.read_only)
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".