3
如何將tkinter文本小部件的一部分標記爲只讀?也就是說,我希望只能在小部件的某些部分進行編輯。例如,我想只在提示後才能編輯,但不能在之前模擬控制檯。如何將文本小部件的一部分標記爲只讀?
如何將tkinter文本小部件的一部分標記爲只讀?也就是說,我希望只能在小部件的某些部分進行編輯。例如,我想只在提示後才能編輯,但不能在之前模擬控制檯。如何將文本小部件的一部分標記爲只讀?
最強大的解決方案是攔截低級插入和刪除命令,並將邏輯放在那裏以防止基於某種標準的插入和刪除。例如,您可以禁止在標籤爲「只讀」的任何文本範圍內進行編輯。
下面是這種技術的一個例子。它利用了所有插入和刪除操作最終都會調用底層tk widget命令的insert
或delete
子命令以及可以用Tcl proc替換widget命令的事實。
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's 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()