独創的な考え方に不利でない場合は、少しのカスタムTclコードでこれを解決できます。これは、それ自体が最良の解決策であるからではなく、興味深い解決策であるために書いています。
解決策は次のように機能します。スクロールすると、最終的に呼び出されるのは、実際にスクロールを実行するための基になるtkウィジェットのサブコマンドです。たとえば、self.canvas.xview_moveto(...)
結果は。のようなtclコマンドになります.123455.234123 xview moveto ...
。奇妙に見える一連の数字とドットは、ウィジェットの内部名です。これは、スクロール動作を実装するコマンドの名前でもあります。「xview」は、ウィジェットオブジェクトのメソッドのように考えることができますが、tclの命名法ではサブコマンドと呼ばれます。
さて、Tclのすばらしい点は、任意のコマンドの名前を変更して、別のコマンドに置き換えることができることです。このウィジェットに発生するすべてのものがこのコマンドを呼び出すため、すべてのコマンドが送信されるプロキシを作成できます。
あなたの場合、キャンバスがスクロールされるたびにイベントを発生させたいと思います。ウィジェットコマンドが「xview」または「yview」サブコマンドで呼び出されるたびにスクロールすることがわかっています。したがって、ウィジェットコマンドをプロキシに置き換え、プロキシにこれらのサブコマンドを検索させることで、まさにそれを実現できます。
Python2.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()
警告:これは、領域をスクロールする場合にのみ機能しますが、キーボードでスクロールする場合でも機能するはずです(たとえば、Page Up、Page Downなど)。ウィンドウのサイズを変更しても、イベントは発生しません。にバインドすることで、そのケースを処理できます<Configure>
。また、かなり堅牢なはずですが、簡潔にするためにエラーチェックを省略しました。最後に、この特定の実装は、「widget_proxy」をよりユニークなものにするのではなくハードコーディングしたため、プログラムで1回しか使用できません。それは読者に残された演習です。