简体   繁体   English

Readonly tkinter文本小部件

[英]Readonly tkinter text widget

I want to use tkinter text widget as a readonly widget. 我想使用tkinter text widget作为readonly小部件。 It should act as a transcript area. 它应该作为transcript区域。 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. 我的想法是将此脚本保存在一个file ,每当用户写入任何内容时,只需删除该小部件的所有内容,然后重新重写即可。

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: readonly函数看起来像:

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. 我怀疑原因是调用了readonly函数, then将用户输入写入窗口小部件。 How to reverse this order & let the readonly function be called after the user input is wrote to the widget? 如何反转此顺序并after将用户输入写入窗口小部件after调用readonly函数?

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/ 例如,请参阅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" . 如果要创建只读文本窗口小部件,可以将state属性设置为"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; 这是使用python 2.x的一个例子; for 3.x you just have to change the imports: 对于3.x,您只需更改导入:

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. 这不是必需的,Tkinter可以自定义,只是不允许用户更改内容。

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". 我的解决方案是创建一个只包含“只读命令”的新Widget绑定映射。 Then, just reconfigure your widget to use the new RO binding map instead of the default one : 然后,只需重新配置您的小部件以使用新的RO绑定映射而不是默认的:

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. 所有Text命令(如insert,delete,...)仍然可用。

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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM