SystemColorsではシステムカラーにならない
VB2005・VB2008 共通の現象なので、たぶん.NET Framework 2.0 に起因するんだと思うんですが。
TextBox とかの背景色を特定の状況に対して固定色を割り当てた場合、その特定の状況が解除された時にシステムデフォルトな状態に戻したいわけです。
で私、こーいう場合は SystemColors なんだと。何の疑いもなく覚えていたんですね。
TextBox1.BackColor = Drawing.SystemColors.Window
みたいな感じで。
したっけこの設定では、Enabled = False にした時にも、Enabled = True の時と同じ配色のままであることが判明。
いやそれじゃ半分しか意味ねぇだろうよ。
どうも Enabled の状態まで含めて元に戻したい場合は、
TextBox1.ResetBackColor()
で初期化してやらないばいかんらしいです。
でもそれでは、別のシステムカラーに差し替えることができないではないですか。
ResetBackColor なんか msdnLibでこのクラスでは、このメソッドは使用されません。とか書かれちゃってるじゃないですか。何でそんなことを書いてあるのかと思ったら、EditorBrowsableAttributeとかでエディタから非参照にしているからじゃないですか。
てことは、公式にはコントロールにシステムカラーをセットする手段がない、ってことになっちゃうんですかね。
なんか納得いかーん。
ということで、ちょっと調べてみました。
まず、てきとーに動作検証用のプログラムを組みます。こんな感じで。
このプログラムは、[ Disabled ]ボタンを押したら TextBox の Enabled を False にし、
[ Enabled ]ボタンを押したら TextBox の Enabled を True にする
というものです。
あと、TextBox の BackColor を「 Red 」「 Window ( Set )」に設定する機能も付けてみました。「 Window ( Reset )」とあるのは、SystemColors.Window の設定ではなく、ResetBackColor メソッドで再初期化するようにしてあります。
「 Red 」を選択して [ Set ] ボタンを押すと、
こんな感じで TextBox の背景色が変わります。
この状態でTextBox の Enabled を False にしてみると、
背景色は変わらず、文字色だけがグレーアウトします。
Enabled を True に戻すと、元の色合いに戻ります。
次、SystemColors.Window を設定してみます。
この時点では、見た目は起動時と変わらないわけですが、
Enabled を False にすると、背景色がグレーアウトしなくなってしまいます。
ResetBackColor で初期状態に戻してやると、
また Enabled = False で背景色もグレーアウトするようになります。
さてこれで状況はおさらいできたとして、
- こんな動作になるのはなぜなのか
- 非推奨な ResetBackColor メソッドではない方法で初期状態にする方法はあるのか
- コントロールデフォルトではない他のシステムカラーにするにはどう設定すればいいのか
ってことを知りたいんですよね。
先日.NET Frameworkの中身を見てみる、.NET Frameworkの中身を見てみたでご紹介しましたが、.NET Framework Library のソースが公開されるようになりました。
こんな時のためにこそ!というわけで、今回は BackColor の内側で実際に何をやっているのかを覗いてみることにします。
ちなみに、ライブラリソースの扱いについての説明は、今回はさらりと流します。
詳しく知りたい方は、上述のエントリをご参照ください。
まず、[ Set ] ボタンのClickイベントにブレークポイントを置きます。
で、「 Red 」RadioButton を選択して[ Set ] ボタンを押します。
ここまででこんな感じ。
TextBox は System.Windows.Forms 名前空間下のオブジェクトですので、この時点で System.Windows.Forms.dll のシンボルを読み込んでから F8 でライブラリソースに潜ります。
おおっ、TextBox の BackColor プロパティのソースが表示されましたよ。
BackColor プロパティは TextBoxBase.cs にあるんですね。
…なるほど、こんな仕組みになっていたんだ。
代入する時はとにかくそのまま受け取りますが、参照する時にちょろちょろやって、って
単に 2 つのシステムカラーを振り分けているだけかい!
ReadOnly プロパティで振り分けているってことは、ここは Enabled のTrue / False がその基準になっているってことです。
ReadOnly = True、つまり Enabled = False の時には SystemColors.Control を使っているだけなんですね。
このロジックにたどり着く前提として、ShouldSerializeBackColor による振り分けを行っていますね。これは何だろう。
という時は、ここにもブレークポイントを置きます。
左端のブレークポイントマークが中抜け赤丸になっているのは、設定したブレークポイントが有効になっていないという印です。
このマークをマウスでポイントすると、有効にするためのアドバイスが表示されます。
つことで説明されたとおりに、ブレークポイントマークを右クリック → [ 場所 ] を選択。
「ファイルのブレークポイント」ダイアログが表示されますので、[ 元のバージョンと異なるソースコードを許可する ] にチェックをつけて OK。
実際ソースとシンボルにずれは見つけられないんですが、なんか細かくバージョンが合わないみたいで、こんな手順を踏まなければならない ( 時もある ) ようですね。
で、めでたく ShouldSerializeBackColor においたブレークポイントを有効にできました。
あとは F5 で ShouldSerializeBackColor まで処理を流して、再度ストップかかったら、F8 押してさらに潜ります。
ShouldSerializeBackColor メソッドは Control.cs にあるんですね。
うーんと、これは BackColor が Empty かどうかを調べて、その結果の逆の Boolean 値を返しているだけですね。Empty でなければ True を返すという。
で、さっきの BackColor プロパティのソースと見合わせると、「Empty でなければ BackColor をそのまま返す」「Empty であれば、Enabled の状態に合わせて SystemColors.Control か SystemColors.Window のどちらかを返す」ということなのがわかります。
では、ResetBackColor メソッドを実行した場合は、どのように初期化しているのか。
これは、「Windows ( Reset )」を選択して [ Set ] ボタンを Click したところにブレークポイントをかけ、
F8 で潜る!
Empty 代入しているだけですかい!
わかりやすー。
つことで、結論。
- BackColor をコントロールの初期値に戻すには、Color.Empty の代入が正しい。
ResetBackColor メソッドでも同じこと。 - 他のシステムカラーを設定するなら、Enabled の値に応じて振り分ける BackColor プロパティを独自に作って OverRide でOK。
なんか泥臭いような気もしますが、ライブラリ自体がそもそもそうやっていることですし。
なんか意外と簡単に納得できちゃったなー。
コメント
はじめまして。いつも拝見しています。
数年前にカーソルのあるときにBackColorを変えるというコントロールを作って、元に戻す処理にて同様の問題に悩まされた事がありました。
Color.Empyで解決できそうです。
投稿者: Chuta | 2008年02月24日 11:40
お世話様ですさるべーじです。
私も今回同様なコントロールのデバッグで猛烈にハマりました。
人様の作ったコントロールで2重に派生してたりしたので、どこが悪さしてるのか突き止めるまでに2日かかりましたー(;-;)。
札幌の方からコメントいただけるのは珍しいです。
ありがとうございます。
投稿者: さるべーじ | 2008年02月25日 00:21
いつも拝見しております・・・旧サイトのころから・・・ ^^
.NetFrameworkのソースが公開されたといっても少し二の足を踏んでいたんですが、
おかげさまで少し壁が低くなったように感じました。
ありがとうございます!
そして、お疲れ様でございます!!
でも・・・最近つくづく思うんですが、
結局技術って地道な積み重ねですよねー。
投稿者: あおおにくん | 2008年02月25日 21:34
> 旧サイトのころから
ありがとうございますー。
ライブラリソースの取り扱いは、実は昔ながらのマルチランゲージプログラミングの際のデバッグ手法そのままだったりします。
私は割とこのへんに抵抗がなかったんですが、これはたぶん以前 VB6 & VC++6 の混在システムで、VB6 のデバッグから VC++6 までトレースしていったことが記憶にあったからだと思います。
最近 VS も規模が大きくなりすぎて、資料の提示が追い付かなくなっている傾向にあります。待っていてもマイクロソフトがやってくれることには限りがありますので(内部でも認識されているようですが、どうも人手と予算が限界みたいです)、知りたいことは自分でさっさと調べちゃえーみたいな気持でいます。
> 結局技術って地道な積み重ねですよねー。
まったく同感です。
でも、「積み重ねりゃ済む」ってのは、ある意味楽かもしれませんね。
努力量と成果量がだいたい比例しますし。
投稿者: さるべーじ | 2008年02月25日 23:40
ちなみにプロパティ値を初期値に戻すためには、「PropertyDescriptor.ResetValue メソッド」を使う方法もあります。
TypeDescriptor.GetProperties(obj) して
.Find(propertyName, False) して
.ResetValue(obj) という感じで。
普段はあまり出番は無いですが、[DefaultValue 属性]が使われたプロパティのように、ResetXX が無い場合にも利用できます。
投稿者: 魔界の仮面弁士 | 2008年02月26日 23:58
> PropertyDescriptor.ResetValue メソッド
うわーそんな手もありですかありがとうございます。
ここまで来ると、もう「どこまで調べたからOK」なのかの判断が
付けられませんね。意外な解決策出まくりで。
広いというか深いというか。
投稿者: さるべーじ | 2008年02月28日 13:24