Python3-tkinter中的只读文本小部件;跨平台

6 投票
3 回答
3798 浏览
提问于 2025-04-17 04:24

如何禁止最终用户在文本组件中编辑、添加或删除文字?(Python v3.2 和 tkinter)

重点是只禁止用户更改、添加或删除文本,而不影响其他功能。也许叫它“只读文本组件”更合适。

我试过用 .text['state'] = 'disabled',在Windows上效果差不多可以,但还是有点问题(用户仍然可以选择/复制文本,选中的部分会高亮,翻页和上下键也能用。唯一的问题是光标变得不可见了。)

但是在Mac上就完全不行了。没有高亮,没有选择/复制……真让人头疼。

因为Tkinter在Python中几乎没有文档,我搜索了一些TCL的建议,想要创建一个新类来禁止插入删除功能。

所以,我尝试了以下代码:

class roText(tk.Text):
    def insert(self,*args,**kwargs):
        print(" Hey  - Im inside roText.insert")
        pass
    def delete(self,*args,**twargs):
        pass    
    def pInsert(self,*args,**twargs):
        super().insert(*args,**twargs)

可惜这并没有正确工作。显然,tkinter在用户输入或删除文本时并不使用这些插入和删除功能。也许那些TCL的插入/删除是别的东西,我在从TCL和斯瓦希里语翻译时搞错了。那tkinter.Text在用户编辑文本时用的是哪些功能呢?希望它们不是内部的……

那么,有没有办法修改文本组件,只禁止用户编辑呢?有没有办法在不深入内部覆盖Tkinter代码的情况下做到这一点,这样在Tkinter更新时就不会出问题?

看着Idle的命令行窗口,我发现他们成功地禁止了编辑(除了最后一行)。所以肯定有办法。但那是什么呢?成本高吗?

3 个回答

-1

@BryanOakley 我花了一段时间来测试你的建议,因为我没有Mac。可惜的是,Python在Mac上的实现有些问题。

我添加了焦点,也就是我在创建窗口并插入文本后调用的禁用功能,现在首先调用:

self.txt['state'] = 'disabled'

然后调用

self.txt.focus_set()

这就是我认为你建议的做法。

效果“有点”不错。也就是说,当我选择文本(点击并拖动或双击)时,突出显示大部分时间都能正常工作。Python可能有一些糟糕的内存引用或其他bug:有时一开始突出显示不起作用,但在同一个窗口里多点击几下后又开始工作。有时程序启动时就能正常工作。有时按Shift加右箭头键选择文本可以,但用鼠标选择却不行,然后又开始正常工作。或者在一个窗口里工作得很好,但在另一个窗口里却不行(虽然这两个窗口是同一类的),然后又在所有窗口里都开始正常工作……等等。

好的一点是,添加焦点并没有对Windows造成不良影响(也就是说,一切都和没有焦点时一样正常)。我想在这个时候我只能希望未来的Python Mac版本能修复这些bug……

顺便说一下,似乎Mac在Python的支持上有点被忽视。它的实现比Windows要丑陋得多。我是说字体看起来更差,按钮等等……或者这可能是因为不同的屏幕分辨率和Python的移植没有很好地考虑到这些。不太确定。

总之,谢谢你的帮助和建议,让我在Mac上使用焦点。

2

在Mac上,disabled状态似乎不起作用的原因是,它关闭了让控件获得焦点的绑定。没有焦点的话,Mac上就不会显示高亮。如果你把状态设置为disabled,但同时给<ButtonPress-1>绑定一个操作,让禁用的文本控件获得焦点,那么你就可以选择和复制文本,并且高亮会显示出来。

至于光标消失的问题……可以说,这其实是正常现象。光标的作用是告诉用户“文本会在这里插入”。但因为不会插入任何文本,光标的存在反而会让用户感到困惑。如果这真的很重要,你可以在用户点击的地方插入一个小图像来模拟光标。

关于控件是否真的使用insertdelete方法的问题:实际上,控件底层的方法就是默认绑定所使用的,所以在子类中重写这些方法是没有效果的。要让它生效,你需要重新设置所有的默认绑定。这是可以做到的,但工作量很大。

不幸的是,这是Tcl编程特别强大的地方,因为你可以简单地禁用控件的insertdelete命令。当然,你在Tkinter中也可以直接做到这一点,因为最终它也是运行tcl代码来完成所有操作,但这就需要写一些tcl代码,从Python程序员的角度来看,这并不是一个很好的解决方案。

我认为最好的办法是使用禁用状态,然后添加足够的绑定来实现你想要的功能。

下面是一个简单的例子,通过鼠标点击显式设置焦点。使用这段代码,我可以点击并拖动选择一个区域,或者双击或三击选择单词和行:

import Tkinter as tk

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.text = tk.Text(width=40, height=20)
        self.text.bind("<1>", self.set_focus)
        self.text.insert("end", "\n".join(dir(tk.Tk)))
        self.text.configure(state="disabled")
        self.text.pack(fill="both", expand=True)

    def set_focus(self, event):
        '''Explicitly set focus, so user can select and copy text'''
        self.text.focus_set()

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()
3

抱歉打扰了一个旧问题,但我也在寻找这个问题的答案,最后找到了一个解决办法。这个解决办法很简单,就是在文本小部件获得焦点时,重写它的按键绑定。我是在这里找到的。

要重写一个小部件的绑定,可以使用一个叫做 bind 的函数,你需要传入一个字符串,表示要重写的内容,以及你想要调用的新函数。

    self.txtBox.bind("<Key>", self.empty)

在类的其他地方,你需要定义一个函数来处理这个事件。

    def empty(self, event):
        return "break"

通过返回字符串 "break",事件处理程序就知道在你的函数执行完后要停止,而不是继续执行默认的操作。

希望这能解答你的问题。谢谢。

撰写回答