見に来てくださってありがとうございます。石川さんです。
先日書いた記事に修正を加えようと思って、ソースコードを探し出して実行してみたら、なぜかうまく動かなくてずいぶんと悩んでしまいました。今回はそのお悩みを解決した話です。
修正自体はスクロールバーが必要な時には表示されて、不要な時には非表示になる、という修正をしようと思いたちました(その記事は次回の予定に。。。)。スクロールバー使ったソースはどれかしら、と、探し出して、実行してみたところなんと、ホイールを使ったスクロールがなぜか動作しません。先日、少なくともブログを書いた今年の4月頃までは動作していたのに!
これはもしや知らないうちにPythonの仕様が変更されたのでは、時々バージョンアップしてるからねぇ、と、Pythonの仕様変更を疑ってしまいました。でも、いやいや待て待て、そんな影響が起きるような仕様変更がそんなに頻繁にあるわけはなかろう、と、気を取り直しまして、、、とりあえず、ソースコードをよく読みこんでみました。
ええと、ちゃんとホイールが利用されたときのコーディングもされていますね。と、よく見ると、if文の条件が「event.state == 5」となっていますね。このstateは何かのキーが同時に押されると特定のビットを立てていくようになっていたはずです。ちょっと怪しいのでprintして確認してみました。
mouse_wheel: <MouseWheel event state=Mod3 delta=-120 x=231 y=103> 32
なんと、stateに「Mod3(32 = 2**5)」がセットされている。。。何だこりゃ?!そりゃ、動きませんなぁ。しかし、何でしょうこのMod3は。Google先生に聞いてもなかなか見つけられなかったのですが、、、ありました。ここです。なるほど、ScrollLockですね。値としては、2**5でした。他にもCapsLockでは、2**1が確認できました。ScrollLockとCapsLockが同時にセットされた場合は、2**1 | 2**5となるわけですね。(2進数だと、00100010というように下位から1番目と5番目のビットがセットされてくる。(0オリジンです。))こんな風に、いろいろとセットされてくる可能性があるので、「event.state == 5」でShiftとCtrlが押されていることを判断するのではちょっと足りなかった、ということだったのですね。
と、いうことでstateをチェックしているところはすべて以下のように修正しました。
if event.state & (2**0 | 2**2) == 5: # Shift|Control ... elif event.state & 2**2 == 4: # Control ... elif event.state & 2**0 == 1: # Shift ... elif event.state & (2**0 | 2**2) == 0: # None ...
ちょっと美しくないので、なんとかしたいところですが、ま、そのうち、おいおいと。
調べた結果をまとめますと、以下の通りです。マウスクリックしたときの<ButtonPress>イベントについて記載します。
state | 数値 | Windows | Linux | Mac OSX |
Shift | 2**0 = 1 | Shiftキー | ※ | ※ |
Lock | 2**1 = 2 | Caps Lock | ※ | ※ |
Control | 2**2 = 4 | Ctrlキー | ※ | ※ |
Mod1 | 2**3 = 8 | NumLock | Alt | Command(=Meta) |
Mod2 | 2**4 = 16 | ※ | NumLock | Option(=Alt) |
Mod3 | 2**5 = 32 | ScrollLock | ※ | NumLock |
Mod4 | 2**6 = 64 | ※ | Win | Fn |
Mod5 | 2**7 = 128 | ※ | ※ | ※ |
0x20000 | 2**17 = 131,072 | Alt | ※ | ※ |
わからないところが結構あって、埋められていません。どなたかわかる方、教えていただけるとうれしいです。以下のスクリプトを実行すると、イベントがprintされますので、確認してみてください。
import tkinter c = tkinter.Canvas() c.pack() c.bind("<ButtonPress>", print) c.mainloop()
まとめ
CtrlキーやShiftキーが押された状態を扱う処理を作る場合は、bit演算が必要です。