色の使い方(5) コンポーネント(コントロール)で色を使うTip&解説ここではコンポーネント(コントロール)でのパレットの使い方を説明します。簡単なカラーパネルコントロールを作って見ましょう。見本を下に示します。 中央に見えるのがカラーパネルコントロールで Color property で指定された色 を表示するだけの簡単なコンポーネント(コントロール)です。但し常に純色で表示します。早速コーディングに入りましょう。 まず,カラーパネルコントロールの宣言を以下に示します。 List 1 type TColorPanel = class(TGraphicControl) private FColor: TColor; // パネルの色 pal: HPALETTE; // 論理パレット procedure SetColor(Value: TColor); // 色を変える protected // VCL にカラーパネルのパレットを通知する。 function GetPalette: HPALETTE; override; // カラーパネルを描く procedure Paint; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published // カラーパネルの色 property Color: TColor read FColor write SetColor default clWhite; end; お気づきとは思いますが Tips 15〜18 でおなじみの GetPalette メソッドが出てきました。但しこれはフォームの GetPalette ではなくカラーパネルコントロールの GetPalette です。これはどのような意味を持つのでしょうか? GetPalette は コントロールが VCL に欲しい色を伝えるためのメソッドです。各コントロールの GetPalette メソッドは VCL が適宜呼び出します。GetPalette メソッドが 0 を返す場合は「予約色でいいよ」ということを表します。フォームもコントロールの一種なのです。 フォームにコントロールがのっている場合,フォームも含めてたくさんの GetPalette が呼ばれることになります。VCL はこれをどう取りまとめているのでしょうか? 答えは簡単で,ある優先順位にしたがって色を順番に各コントロールに割り当てているのです。下の図を見てください。 コントロールに付いている番号が色の優先順位です。色の優先順位はコントロールの親子関係と Z-Order で決まります。法則は以下の通りです。
これだけです。この法則は再帰的に適用されます。Z-Order とは図から判るようにコントロールが表示される優先順位で Z-Order が手前のコントロールは後ろのコントロールを隠します。Z-Order は設計時に後に置いたコントロールほど手前になりますが,コントロールを右クリックしてメニューから前面に移動,背面に移動を選ぶことで変更できます。また実行時に BringToFront/SendToBack メソッドで変更することも出来ます。尚,グラフィックコントロール(ウィンドウハンドルを持たないコントロール)はウィンドウコントロール(ウィンドウハンドルを持つコントロール)よりも常に後ろになることに注意してください。 色の優先順位は各コントロールが色を獲得する順番です。256色のモードでは全部で色が256色しかないわけですから,優先順位の低いコントロールほど不正確な色で表示される可能性が高くなります。 さて,コーディングに戻りましょう。まず,コンストラクタとデストラクタを作ります。 List 2 constructor TColorPanel.Create(AOwner: TComponent); begin inherited Create(AOwner); Width := 50; Height := 50; // コントロールの大きさ 50x50(Default) SetColor(clWhite); // 色の初期値 = 白 end; destructor TColorPanel.Destroy; begin if pal <> 0 then DeleteObject(pal); inherited Destroy; end; これは説明はほとんど不要でしょう。pal は論理パレットのハンドルです。GetPalette メソッドは以下のようになります。 List 3 function TColorPanel.GetPalette: HPALETTE; begin Result := pal; end; 次に最も肝心な色の設定(SetColor メソッド)を書きます。 List 4 procedure TColorPanel.SetColor(Value: TColor); var LogPalette: TLogPalette; c: TColor; f: TForm; begin if FColor <> Value then begin FColor := Value; // System Color 型で指定された場合に備え // RGB 型に変換する。 c := ColorToRGB(Value); if pal <> 0 then DeleteObject(Pal); // 一色の論理パレットを作る。 with LogPalette do begin palVersion := $0300; palNumEntries := 1; palPalEntry[0].peRed := GetRValue(c); palPalEntry[0].peGreen := GetGValue(c); palPalEntry[0].peBlue := GetBValue(c); pal := CreatePalette(LogPalette); end; // VCL に論理パレットの変更を報せる。 f := TForm(GetParentForm(Self)); if (f <> Nil) and f.Active then f.Perform(WM_QUERYNEWPALETTE, 0, 0); Invalidate; // 再描画 end; end; このコントロールでは1色しか使いませんから,1色の論理パレットを作ります。パレットが変わったらフォームの PaletteChanged を呼び出さなければなりません。コントロールがのっているフォームを取得するには GetParentForm 関数を使います。そして PaletteChanged メソッドを呼べばよいのですが...なんと TForm.PaletteChanged は protected なメソッドなどでコントロールの中から直接呼び出すことはできません。ではどうするかというと,上に示したように WM_QUERYNEWPALETTE メッセージを無理矢理フォームに送れば良いのです。 フォームがアクティブかどうかチェックしていることに注意してください。WM_QUERYNEWPALETTE は フォームがアクティブな時しか来ないはずのメッセージなので念のためこうしてます。フォームは WM_QUERYNEWPALETTE が来ると PaletteChanged(True) を呼び出します。 フォームがアクティブで無い時は PaletteChanged(True) を呼び出さなくてはいいのか? という疑問が当然湧くと思います。答えはイエスです。フォームがアクティブでない場合は PaletteChanged(True) を呼び出しても色の再配分が起きません。色の再配分はアクティブなフォームにしかできないのです。 さて残りを書いてしまいましょう。後はコントロールを描画するだけです。 List 5 procedure TColorPanel.Paint; var OldPalette: HPALETTE; begin // パレットを選択/実体化する。 OldPalette := SelectPalette(Canvas.Handle, pal, True); RealizePalette(Canvas.Handle); // ブラシの色をセットする(PaletteRGB 型) Canvas.Brush.Color := (FColor and $00FFFFFF) or $02000000; // ブラシでコントロールを塗る Canvas.FillRect(Rect(0, 0, Width, Height)); // パレットを元にに戻す。 SelectPalette(Canvas.Handle, OldPalette, True); end; 特に難しい点は無いはずです。パレットを Canvas に選択し,コントロールを塗りつぶしているだけです。色は 最上位バイトを $02 にして PaletteRGB 型に変更してから ブラシにセットしています。こうしないとディザカラーになってしまうからです。 まとめ パレットを使うコントロールの書き方がお判りになりましたでしょうか。要点は次の3点です
これさえ守れば VCL はコントロールは出来うる限り正しい色で表示されるように努力してくれます。例えば コントロールの Z-Order が変わった時,コントロールが動的に作られたり破棄された時等,コントロール同士の色の優先順位が変化した時は自動的に VCL が面倒を見てくれます。コントロール側であれこれ処理をする必要は有りません。この VCL のサービスを無視せずに有効に使ってください。でないと VCL が泣きます (^^; 最後に TColorPanel コントロールの全リストを載せておきます。参考としてお使いください。 List 6 unit ColorPanel; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TColorPanel = class(TGraphicControl) private FColor: TColor; // パネルの色 pal: HPALETTE; // 論理パレット procedure SetColor(Value: TColor); // 色を変える protected // VCL にカラーパネルのパレットを通知する。 function GetPalette: HPALETTE; override; // カラーパネルを描く procedure Paint; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published // カラーパネルの色 property Color: TColor read FColor write SetColor default clWhite; // カラーパネルのデフォルトの大きさ 50x50 property Width default 50; property Height default 50; end; procedure Register; implementation constructor TColorPanel.Create(AOwner: TComponent); begin inherited Create(AOwner); Width := 50; Height := 50; // コントロールの大きさ 50x50(Default) SetColor(clWhite); // 色の初期値 = 白 end; destructor TColorPanel.Destroy; begin if pal <> 0 then DeleteObject(pal); inherited Destroy; end; function TColorPanel.GetPalette: HPALETTE; begin Result := pal; end; procedure TColorPanel.SetColor(Value: TColor); var LogPalette: TLogPalette; c: TColor; f: TForm; begin if FColor <> Value then begin FColor := Value; // System Color 型で指定された場合に備え // RGB 型に変換する。 c := ColorToRGB(Value); if pal <> 0 then DeleteObject(Pal); // 一色の論理パレットを作る。 with LogPalette do begin palVersion := $0300; palNumEntries := 1; palPalEntry[0].peRed := GetRValue(c); palPalEntry[0].peGreen := GetGValue(c); palPalEntry[0].peBlue := GetBValue(c); pal := CreatePalette(LogPalette); end; // VCL に論理パレットの変更を報せる。 f := TForm(GetParentForm(Self)); if (f <> Nil) and f.Active then f.Perform(WM_QUERYNEWPALETTE, 0, 0); Invalidate; // 再描画 end; end; procedure TColorPanel.Paint; var OldPalette: HPALETTE; begin // パレットを選択/実体化する。 OldPalette := SelectPalette(Canvas.Handle, pal, True); RealizePalette(Canvas.Handle); // ブラシの色をセットする(PaletteRGB 型) Canvas.Brush.Color := (FColor and $00FFFFFF) or $02000000; // ブラシでコントロールを塗る Canvas.FillRect(Rect(0, 0, Width, Height)); // パレットを元にに戻す。 SelectPalette(Canvas.Handle, OldPalette, True); end; procedure Register; begin RegisterComponents('NakCtrl', [TColorPanel]); end; end. |