PictureBox コントロールの "恐ろしい" <del>バグ</del>仕様

いつだったか、TabStrip コントロールの使えなさを嘆いた記事があったけど、それの続きのような話。BMSE のバグの話から Visual Basic 6.0 のバグ仕様の話に繋がっていく長文なので、興味のない人は読み飛ばすべし。

先日、IRC で BMSE 1.3.3 の『オブジェが吹き飛び、果ては強制終了する上に編集中のデータも保存されない』というバグが報告された。そこで示された手順の通りに操作してみると・・・確かにオブジェは画面外へと吹き飛び、その後にエラーを示すポップアップが現れ、編集中のデータは保存されなかった (正確には保存しようとして失敗している)!見た目は面白いが、実際のところかなり深刻なバグだ。ちなみにその手順とは以下の通り (もちろん、これらの手順を行ったことによる損害は一切関知しない)。

  1. オブジェを選択する (数は問わない)。
  2. メニューバーの右側にある空欄をマウスでクリックする (当然、ここでは何も起こらない)。
  3. クリックしたまま、マウスをメインウィンドウへと移動する。
  4. 選択されていないオブジェの上を通過する。
  5. オブジェが吹き飛ぶ (任意の方向へ吹き飛ばすことも可能だ)。
  6. そのまま続けているとエラーが発生して強制終了する (前述した通り、編集中のデータは保存されない)。

※なお、これは BMSE 1.3.3 までの全てのバージョンで起こりうるバグで、1.3.4 にて修正されている。1.3.3 までのバージョンを使用している人は忘れずにアップデートしておこう。

さて、なぜこのようなバグが発生したのかというと・・・結論からいえば、これは Visual Basic 6.0 のバグ仕様によるものだ。BMSE のメインウィンドウには PictureBox コントロールを使用しており、オブジェの移動や選択、書き込みなどマウス関連の処理には同コントロールの MouseDown、MouseMove、そして MouseUp イベントを使用している。本来、これら3つのイベントの発行条件は以下のようになっている。

MouseDown
  • PictureBox 上でマウスボタンがクリックされた時にのみ発行される。
MouseMove
  • マウスボタンがクリックされていない場合、PictureBox 上をマウスが移動した時に発行される。
  • PictureBox 上でマウスボタンがクリックされ、かつ PictureBox 上をマウスが移動した時に発行される。
MouseUp
  • PictureBox 上でクリックされたマウスボタンがリリースされた時にのみ発行される。

つまり、これに従えば MouseDown → (MouseMove →) MouseUp という順でイベントが発行されるはずである。なぜこのような回りくどい書き方をするのかというと、場合によってはこの通りにイベントが発行されないことがあるからだ。最初に紹介した BMSE のバグの原因はここにある。その原因とは、先の手順の2番 (メニューバーの右側にある空欄をクリックする) で、これを実行すると、PictureBox の MouseDown をスキップしながら MouseMove・MouseUp が発行されるのだ。これにより、マウスボタンクリック時の MouseMove・MouseUp が MouseDown の後に来ることを期待したコードを書いているとバグが生まれることになる (この現象を簡単に確認できるアプリケーションを用意したので、試してもらいたい。なお、このアプリケーションで多数配置されたコントロールはどのコントロールで同じバグ仕様が発生するかを調べるためのもので、マウスボタンの状態を出力する ListBox と入力を監視する PictureBox を除いて何の効果もない。そして、ComboBox コントロールがフォーカスを得た瞬間に限り同じバグ仕様が発生することが確認できる)。

このバグ仕様の対策として最も簡単なのは、変数をマウスボタンのフラグとして用いることである (サンプル対策を実装したアプリ)。こうすることで、MouseDown イベントがスキップされた時に MouseMove・MouseUp イベントが実行されるのを防ぐことができる。なんでわざわざこんなことを・・・という気がしないでもないが、そういうバグ仕様があるのだからしょうがない。というわけで、VB6 で PictureBox を使っている人は注意した方がいいだろう。

余談・・・上のアプリで遊んでいて思い出したが、PictureBox コントロールでマウスボタンを連打すると MouseDown イベントが発行されないことがある、というバグ仕様もあった。先のバグ仕様と同様、上のコードで対策が取れるが、こちらにも注意されたし。