为什么生成的tkinter按钮-1事件无法识别?

huangapple go评论69阅读模式
英文:

Why is the generated tkinter button-1 event not recognized?

问题

我有2个画布矩形,它们都绑定了Button-1事件。有时候,我希望当一个矩形接收到这个事件时,另一个矩形也能接收到这个事件。因此,我需要通过widget方法"event_generate"来复制Button-1事件。

我的示例代码相当简化,因此你只能按一次左鼠标按钮。当你在红色矩形上按下左鼠标按钮时,会识别此事件,这通过显示消息"button1 red "来证明。因为我为每个矩形添加了第二个绑定,所以也会启动"create_event_for_rectangle"方法,这通过显示消息"Create event for other rectangle"来证明。这个方法还在绿色矩形处生成一个新的Button-1事件。生成的事件的坐标似乎是正确的,因为还会在计算的坐标处创建一个小矩形。现在,在绿色矩形处生成的这个事件应该创建消息"button1 green 45 45",但什么都没有发生。

这是我的代码:

import tkinter as tk

class rectangle():
    def __init__(self, factor, color):
        self.canvas_id = canvas.create_rectangle(factor * 10, factor * 10,
            (factor + 1) * 10, (factor + 1) * 10, fill=color)
        canvas.tag_bind(self.canvas_id, "<Button-1>", self.button1)
        self.color = color

    def button1(self, event):
        print("button1", self.color, event.x, event.y)

    def get_canvas_id(self):
        return self.canvas_id

    def get_center(self):
        coords = canvas.coords(self.canvas_id)
        return (coords[0] + coords[2]) / 2, (coords[1] + coords[3]) / 2

def create_event_for_rectangle(not_clicked):
    print("Create event for other rectangle")
    canvas.tag_unbind(red.get_canvas_id(), "<Button-1>", func_id_red)
    canvas.tag_unbind(green.get_canvas_id(), "<Button-1>", func_id_green)
    not_clicked_center_x, not_clicked_center_y = not_clicked.get_center()
    canvas.event_generate("<Button-1>",
        x=not_clicked_center_x, y=not_clicked_center_y)
    canvas.create_rectangle(not_clicked_center_x - 1, not_clicked_center_y - 1,
        not_clicked_center_x + 1, not_clicked_center_y + 1)

root = tk.Tk()
canvas = tk.Canvas(width=100, height=100)
canvas.grid()
red = rectangle(1, "red")
green = rectangle(4, "green")
func_id_red = canvas.tag_bind(red.get_canvas_id(), "<Button-1>",
    lambda event: create_event_for_rectangle(green), add="+multi")
func_id_green = canvas.tag_bind(green.get_canvas_id(), "<Button-1>",
    lambda event: create_event_for_rectangle(red), add="+multi")
root.mainloop()

我做错了什么?

英文:

I have 2 canvas rectangles, which have a binding for Button-1. Sometimes I want that when one rectangle gets this event, then the other rectangle should also get this event. So I have to duplicate the Button-1 event by the widget method "event_generate".

My example code is kind of minimal, so you can only press once the left mouse button. When you press the left mouse button over the red rectangle, this event is recognized, which is proved by the message "button1 red <x-coord> <y-coord>" showing up. Because I have added a second binding to each of the rectangles, also the method "create_event_for_rectangle" is started, which is proved by the message "Create event for other rectangle" showing up. This method also generates a new Button-1 event at the green rectangle. The coordinates of the generated event seem to be correct, as additionally a small rectangle is created at the calculated coordinates. This generated event at the the green rectangle now should create the message "button1 green 45 45", but nothing happens.

This is my code:

import tkinter as tk
class rectangle():
    def __init__(self, factor, color):
        self.canvas_id=canvas.create_rectangle(factor*10,factor*10,
                       (factor+1)*10,(factor+1)*10,fill=color)
        canvas.tag_bind(self.canvas_id, &quot;&lt;Button-1&gt;&quot;, self.button1)
        self.color = color
    def button1(self, event):
        print(&quot;button1&quot;, self.color, event.x, event.y)
    def get_canvas_id(self):
        return self.canvas_id
    def get_center(self):
        coords = canvas.coords(self.canvas_id)
        return (coords[0]+coords[2])/2, (coords[1]+coords[3])/2
def create_event_for_rectangle(not_clicked):
    print(&quot;Create event for other rectangle&quot;)
    canvas.tag_unbind(red.get_canvas_id()  , &quot;&lt;Button-1&gt;&quot;, func_id_red)
    canvas.tag_unbind(green.get_canvas_id(), &quot;&lt;Button-1&gt;&quot;, func_id_green)
    not_clicked_center_x, not_clicked_center_y = not_clicked.get_center()
    canvas.event_generate(&quot;&lt;Button-1&gt;&quot;,
           x=not_clicked_center_x, y=not_clicked_center_y)
    canvas.create_rectangle(not_clicked_center_x-1, not_clicked_center_y-1,
                            not_clicked_center_x+1, not_clicked_center_y+1)
root   = tk.Tk()
canvas = tk.Canvas(width=100, height=100)
canvas.grid()
red    = rectangle(1, &quot;red&quot;  )
green  = rectangle(4, &quot;green&quot;)
func_id_red   = canvas.tag_bind(red.get_canvas_id()  , &quot;&lt;Button-1&gt;&quot;,
                lambda event: create_event_for_rectangle(green), add=&quot;+multi&quot;)
func_id_green = canvas.tag_bind(green.get_canvas_id(), &quot;&lt;Button-1&gt;&quot;,
                lambda event: create_event_for_rectangle(red  ), add=&quot;+multi&quot;)
root.mainloop()

What am I doing wrong?

答案1

得分: 0

这是你要翻译的代码部分:

That's because the ```tag_unbind()``` or ```unbind()``` clears all the existing handlers. The backend Tcl/Tk library does not provide a command for unbinding, and [the current implementation](https://github.com/python/cpython/blob/v3.11.3/Lib/tkinter/__init__.py#L1450) of the ```unbind()``` binds a dummy empty script. (There is [an old open issue](https://github.com/python/cpython/issues/75666) on this problem. What about pinging the issue?)

Changing event handlers and synthesizing events make the program unnecessarily complex. In your case, it's much better to use the ```after_idle()``` and closures, like the following example.

import tkinter as tk

root = tk.Tk()

canvas = tk.Canvas(width=100, height=100)
canvas.grid()

class rectangle():
    def __init(self, factor, color):
        self.canvas_id = canvas.create_rectangle(...)
        canvas.tag_bind(self.canvas_id, "<Button-1>", self.button1)
        self.other = None
    def get_center(self):
        coords = canvas.coords(self.canvas_id)
        return (coords[0]+coords[2])/2, (coords[1]+coords[3])/2
    def button1(self, event=None):
        print("button1", self.color, event)
        other = self.other
        if other and event:
            x, y = other.get_center()
            canvas.create_rectangle(x-1, y-1, x+1, y+1)
            print("Schedule the button1() of other rectangle")
            root.after_idle(other.button1)

red = rectangle(1, "red"  )
green = rectangle(4, "green")
red.other = green
green.other = red

root.mainloop()

如果你需要进一步的翻译或有其他问题,请告诉我。

英文:

That's because the tag_unbind() or unbind() clears all the existing handlers. The backend Tcl/Tk library does not provide a command for unbinding, and the current implementation of the unbind() binds a dummy empty script. (There is an old open issue on this problem. What about pinging the issue?)

Changing event handlers and synthesizing events make the program unnecessarily complex. In your case, it's much better to use the after_idle() and closures, like the following example.

import tkinter as tk

root = tk.Tk()

canvas = tk.Canvas(width=100, height=100)
canvas.grid()

class rectangle():
    def __init__(self, factor, color):
        self.canvas_id = canvas.create_rectangle(...)
        canvas.tag_bind(self.canvas_id, &quot;&lt;Button-1&gt;&quot;, self.button1)
        self.other = None
    def get_center(self):
        coords = canvas.coords(self.canvas_id)
        return (coords[0]+coords[2])/2, (coords[1]+coords[3])/2
    def button1(self, event=None):
        print(&quot;button1&quot;, self.color, event)
        other = self.other
        if other and event:
            x, y = other.get_center()
            canvas.create_rectangle(x-1, y-1, x+1, y+1)
            print(&quot;Schedule the button1() of other rectangle&quot;)
            root.after_idle(other.button1)

red = rectangle(1, &quot;red&quot;  )
green = rectangle(4, &quot;green&quot;)
red.other = green
green.other = red

root.mainloop()

答案2

得分: 0

我在https://github.com/python/cpython/issues/75666找到了一个解决方案,并更新了我的代码。这个补丁重载了tag_unbind方法。新的tag_unbind首先读取绑定的函数id,然后移除要移除的函数,然后重新绑定。现在它按预期工作。

import tkinter as tk

class CanvasPatched(tk.Canvas):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def tag_unbind(self, tagOrId, sequence, funcid=None):
        funcs_string = self.tk.call(self._w, 'bind', tagOrId, sequence, None).split('\n')
        bound = '\n'.join([f for f in funcs_string if not f.startswith('if {"[' + funcid)])
        self.tk.call(self._w, 'bind', tagOrId, sequence, bound)
        if funcid:
            self.deletecommand(funcid)

class Rectangle():
    def __init__(self, factor, color):
        self.canvas_id = canvas.create_rectangle(factor * 10, factor * 10,
                                                (factor + 1) * 10, (factor + 1) * 10, fill=color)
        canvas.tag_bind(self.canvas_id, "<Button-1>", self.button1)
        self.color = color

    def button1(self, event):
        print("button1", self.color, event.x, event.y)

    def get_canvas_id(self):
        return self.canvas_id

    def get_center(self):
        coords = canvas.coords(self.canvas_id)
        return (coords[0] + coords[2]) / 2, (coords[1] + coords[3]) / 2

def create_event_for_rectangle(not_clicked):
    print("Create event for other rectangle")
    canvas.tag_unbind(red.get_canvas_id(), "<Button-1>", func_id_red)
    canvas.tag_unbind(green.get_canvas_id(), "<Button-1>", func_id_green)
    not_clicked_center_x, not_clicked_center_y = not_clicked.get_center()
    canvas.event_generate("<Button-1>", x=not_clicked_center_x, y=not_clicked_center_y, state=3)
    canvas.create_rectangle(not_clicked_center_x - 1, not_clicked_center_y - 1,
                            not_clicked_center_x + 1, not_clicked_center_y + 1)

root = tk.Tk()
canvas = CanvasPatched(width=100, height=100)
canvas.grid()
red = Rectangle(1, "red")
green = Rectangle(4, "green")
func_id_red = canvas.tag_bind(red.get_canvas_id(), "<Button-1>",
                lambda event: create_event_for_rectangle(green), add="+multi")
func_id_green = canvas.tag_bind(green.get_canvas_id(), "<Button-1>",
                lambda event: create_event_for_rectangle(red), add="+multi")
root.mainloop()
英文:

I found a solution at https://github.com/python/cpython/issues/75666 and updated my code. The patch overloads the method tag_unbind. The new tag_unbind reads first the bound function ids, removes the function which shall be removed, and binds then again. Now it works as expected.

import tkinter as tk
class CanvasPatched(tk.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def tag_unbind(self, tagOrId, sequence, funcid=None):
funcs_string = self.tk.call(self._w, &#39;bind&#39;, tagOrId, sequence, None).split(&#39;\n&#39;)
bound = &#39;\n&#39;.join([f for f in funcs_string if not f.startswith(&#39;if {&quot;[&#39; + funcid)])
self.tk.call(self._w, &#39;bind&#39;, tagOrId, sequence, bound)
if funcid:
self.deletecommand(funcid)
class rectangle():
def __init__(self, factor, color):
self.canvas_id=canvas.create_rectangle(factor*10,factor*10,
(factor+1)*10,(factor+1)*10,fill=color)
canvas.tag_bind(self.canvas_id, &quot;&lt;Button-1&gt;&quot;, self.button1)
self.color = color
def button1(self, event):
print(&quot;button1&quot;, self.color, event.x, event.y)
def get_canvas_id(self):
return self.canvas_id
def get_center(self):
coords = canvas.coords(self.canvas_id)
return (coords[0]+coords[2])/2, (coords[1]+coords[3])/2
def create_event_for_rectangle(not_clicked):
print(&quot;Create event for other rectangle&quot;)
canvas.tag_unbind(red.get_canvas_id()  , &quot;&lt;Button-1&gt;&quot;, func_id_red)
canvas.tag_unbind(green.get_canvas_id(), &quot;&lt;Button-1&gt;&quot;, func_id_green)
not_clicked_center_x, not_clicked_center_y = not_clicked.get_center()
canvas.event_generate(&quot;&lt;Button-1&gt;&quot;, x=not_clicked_center_x, y=not_clicked_center_y, state=3)
canvas.create_rectangle(not_clicked_center_x-1, not_clicked_center_y-1,
not_clicked_center_x+1, not_clicked_center_y+1)
root   = tk.Tk()
canvas = CanvasPatched(width=100, height=100)
canvas.grid()
red    = rectangle(1, &quot;red&quot;  )
green  = rectangle(4, &quot;green&quot;)
func_id_red   = canvas.tag_bind(red.get_canvas_id()  , &quot;&lt;Button-1&gt;&quot;,
lambda event: create_event_for_rectangle(green), add=&quot;+multi&quot;)
func_id_green = canvas.tag_bind(green.get_canvas_id(), &quot;&lt;Button-1&gt;&quot;,
lambda event: create_event_for_rectangle(red  ), add=&quot;+multi&quot;)
root.mainloop()

huangapple
  • 本文由 发表于 2023年4月13日 23:36:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76007323.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定