【问题标题】:tkinter: stopping event propagation in text widgets tagstkinter:停止文本小部件标签中的事件传播
【发布时间】:2022-03-08 06:09:32
【问题描述】:

我目前正在编写一个配色方案编辑器。对于方案的预览,我使用了一个文本小部件,在其中插入带有相应颜色标签的文本(我以编程方式生成)。

我想要的是以下行为:

  • 单击文本小部件上没有文本的任意位置:更改背景颜色
  • 点击插入带有标签的文本:更改标签对应的前景色

这是我的问题:

当我点击一个标记的文本时,会调用标记的回调。到目前为止,一切都很好。但是,文本小部件的回调也被调用,尽管我在标签回调方法中返回“break”(这应该停止进一步的事件处理)。 我怎样才能阻止这种情况?

为了说明这个具体问题,我编写了这个工作示例(适用于 Python 2 和 3):

#!/usr/bin/env python

try:
    from Tkinter import *
    from tkMessageBox import showinfo
except ImportError:
    from tkinter import *
    from tkinter.messagebox import showinfo

def on_click(event, widget_origin='?'):
    showinfo('Click', '"{}"" clicked'.format(widget_origin))
    return 'break'

root = Tk()
text = Text(root)
text.pack()
text.insert(CURRENT, 'Some untagged text...\n')
text.bind('<Button-1>', lambda e, w='textwidget': on_click(e, w))
for i in range(5):
    tag_name = 'tag_{}'.format(i)
    text.tag_config(tag_name)
    text.tag_bind(tag_name, '<Button-1>',
        lambda e, w=tag_name: on_click(e, w))
    text.insert(CURRENT, tag_name + ' ', tag_name)
root.mainloop()

感谢任何帮助!

编辑:也试过 Python 2。

【问题讨论】:

    标签: python event-handling tkinter


    【解决方案1】:

    感谢您发布此问题并提供解决方案。我无法计算为了修复这种行为造成的症状而损失了多少小时。奇怪的Tk 设计决定tag_bindreturn "break" 不敏感。

    按照您的想法,通过将Text 小部件绑定到与tag_bind 相同的事件序列来劫持它,我改进了解决方案,现在可以模拟Tk 的其他绑定的预期return "break" 行为+回调对。想法如下(完整来源如下):

    1. 围绕希望的callback 创建一个包装器,即一个可调用的类实例
    2. 调用类实例时,运行callback 并检查其结果。
      • 如果结果为"break",则暂时劫持事件传播:bindText 小部件到绑定到tag_bind 的同一事件,回调为空。然后,在空闲时间之后,unbind
      • 如果结果不是"break":什么都不做,事件会自动传播到Text

    这是一个完整的工作示例。我的具体问题是获得某种 hyper text 行为:ctrl-clicking 超文本不应该将插入点移动到点击的位置。下面的示例显示,在包装在 tag_bind 中的同一回调中,我们可以将事件传播或不传播到 Text 小部件,只需返回 "break" 或其他值。

    try:
        # Python2
        import Tkinter as tk
    except ImportError:
        # Python3
        import tkinter as tk
    
    class TagBindWrapper:
        def __init__(self, sequence, callback):
            self.callback=callback
            self.sequence=sequence
    
        def __call__(self, event):
            if "break" == self.callback(event):
                global text
                self.bind_id=text.bind(self.sequence, self.break_tag_bind)
                return "break"
            else:
                return
    
        def break_tag_bind(self, event):
            global text
            # text.after(100, text.unbind(self.sequence, self.bind_id))
            text.after_idle(text.unbind, self.sequence, self.bind_id)
            return "break"
    
    
    
    def callback_normal(event):
        print "normal text clicked"
        return "break"
    
    def callback_hyper(event):
        print "hyper text clicked"
        if event.state & 0x004: # ctrl modifier
            return "break" # will not be passed on to text widget
        else:
            return # will be passed on to text widget
    
    # setup Text widget
    root=tk.Tk()
    text = tk.Text(root)
    text.pack()
    text.tag_config("normal", foreground="black")
    text.tag_config("hyper", foreground="blue")
    text.tag_bind("hyper", "<Button-1>", TagBindWrapper("<Button-1>", callback_hyper))
    text.tag_bind("normal", "<Button-1>", callback_normal)
    
    # write some normal text and some hyper text
    text.insert(tk.END, "normal text, ", "normal")
    text.insert(tk.END, "hyper text (try normal-click and ctrl-click).", "hyper")
    
    root.mainloop()
    

    我找不到一个简化方法:将包装调用TagBindWrapper("&lt;Button-1&gt;", callback_hyper) 替换为TagBindWrapper(callback_hyper),即仅从event 获取事件“序列”字符串("&lt;Button-1&gt;")的信息对象传递给__call__。有可能吗?

    【讨论】:

    • 作为上述简化的一种解决方法,可以编写一个函数:def tag_bind(textwidget, tagname, event_sequence, callback): textwidget.tag_add(tagname, event_sequence, TagBindWrapper(event_sequence, callback)),然后编写tag_bind(text, "tag", "&lt;1&gt;", callback)。只是避免重复论点。如果将文本小部件作为 TagBindWrapper 类构造函数 __init__ 的参数给出,该技巧也可以消除全局变量
    【解决方案2】:

    好的,在 tkint 稍作修改后,我找到了一个可行的解决方案。我认为问题在于标签可能不是 BaseWidget 的子类。

    我的解决方法:

    • 为标签单独回调;在那里设置一个变量来跟踪哪个标签被点击了
    • 让文本小部件的事件处理程序根据此变量的内容决定要做什么

    代码中的解决方法(抱歉在这里使用global,但我只是修改了我的问题简单示例......):

    #!/usr/bin/env python
    
    try:
        from Tkinter import *
        from tkMessageBox import showinfo
    except ImportError:
        from tkinter import *
        from tkinter.messagebox import showinfo
    
    tag_to_handle = ''
    
    def on_click(event, widget_origin='?'):
        global tag_to_handle
        if tag_to_handle:
            showinfo('Click', '"{}" clicked'.format(tag_to_handle))
            tag_to_handle = ''
        else:
            showinfo('Click', '"{}  " clicked'.format(widget_origin))
    
    def on_tag_click(event, tag):
        global tag_to_handle
        tag_to_handle = tag
    
    root = Tk()
    text = Text(root)
    text.pack()
    text.insert(CURRENT, 'Some untagged text...\n')
    text.bind('<Button-1>', lambda e, w='textwidget': on_click(e, w))
    for i in range(5):
        tag_name = 'tag_{}'.format(i)
        text.tag_config(tag_name)
        text.tag_bind(tag_name, '<Button-1>',
            lambda e, w=tag_name: on_tag_click(e, w))
        text.insert(CURRENT, tag_name + ' ', tag_name)
    root.mainloop()
    

    我希望这对遇到同样问题的人有所帮助。

    当然,我仍然愿意接受更好的解决方案!

    【讨论】:

    • 标签是文本特定的项目,没有标签类。它们绝对不是 BaseWidget 的子类。例如,对于 Canvas 特定的项目(如弧线和线条)也是如此。很好的解决方法。
    猜你喜欢
    • 1970-01-01
    • 2016-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-05
    相关资源
    最近更新 更多