Как вы можете пометить часть текстового виджета как только для чтения?

Как пометить часть текстового виджета tkinter как только для чтения? То есть я хочу иметь возможность разрешать редактирование только в определенных частях виджета. Например, я хочу разрешить редактирование только после приглашения, но не раньше, для имитации консоли.

1 ответ

Решение

Наиболее пуленепробиваемое решение - перехватывать низкоуровневые команды вставки и удаления и вводить в них логику для предотвращения вставок и удалений на основе каких-либо критериев. Например, вы можете запретить редактирование в любом диапазоне текста с тегом "только для чтения".

Вот пример этой техники. Он использует тот факт, что все вставки и удаления в конечном итоге вызывают insert или же delete подкоманда базовой команды tk widget и тот факт, что команду widget можно заменить на процедуру Tcl.

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()
Другие вопросы по тегам