Readonly tkinter text widget
Solution 1
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()
Solution 2
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.
yassin
Updated on June 19, 2022Comments
-
yassin almost 2 years
I want to use
tkinter text widget
as areadonly
widget. It should act as atranscript
area. My idea is to keep this transcript in afile
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 calledafter
the user input is wrote to the widget?Any hints?
-
Russell Smith about 10 yearsWhy not just set the widget's
state
attribute to"disabled"
? -
mgautierfr about 10 yearsThe "disable " state disable a lot of stuff. Every operations that change the content of the text are disable. insert/delete commands will not work anymore. In the same way, the text will not answer to "<<Copy>>" binding
-
Russell Smith almost 9 yearsThis won't work if you past more than one character into the widget. And, of course, it doesn't prevent you from deleting characters. Also, it will throw an error if anything other than a text widget has focus.
-
glep almost 9 yearsTrue, but still a useful solution if all you require is the ability to capture key events without writing the captured characters to the widgets.