Tkinter画布滚动/拖动通知

时间:2013-03-04 17:42:26

标签: python events canvas scroll tkinter

我想在视图更改时重绘画布(自定义滚动大图像)。

目前我挂钩了几个函数来实现这个目标:xview_moveto,yview_moveto,scan_dragto

是否有可用于清洁工作的事件?还是另一种方法呢?

当前代码类似于:

class CustomCanvas(Tkinter.Canvas):
    def xview(self, *args):
        r = Tkinter.Canvas.xview(self, *args)
        if args:
            self.event_generate("<<ScrollEvent>>")
        return r
    def yview(self, *args):
        r = Tkinter.Canvas.yview(self, *args)
        if args:
            self.event_generate("<<ScrollEvent>>")
        return r    
    def xview_moveto(self, *args):
        Tkinter.Canvas.xview_moveto(self, *args)
        self.event_generate("<<ScrollEvent>>")
    def yview_moveto(self, *args):
        Tkinter.Canvas.yview_moveto(self, *args)
        self.event_generate("<<ScrollEvent>>")
    def scan_dragto(self, *args):
        Tkinter.Canvas.scan_dragto(self, *args)
        self.event_generate("<<ScrollEvent>>")

1 个答案:

答案 0 :(得分:1)

如果你对一些开箱即用的想法没有反感,你可以用一些自定义的Tcl代码来解决这个问题。我写这不是因为它是本身的最佳解决方案,而是因为它是一个有趣的解决方案。

解决方案的工作方式如下:滚动时,最终调用的是底层tk小部件的子命令,以实际执行滚动。例如,self.canvas.xview_moveto(...)会生成类似.123455.234123 xview moveto ...的tcl命令。奇怪的数字和点系列是小部件的内部名称。它也是实现滚动行为的命令的名称。 “xview”在tcl命名法中称为子命令,尽管它可以被认为是widget对象上的方法。

现在,Tcl的酷炫之处在于,您可以重命名任何命令并将其替换为不同的命令。由于此小部件发生的一切都调用此命令,因此我们可以创建一个代理,通过该代理发送所有命令。

在您的情况下,您希望在滚动画布时触发事件。我们知道只要使用“xview”或“yview”子命令调用widget命令,它就会滚动。因此,通过使用代理替换widget命令,并让代理查找这些子命令,我们可以完成这一点。

这是使用python 2.7的一个工作示例:

# use 'tkinter' instead of 'Tkinter' if using python 3.x
import Tkinter as tk 

class CustomCanvas(tk.Canvas):
    def __init__(self, *args, **kwargs):
        '''A custom canvas that generates <<ScrollEvent>> events whenever
           the canvas scrolls by any means (scrollbar, key bindings, etc)
        '''
        tk.Canvas.__init__(self, *args, **kwargs)

        # replace the underlying tcl object with our own function
        # so we can generate virtual events when the object scrolls
        tcl='''
            proc widget_proxy {actual_widget args} {
                set result [$actual_widget {*}$args]
                set command [lindex $args 0]
                set subcommand [lindex $args 1]
                if {$command in {xview yview} && $subcommand in {scroll moveto}} {
                    # widget has been scrolled; generate an event
                    event generate {widget} <<ScrollEvent>>
                }
                return $result
            }

            rename {widget} _{widget}
            interp alias {} ::{widget} {} widget_proxy _{widget}
        '''.replace("{widget}", str(self))
        self.tk.eval(tcl)

class Example(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

        # create an instance of the custom canvas. Make sure it
        # has a largeish scroll region, for demonstration purposes
        self.canvas = CustomCanvas(self, width=400, height=400, 
                                   borderwidth=0, scrollregion=(0,0,1000,1000))
        self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.hsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
        self.canvas.configure(xscrollcommand=self.hsb.set, yscrollcommand=self.vsb.set)

        self.canvas.grid(row=0, column=0, sticky="nsew")
        self.vsb.grid(row=0, column=1, sticky="ns")
        self.hsb.grid(row=1, column=0, sticky="ew")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        # this binds to the virtual event that is sent by the proxy
        self.canvas.bind("<<ScrollEvent>>", self.on_scroll)

        # some data, just so that we can see that the canvas
        # really is scrolling
        for y in range(0, 1000, 100):
            for x in range(0, 1000, 100):
                self.canvas.create_text(x, y, text="%s/%s" % (x,y), anchor="nw")

    def on_scroll(self, event):
        print "widget scrolled..."

if __name__ == "__main__":
    root = tk.Tk()
    view = Example(root)
    view.pack(side="top", fill="both", expand=True)
    root.mainloop()

注意事项:这仅适用于滚动区域时,即使您使用键盘滚动(例如:向上翻页,向下翻页等)它也应该有效。如果您调整窗口大小,则事件不会触发。您可以通过绑定到<Configure>来处理该情况。此外,为了简洁起见,我省略了错误检查,尽管它应该相当健壮。最后,你只能在一个程序中使用这个特定的实现,因为我硬编码“widget_proxy”而不是使它更独特。这是一个留给读者的练习。

相关问题