tkinter で 低層のウィジェットでイベントを検出する

tkinter で 低層のウィジェットでイベントを検出する

概要

tkinterでイベントを検出する際、対象のウィジェットの手前に別のウィジェットがあるとイベントを検出できません。
それを回避し、手前にウィジェットが存在してもイベントが検出できるサンプルプログラムを書いてみました。

サンプルプログラム

さっそくサンプルプログラムを貼りつけておきます。

解説のようなもの

canvasにバインドしたイベントハンドラ

以下の部分が普通にcanvasにマウスクリックイベントをバインドしている部分です。

        self.test_canvas.bind('<ButtonPress-1>', self._on_click_canvas)

canvas上(水色の部分)でクリックすると_on_click_canvasが実行され、 _on_click_canvas : click on canvasと表示されます。
しかし、その上に配置されたラベル(labex_1x)上でクリックしたときは表示されません。

labelもcanvasの一部なので、ここでもクリック処理が動いてほしいことはよくあると思います。

rootにバインドしたイベントハンドラ

そこで、以下の部分でrootにマウスクリックイベントをバインドします。

        self.winfo_toplevel().bind('<ButtonPress-1>', self._on_click, "+")

これはrootにバインドされたイベントですから、当然canvas以外の部分でも処理が動きます。
そこで、イベントハンドラでマウス座標を取得し、canvasの範囲内かを確認し、 そうであればクリック処理(ここでは_on_click : click on canvasを表示)を実行します。
パラメータの最後の"+"は、既にバインドされているハンドラがあった時、上書きせずに追加することを指定しています。
(つまり、以前にバインドしたハンドラと今回のハンドラ両方が実行されます)

こちらの処理はcanvas上(水色の部分)でクリックしたときも その上に配置されたラベル(labex_1x)上でクリックしたときもクリック処理が実行されます。

なお、イベントハンドラに渡されるパラメータeventevent.xevent.yはウィジェット内の相対座標なので 比較にはevent.x_rootevent.y_rootを使用して画面上の座標を使用します。
当然比較するcanvasの座標もwidget.winfo_rootx()widget.winfo_rooty()で画面上の座標を使用しなければなりません。

おまけ

おまけとして、ダブルクリックした位置に存在するウィジェットの一覧を表示する処理を入れておきました(_on_doubleclick) 。
ダブルクリックに意味はなく、クリックは既に使っていたのでダブルクリックにしただけです。

おーちゃくせずに別のプログラム書けよ > 自分

ふと思ったこと

event.widget から 順に master をたどって行き、対象のウィジェットが見つかるかで判断する方法もあるなぁ…
rootの masterNone なのでそこでサーチ終了。
どっちが簡単かな…

ふと思ったこと その2

bindするときに、.winfo_children() を再帰的にサーチしてすべての子ウィジェットにbindしていく、という手もあるなぁ。
いっぱいウィジェット配置してるとえらいことになるかもしれんけど…