2013年6月22日土曜日

コントロールハンドラ

以前TabControlのコントロールハンドルが作られていない状態で
TabPagesにInsertしようとすると落ちる、みたいな話を書きました。

TabControlのように落ちるとまではいかずとも、
コントロールハンドル絡みの話は意外といろんなところで発生するみたいなんですよね。

こないだ発生した現象としては、
ハンドル未作成のTextBoxに対してReadOnlyプロパティをtrueからfalseに変更すると、
なぜか内部的に勝手にコントロールハンドルが作成されます。



なんでやねんと思ってソース追いかけてみました。
ポイントとしては、TextBoxコントロールのIMEをONにできるかどうかを取得する
TextBox.CanEnableImeプロパティというのがあるんですが、こいつの中身が
『TextBoxがReadOnlyでなく、かつパスワードマスクされていない』
という条件で判定しているということです。

.NET Framework内部の流れとしては
①TextBoxのReadOnlyプロパティが変更される
②CanEnableImeを用い、ImeModeプロパティの変更が必要かどうかを判断する
③CanEnableImeの中身で、自身がパスワードマスクされているかどうかを判定
 (ReadOnlyがtrueの場合はここまで来ないため、falseを設定した場合のみ発生する)
④その処理の中で、既定のシステムのパスワード文字を取得
⑤この際にコントロールハンドルが存在しなければ作成する

で、これで何が困るかっていうことなんですが…

コントロールハンドルが作成されるタイミングって、BindingContextChanged等いろんなイベントが走ります。
なので、このへんを知らずに迂闊にマイナーなイベントを使ったりすると、「どこからともなくイベントが発生するんですけど…」みたいなことになります。
こういうのは原因究明が困難ですよね。

回避方法なんですが、要はTextBoxのReadOnlyを変更する前に、そのTextBoxのコントロールハンドルが出来上がってりゃいいわけです。
なんだ、それじゃCreateControlやCreateHandleで先に作ればいいじゃん^^

と思ったんですが…

CreateControlは、子コントロールとそのハンドルまで強制的に作ってくれます。
しかし、こいつはコントロールのVisibleがfalseの場合はハンドルを作成しません。
そもそもReadOnlyをセットするタイミングでハンドルが作成されていないっていうのは、そのTextBox自体が(別のタブに存在する等の理由で)初期状態で画面に表示されていないということに他ならないので、CreateControlは使えません。
画面に最初から表示されているのであれば、その時点でハンドルできてるはずですからね。

CreateHandleであれば強引にハンドルを作れますが、これは子コントロールまでたどって作ってくれるわけではありません。
カスタムコントロール化してコンストラクタで設定する、とかはできれば避けたい。

ぶっちゃけ力技ですが、今回のケースに限って言えば回避策はあります。
それは、TextBoxのCanEnableImeプロパティをオーバーライドして、ゲッターの中身をreturn true等の無害なものに変えてしまうというもの。
ユーザーコードのReadOnlyの設定から.NET内部のパスワード文字取得までの流れのうち、ぶった切れるところをどこか1か所ぶった切ってしまえばいいわけです。
return trueではさすがにあれなので、自身のReadOnlyくらいは見たほうがいいかもですね。

ぶっちゃけこのやり方もあんまりよくないと思いますが…。

まとめると、

  • コントロールハンドルが作成されていないTextBoxのReadOnlyをfalseに設定すると、勝手ににハンドルが作成される
  • ハンドルが作成されると予期せぬイベントが走るのでウザい
  • 画面初期表示時に表示されていないTextBoxは、表示されるまではハンドルが作成されていない状態になっている
  • 原因は、システム既定のパスワード文字を取得する箇所
  • 対策は、システム既定のパスワードを取りに行かないようにCanEnableImeを上書きしてしまう

こんな感じでしょうか。
通常これで困ることはあまりないと思いますが、原因が特定しにくいバグに遭遇した時などに疑ってみてください。

ちなみに、ListBoxのSelectionModeでも発生した気がします。
RecreateHandleメソッドか何かを呼び出しているので、たぶん手の施しようがありません。

0 件のコメント:

コメントを投稿