I am trying to make the TEXT widget read-only, so the user can view it yet cannot edit it. I saw the state of "readonly" being used in another SO question, however it throws this error at me
_tkinter.TclError: bad state "readonly": must be disabled or normal
My code is below
e = Text(root ,height=10, width=50).config(state="readonly")
e.place(x=1,y=1)
There's no such a possible value "readonly" for the state of a Text widget. You can disable it, setting the state to "disabled" (and you can do it directly in the constructor):
e = Text(root, height=10, width=50, state='disabled') # no need to call config
From the Tk documentation:
If the text is disabled then characters may not be inserted or deleted
and no insertion cursor will be displayed, even if the input focus is
in the widget.
I think you should use a Label, if you want just to show some text, that's why labels exist.
Related
I want to bind self events after Text widget class bindings, in order to change the text of the widget when my binding function is called. My binding, for example self.text.bind("<Key>", self.callback), is called before the content in Text widget changes.
What is happening in your case is that your binding to print the value happens before the class binding, and it's the class binding that actually takes user input and puts it in the widget. There are several ways to solve this problem. You could bind to <KeyRelease> instead of <KeyPress>, or you could use the built-in entry validation features to have your code called on every key press. With that solution you'll be given all the data you need -- the value before the change, the value after the change, the key that was pressed, etc.
Another choice is to change the order in which events are processed. Since your question specifically asked how to change the order, that is what I will address.
Even though a binding appears to be associated with a widget when you do something like entry.bind(...), you're actually assigning a binding to a "bind tag" (or "bindtag"). By default each widget has a bindtag that is the same as the name of the widget. Other bindtags include the class of a widget (for example, "Entry"), the path of the root window (eg: ".") and the special tag "all". Widgets are assigned a set of bindtags which are processed in order when an event is received. The default order goes from most- to least-specific: widget, class, toplevel, all.
There are a couple ways to manipulate the bindtags to get the result you desire. One choice is to rearrange the order of the bindtags. By moving the bindtag that represents the widget to be after the bindtag representing the class, the class will handle the event before passing it on to the specific widget.
Another choice is to add an additional bindtag that is after the class binding, and then put your bindings on this tag rather than on the tag that represents the widget.
Why choose one over the other? By rearranging the order you will affect all bindings on that widget. If you have many bindings and some depend on the order (so that the can, for example, disallow certain keystrokes), changing the order may cause those bindings to stop working.
By introducing a new bindtag, you can choose which bindings happen before class bindings and which happen after.
In the following code I create three entry widgets. The first uses the default set of bindtags (explicitly set in the example, though they are identical to the default). The second changes the order, and the third introduces an additional bindtag. Run the code then press a key while the focus is in each window. Notice that in the first entry widget the binding always seems to be one character behind. Again, this is because the widget binding happens before the class binding puts the character into the widget.
In the second and third examples, the binding happens after the class binding so the function sees the change in the widgets.
import Tkinter
def OnKeyPress(event):
value = event.widget.get()
string="value of %s is '%s'" % (event.widget._name, value)
status.configure(text=string)
root = Tkinter.Tk()
entry1 = Tkinter.Entry(root, name="entry1")
entry2 = Tkinter.Entry(root, name="entry2")
entry3 = Tkinter.Entry(root, name="entry3")
# Three different bindtags. The first is just the default but I'm
# including it for illustrative purposes. The second reverses the
# order of the first two tags. The third introduces a new tag after
# the class tag.
entry1.bindtags(('.entry1', 'Entry', '.', 'all'))
entry2.bindtags(('Entry', '.entry2', '.', 'all'))
entry3.bindtags(('.entry3','Entry','post-class-bindings', '.', 'all'))
btlabel1 = Tkinter.Label(text="bindtags: %s" % " ".join(entry1.bindtags()))
btlabel2 = Tkinter.Label(text="bindtags: %s" % " ".join(entry2.bindtags()))
btlabel3 = Tkinter.Label(text="bindtags: %s" % " ".join(entry3.bindtags()))
status = Tkinter.Label(anchor="w")
entry1.grid(row=0,column=0)
btlabel1.grid(row=0,column=1, padx=10, sticky="w")
entry2.grid(row=1,column=0)
btlabel2.grid(row=1,column=1, padx=10, sticky="w")
entry3.grid(row=2,column=0)
btlabel3.grid(row=2,column=1, padx=10)
status.grid(row=3, columnspan=2, sticky="w")
# normally you bind to the widget; in the third case we're binding
# to the new bindtag we've created
entry1.bind("<KeyPress>", OnKeyPress)
entry2.bind("<KeyPress>", OnKeyPress)
entry3.bind_class("post-class-bindings", "<KeyPress>", OnKeyPress)
root.mainloop()
I create my radio buttons and store them in a list as a class property:
for possible_answer in self.possible_answers:
possible_answer =
R = ttk.Radiobutton(self,
text=possible_answer,
variable=var,
value=possible_answer,
command=lambda: self.set_chosen_answer(var.get()))
self.radio_buttons.append(R)
and when the users selects any of the options, I want the radio buttons to become inactive or disabled. I tried doing this in the following manner:
for radio_button in self.radio_buttons:
radio_button.state = DISABLED
The code runs without problems, the only problem I have is that I am still able to click on other buttons after this - which I do not want.
Those two lines do get called as I have verified with printing each of radio button's state right after setting it and it prints "disabled" for each of them.
What am I doing wrong? I have read through documentation and similar posts but did not find anything useful. Did I misunderstand what the disabled state means? Should I do this differently?
state is not an attribute of the widget object. It is a configuration option, which must be set by the config or configure method:
for radio_button in self.radio_buttons:
radio_button.configure(state = DISABLED)
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)
I'm trying to associate a variable with a Tkinter entry widget, in a way that:
Whenever I change the value (the "content") of the entry, mainly by typing something into it, the variable automatically gets assigned the value of what I've typed. Without me having to push a button "Update value " or something like that first.
Whenever the variable gets changed (by some other part of the programm), I want the entry value displayed to be adjusted automatically. I believe that this could work via the textvariable.
I read the example on http://effbot.org/tkinterbook/entry.htm, but it is not exactly helping me for what I have in mind. I have a feeling that there is a way of ensuring the first condition with using entry's "validate". Any ideas?
I think you want something like this. In the example below, I created a variable myvar and assigned it to be textvariable of both a Label and Entry widgets. This way both are coupled and changes in the Entry widget will reflect automatically in Label.
You can also set trace on variables, e.g. to write to stdout.
from tkinter import *
root = Tk()
root.title("MyApp")
myvar = StringVar()
def mywarWritten(*args):
print "mywarWritten",myvar.get()
myvar.trace("w", mywarWritten)
label = Label(root, textvariable=myvar)
label.pack()
text_entry = Entry(root, textvariable=myvar)
text_entry.pack()
root.mainloop()