Python tkinter:在文本小部件标签中停止事件传播

时间:2012-10-25 10:39:28

标签: python event-handling tkinter

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

我想要的是以下行为:

  • 点击文本小部件上没有文字的任何地方:更改背景颜色
  • 点击带有标签的文字:更改标签对应的前景色

现在这是我的问题:

当我点击标记文本时,会调用标记的回调。到现在为止还挺好。但是,也调用了文本小部件的回调,尽管我在标签回调方法中返回“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。

2 个答案:

答案 0 :(得分: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()

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

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

答案 1 :(得分:2)

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

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

  1. 围绕所希望的callback创建一个包装器,即一个可调用的类实例
  2. 调用类实例时,运行"break"并检查其结果。
    • 如果结果为bind,则暂时劫持事件传播:Text tag_bind窗口小部件到绑定到unbind的同一事件,并返回空回调。然后,在空闲时间"break"
    • 之后
    • 如果结果不是Text:什么也不做,事件将自动传播到tag_bind
  3. 这是一个完整的工作示例。我的具体问题是获得某种超文本行为:按住Ctrl键单击超文本不应将插入点移动到点击的位置。下面的示例显示,在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("<Button-1>", callback_hyper)

    有一个简化,我找不到怎么做:用TagBindWrapper(callback_hyper)替换包装器调用"<Button-1>",即获取事件'sequence'字符串的信息(event)只需从传递给__call__的{​​{1}}对象中获取。有可能吗?