2012-10-25 102 views
5

我目前正在编写一个配色方案编辑器。对于该方案的预览,我使用了一个文本小部件,在其中插入带有相应颜色标签(我通过编程生成)的文本。Python 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

好的,在 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

标签是特定于文本的项目,并且没有标签类别。它们绝对不是BaseWidget的子类。例如,对于Canvas特定的项目(例如弧和线)也是如此。很好的解决方法。 –

2

感谢您发布此问题并提供解决方案。我无法计算我失去了多少小时,试图修复由此行为所产生的症状。怪异Tk设计决定tag_bindreturn "break"不敏感。

按照你的想法用相同的事件序列tag_bind结合其劫持Text小部件,我有完善的解决方案,现在能够模拟Tk的的其他绑定+回调对的预期return "break"行为。我们的想法是以下(全下方源):

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

这是一个完整的工作示例。我的具体问题是获得某种超文本行为:按住Ctrl键并单击超文本不应将插入点移动到点击的位置。下面的示例显示,在tag_bind中包装的同一个回调中,我们可以通过简单地返回"break"或其他值来传播或不传播到Text小部件。

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() 

有一种简单化我找不到怎么办:简单地从传递给__call__event对象更换包装呼叫TagBindWrapper("<Button-1>", callback_hyper)通过TagBindWrapper(callback_hyper),即获得该事件的信息“序列”字符串("<Button-1>") 。可能吗?

+0

作为上述简化的解决方法,可以编写一个函数:'def tag_bind(textwidget,tagname,event_sequence,callback):textwidget.tag_add(tagname,event_sequence,TagBindWrapper(event_sequence,callback))' tag_bind(文本,“标记”,“<1>”,回调)'。只是避免重复参数。如果文本小部件被作为TagBindWrapper类构造函数'__init__'的参数给出,那么这个技巧也可以消除全局变量 –