How can you mark part of a text widget as read-only?

How can I mark part of a tkinter text widget as read-only? That is, I want to be able to allow editing only in certain parts of the widget. For example, I want to enable editing only after a prompt, but not earlier, to simulate a console.

+3
source share
1 answer

The most bulletproof solution is to intercept low-level insert and delete commands and put logic in there to prevent inserts and deletes based on some criteria. For example, you can disable editing in any range of text that has a readonly tag.

Here is an example of this technique. It exploits the fact that all insertions and deletions ultimately invoke the insert or delete subcommand of the tk widgetโ€™s main command and the fact that the widget command can be replaced with Tcl processing.

 try: # python 2.x import Tkinter as tk except ImportError: # python 3.x import tkinter as tk class Example(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) text = ReadonlyText(self) sb = tk.Scrollbar(self, orient="vertical", command=text.yview) text.configure(yscrollcommand=sb.set) sb.pack(side="left", fill="y") text.pack(side="right", fill="both", expand=True) text.insert("end", "You can edit this line\n") text.insert("end", "You cannot edit or delete this line\n", "readonly") text.insert("end", "You can edit this, too.") text.tag_configure("readonly", foreground="darkgray") class ReadonlyText(tk.Text): '''A text widget that doesn't permit inserts and deletes in regions tagged with "readonly"''' def __init__(self, *args, **kwargs): tk.Text.__init__(self, *args, **kwargs) # this code creates a proxy that will intercept # each actual insert and delete. self.tk.eval(WIDGET_PROXY) # this code replaces the low level tk widget # with the proxy widget = str(self) self.tk.eval(''' rename {widget} _{widget} interp alias {{}} ::{widget} {{}} widget_proxy _{widget} '''.format(widget=widget)) WIDGET_PROXY = ''' if {[llength [info commands widget_proxy]] == 0} { # Tcl code to implement a text widget proxy that disallows # insertions and deletions in regions marked with "readonly" proc widget_proxy {actual_widget args} { set command [lindex $args 0] set args [lrange $args 1 end] if {$command == "insert"} { set index [lindex $args 0] if [_is_readonly $actual_widget $index "$index+1c"] { bell return "" } } if {$command == "delete"} { foreach {index1 index2} $args { if {[_is_readonly $actual_widget $index1 $index2]} { bell return "" } } } # if we passed the previous checks, allow the command to # run normally $actual_widget $command {*}$args } proc _is_readonly {widget index1 index2} { # return true if any text in the range between # index1 and index2 has the tag "readonly" set result false if {$index2 eq ""} {set index2 "$index1+1c"} # see if "readonly" is applied to any character in the # range. There probably a more efficient way to do this, but # this is Good Enough for {set index $index1} \ {[$widget compare $index < $index2]} \ {set index [$widget index "$index+1c"]} { if {"readonly" in [$widget tag names $index]} { set result true break } } return $result } } ''' def main(): root = tk.Tk() Example(root).pack(fill="both", expand=True) root.mainloop() if __name__ == "__main__": main() 
+4
source

All Articles