Delphi の TControl の派生クラスには、Hint プロパティーがあってこれに適当な文字列を設定し、ShowHint プロパティーを true にすると、右図のようにコントロールの上にカーソルをもっていくと、しばらくしてヒントウィンドウが表示される。これは VCL の THintWindow クラスのインスタンスが Application オブジェクトに制御されて表示されているものである。TApplication には、ヒントウィンドウの見かけに関する三つのプロパティーがある。HintColor は背景色を設定・取得する。HintPause プロパティーは、ヒントが表示されるまで時間をミリ秒で設定できる。デフォルトは0.5秒である。HintShortPause は、別のヒントがすでに表示されている場合にヒントを呼び出すまでの時間を設定できる。デフォルトは50ミリ秒である。
ヒントウィンドウを THintWindow クラスから派生した任意のクラスに置き換えることができる。右図は、その一例である。ヒントウィンドウのフォントを代え、フォントの色を変え、ウィンドウの大きさを変え、左端を任意の色で背景を塗りつぶし、小さなビットマップを表示している。右図の例は配色の趣味がよくないが、要するにコード次第でどのような見かけにすることもできることを示したかったのである。ここでは、この例のようにするにはどうしたらよいかを調べよう。
まず最初は、Application.HintPause と Application.HintColor を変更することから始めよう。これはたんに代入するだけでできるが、ここではヒントウィンドウをカスタマイズする機能をコンポーネントにしたいので、わざわざそのためのクラスをつくってやってみよう。Delphi を起動して保存してから、メニューの[ファイル]、[新規作成]からユニットを選んで、以下のようにして MyHintUnit.pas として保存する。
List 1
unit MyHintUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms;
type
THintSetter = class(TComponent)
private
FBkColor:TColor;
FHintPause:integer;
procedure SetBkColor(const Value: TColor);
procedure SetHintPause(const Value: integer);
protected
public
constructor Create(AOwner: TComponent);override;
published
property BkColor:TColor read FBkColor write SetBkColor;
property HintPause:integer read FHintPause write SetHintPause;
end;
implementation
{ ------------ THintSetter ------------ }
constructor THintSetter.Create(AOwner: TComponent);
begin
inherited;
FBkColor := Application.HintColor;
FHintPause := 500;
end;
procedure THintSetter.SetBkColor(const Value: TColor);
begin
FBkColor := Value;
if not (csDesigning in ComponentState) then Application.HintColor := Value;
end;
procedure THintSetter.SetHintPause(const Value: integer);
begin
FHintPause := Value;
if not (csDesigning in ComponentState) then Application.HintPause := Value;
end;
end.
ものすごく簡単なコンポーネントだが、以下で少しずつ機能を追加してゆく。このコンポーネントを動的に作成してテストするコードを以下にしめす。uses に MyHintUnit を追加する。
List 2
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, MyHintUnit, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
HintSetter:THintSetter;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
HintSetter := THintSetter.Create(self);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
with HintSetter do begin
BkColor := RGB(230,230,255);
HintPause := 200;
end;
end;
end.
実行して Button1 を押すと右図のようになる。
つぎにヒントウィンドウの大きさを変えてみよう。これは、Application のプロパティーでは設定できない。D5 の VCL では、THintWindow クラスは Controls.pas で宣言・実装されているが、このユニットの型宣言に
type THintWindowClass = class of THintWindow;
がある。つまり、THintWindowClass は THintWindow のクラス参照型である。ヒントウィンドウは Application オブジェクトによって動的に作成される。このとき、Application は、Forms.pas にあるグローバル変数
var HintWindowClass: THintWindowClass = THintWindow;
を使って作成する。この初期値は、うえのように THintWindow であるが、任意の時点で THintWindow から派生したクラスと置き換えることができる。重要なことは、クラス参照型の変数を置き換える、ということであり、派生クラスのインスタンスを置き換えることはできない。ヒントウィンドウのインスタンスは Application がつくるからである。したがって、たとえば THintWindow を継承して TMyHint クラスをつくって、これを入れ替えるには
HintWindowClass := TMyHint;
Application.ShowHint := false;
Application.ShowHint := true;とするとよいが、当然、このクラスは、TComponent から派生していないので、そのインスタンスのプロパティーを設計時に直接設定することはできない。ここでは、うえで作った THintSetter のインスタンスを通して、間接的に設定することにしよう。MyHintUnit.pas を以下のようにする。
List 3
unit MyHintUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms;
type
THintSetter = class(TComponent)
private
FBkColor:TColor;
FHintPause:integer;
FLeftMargin:integer;
FHeightMargin:integer;
procedure SetBkColor(const Value: TColor);
procedure SetHintPause(const Value: integer);
protected
public
constructor Create(AOwner: TComponent);override;
published
property BkColor:TColor read FBkColor write SetBkColor;
property HintPause:integer read FHintPause write SetHintPause;
property LeftMargin:integer read FLeftMargin write FLeftMargin;
property HeightMargin:integer read FHeightMargin write FHeightMargin;
end;
implementation
var
HintSetter:THintSetter;
type
TMyHint = class(THintWindow)
private
protected
public
procedure ActivateHint(Rect: TRect; const AHint: string); override;
end;
{ ------------ THintSetter ------------ }
constructor THintSetter.Create(AOwner: TComponent);
begin
inherited;
HintSetter := self;
FBkColor := Application.HintColor;
FHintPause := 500;
if not (csDesigning in ComponentState) then begin
HintWindowClass := TMyHint;
Application.ShowHint := false;
Application.ShowHint := true;
end;
end;
procedure THintSetter.SetBkColor(const Value: TColor);
begin
FBkColor := Value;
if not (csDesigning in ComponentState) then Application.HintColor := Value;
end;
procedure THintSetter.SetHintPause(const Value: integer);
begin
FHintPause := Value;
if not (csDesigning in ComponentState) then Application.HintPause := Value;
end;
{ --------- TMyHint ------------ }
procedure TMyHint.ActivateHint(Rect: TRect; const AHint: string);
begin
Inc(Rect.Right,HintSetter.LeftMargin);
Inc(Rect.Bottom,HintSetter.HeightMargin);
inherited;
end;
end.
そして、これをテストするプログラムの全リストを以下に示す。
List 4
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, MyHintUnit, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
HintSetter:THintSetter;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
HintSetter := THintSetter.Create(self);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
with HintSetter do begin
BkColor := RGB(230,230,255);
HintPause := 200;
LeftMargin := 50;
HeightMargin := 20;
end;
end;
end.
実行して Button1 を押すと右図のようになる。
コードを見てみよう。THintSetter に新たに LeftMargin と HeightMargin の二つのプロパティーを追加した。コンストラクタでは
constructor THintSetter.Create(AOwner: TComponent);
begin
inherited;
HintSetter := self; // ここに注目!
FBkColor := Application.HintColor;
FHintPause := 500;
if not (csDesigning in ComponentState) then begin
HintWindowClass := TMyHint;
Application.ShowHint := false;
Application.ShowHint := true;
end;
end;
のようにして、実装部のグローバル変数
var HintSetter:THintSetter;
に自分のインスタンス参照を設定している。THintWindow の派生クラス TMyHint は実装部に宣言・実装している。
type TMyHint = class(THintWindow) private protected public procedure ActivateHint(Rect: TRect; const AHint: string); override; end;
ここでは、ActivateHint() をオーバーライドしているだけだ。
{ --------- TMyHint ------------ }
procedure TMyHint.ActivateHint(Rect: TRect; const AHint: string);
begin
Inc(Rect.Right,HintSetter.LeftMargin);
Inc(Rect.Bottom,HintSetter.HeightMargin);
inherited;
end;
このように、THintSetter のインスタンス HintSetter の二つのプロパティーを参照して、継承メソッドに渡す Rect 変数を変更している。このようにして、ヒントウィンドウのサイズを変更することができた。
次にフォントを変更してみよう。いまやったサイズの変更とおなじようにして、TMyHint クラスで CalcHintRect() をオーバーライドすることでできる。MyHintUnit.pas を以下に示す。
List 5
unit MyHintUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms;
type
THintSetter = class(TComponent)
private
FBkColor:TColor;
FHintPause:integer;
FLeftMargin:integer;
FHeightMargin:integer;
FFont:TFont;
procedure SetBkColor(const Value: TColor);
procedure SetHintPause(const Value: integer);
procedure SetFont(const Value: TFont);
protected
public
constructor Create(AOwner: TComponent);override;
destructor Destroy; override;
published
property BkColor:TColor read FBkColor write SetBkColor;
property HintPause:integer read FHintPause write SetHintPause;
property LeftMargin:integer read FLeftMargin write FLeftMargin;
property HeightMargin:integer read FHeightMargin write FHeightMargin;
property Font:TFont read FFont write SetFont;
end;
implementation
var
HintSetter:THintSetter;
type
TMyHint = class(THintWindow)
private
protected
public
procedure ActivateHint(Rect: TRect; const AHint: string); override;
function CalcHintRect(MaxWidth: Integer; const AHint: string;
AData: Pointer): TRect; override;
end;
{ ------------ THintSetter ------------ }
constructor THintSetter.Create(AOwner: TComponent);
begin
inherited;
FFont := TFont.Create;
FFont.Assign(Screen.HintFont);
HintSetter := self;
FBkColor := Application.HintColor;
FHintPause := 500;
if not (csDesigning in ComponentState) then begin
HintWindowClass := TMyHint;
Application.ShowHint := false;
Application.ShowHint := true;
end;
end;
destructor THintSetter.Destroy;
begin
FFont.Free;
inherited;
end;
procedure THintSetter.SetBkColor(const Value: TColor);
begin
FBkColor := Value;
if not (csDesigning in ComponentState) then Application.HintColor := Value;
end;
procedure THintSetter.SetHintPause(const Value: integer);
begin
FHintPause := Value;
if not (csDesigning in ComponentState) then Application.HintPause := Value;
end;
procedure THintSetter.SetFont(const Value: TFont);
begin
if FFont <> Value then FFont.Assign(Value);
end;
{ --------- TMyHint ------------ }
procedure TMyHint.ActivateHint(Rect: TRect; const AHint: string);
begin
Inc(Rect.Right,HintSetter.LeftMargin);
Inc(Rect.Bottom,HintSetter.HeightMargin);
inherited;
end;
function TMyHint.CalcHintRect(MaxWidth: Integer; const AHint: string;
AData: Pointer): TRect;
begin
if Canvas.Font <> HintSetter.Font then Canvas.Font.Assign(HintSetter.Font);
result := inherited CalcHintRect(MaxWidth,AHint,AData);
end;
end.
これをテストする全リストを以下に示す。
List 6
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, MyHintUnit, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
HintSetter:THintSetter;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
HintSetter := THintSetter.Create(self);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
with HintSetter do begin
BkColor := RGB(230,230,255);
HintPause := 200;
LeftMargin := 20;
HeightMargin := 10;
Font.Name := 'MS P明朝';
Font.Height := 14;
Font.Style := Font.Style + [fsBold];
Font.Color := clGreen;
end;
end;
end.
実行して Button1 を押すと右図のようになる。ここでは、Font.Color だけが実際の描画に反映されていない。これは派生元の THintWindow の Paint メソッドのなかで設定し直しているからだ。
それでは最後に、Paint メソッドをオーバーライドして描画そのものを変更してみよう。ここでは、ヒントの文字列を描く部分はカスタマイズせずに、左の LeftMargin で確保された部分を描画する機能を THintSetter のイベントとしてユーザに公開する事にしよう。MyHintUnit.pas の全リストを以下に示す。これは、コンポーネントとして THintSetter をインストールできるようにした最終形である。
List 7
unit MyHintUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms;
type
TLeftMarginPaintEvent = procedure (Sender: TObject;
R: TRect; Canvas: TCanvas) of object;
THintSetter = class(TComponent)
private
FBkColor:TColor;
FHintPause:integer;
FLeftMargin:integer;
FHeightMargin:integer;
FFont:TFont;
FOnLeftMaginPaint:TLeftMarginPaintEvent;
procedure SetBkColor(const Value: TColor);
procedure SetHintPause(const Value: integer);
procedure SetFont(const Value: TFont);
protected
public
constructor Create(AOwner: TComponent);override;
destructor Destroy; override;
published
property BkColor:TColor read FBkColor write SetBkColor;
property HintPause:integer read FHintPause write SetHintPause;
property LeftMargin:integer read FLeftMargin write FLeftMargin;
property HeightMargin:integer read FHeightMargin write FHeightMargin;
property Font:TFont read FFont write SetFont;
property OnLeftMaginPaint:TLeftMarginPaintEvent read FOnLeftMaginPaint
write FOnLeftMaginPaint;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [THintSetter]);
end;
//----------
var
HintSetter:THintSetter;
type
TMyHint = class(THintWindow)
private
FActivating: Boolean;
protected
procedure Paint; override;
public
procedure ActivateHint(Rect: TRect; const AHint: string); override;
function CalcHintRect(MaxWidth: Integer; const AHint: string;
AData: Pointer): TRect; override;
end;
{ ------------ THintSetter ------------ }
constructor THintSetter.Create(AOwner: TComponent);
begin
inherited;
FFont := TFont.Create;
FFont.Assign(Screen.HintFont);
HintSetter := self;
FBkColor := Application.HintColor;
FHintPause := 500;
if not (csDesigning in ComponentState) then begin
HintWindowClass := TMyHint;
Application.ShowHint := false;
Application.ShowHint := true;
end;
end;
destructor THintSetter.Destroy;
begin
FFont.Free;
inherited;
end;
procedure THintSetter.SetBkColor(const Value: TColor);
begin
FBkColor := Value;
if not (csDesigning in ComponentState) then Application.HintColor := Value;
end;
procedure THintSetter.SetHintPause(const Value: integer);
begin
FHintPause := Value;
if not (csDesigning in ComponentState) then Application.HintPause := Value;
end;
procedure THintSetter.SetFont(const Value: TFont);
begin
if FFont <> Value then FFont.Assign(Value);
end;
{ --------- TMyHint ------------ }
procedure TMyHint.ActivateHint(Rect: TRect; const AHint: string);
begin
Inc(Rect.Right,HintSetter.LeftMargin);
Inc(Rect.Bottom,HintSetter.HeightMargin);
inherited;
end;
function TMyHint.CalcHintRect(MaxWidth: Integer; const AHint: string;
AData: Pointer): TRect;
begin
if Canvas.Font <> HintSetter.Font then Canvas.Font.Assign(HintSetter.Font);
result := inherited CalcHintRect(MaxWidth,AHint,AData);
end;
procedure TMyHint.Paint;
var
R: TRect;
LM:integer;
begin
FActivating := true;
try
R := ClientRect;
LM := HintSetter.LeftMargin;
if Assigned(HintSetter.OnLeftMaginPaint) and (LM > 0) then
HintSetter.OnLeftMaginPaint(HintSetter,
Rect(R.Left,R.Top,R.Left+LM-2,R.Bottom),Canvas);
Canvas.Brush.Style := bsClear;
Inc(R.Left,LM+2);
DrawText(Canvas.Handle, PChar(Caption), -1, R,
DT_LEFT or DT_NOPREFIX or DT_VCENTER or DT_SINGLELINE);
finally
FActivating := false;
end;
end;
end.このコンポーネントを動的に作成してテストするプログラムの全リストを以下に示す。
List 8
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, MyHintUnit, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
HintSetter:THintSetter;
procedure LMPaint(Sender: TObject; R: TRect; Canvas: TCanvas);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
HintSetter := THintSetter.Create(self);
HintSetter.OnLeftMaginPaint := LMPaint;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
with HintSetter do begin
BkColor := RGB(230,255,230);
HintPause := 200;
LeftMargin := 10;
HeightMargin := 4;
with Font do begin
Name := 'MS UI Gothic';
Style := Style + [fsBold];
Color := clNavy;
Height := 12;
end;
end;
end;
procedure TForm1.LMPaint(Sender: TObject; R: TRect; Canvas: TCanvas);
var
Pts:array[0..2] of TPoint;
begin
Pts[0] := Point(-1,0);
Pts[1] := Point(R.Right,R.Bottom+1);
Pts[2] := Point(-1,R.Bottom+1);
with Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clBlue;
FillRect(R);
Brush.Color := clFuchsia;
Pen.Color := clFuchsia;
Polygon(Pts);
end;
end;
end.
実行して Button1 を押すと右図のようになる。
最後に THintSetter をインストールしてテストするプログラムを以下に示す。ここでは、小さなビットマップをここの EXE に埋め込む - リソースの使い方 でやったようにリソースにして TBitmap のインスタンスで取り出して使用している。
List 9
public
bmp:TBitmap;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
bmp := TBitmap.Create;
bmp.LoadFromResourceName(hInstance,'HintBmp');
bmp.Transparent := true;
HintSetter1.LeftMargin := bmp.Width+4;
HintSetter1.HeightMargin := 2;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
bmp.Free;
end;
procedure TForm1.HintSetter1LeftMaginPaint(Sender: TObject; R: TRect;
Canvas: TCanvas);
begin
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clYellow;
Canvas.FillRect(R);
Canvas.Draw((R.Right-bmp.Width) div 2,(R.Bottom-bmp.Height) div 2,bmp);
end;
実行すると右図のようになる。
また、背景を描画せずに右図のようにビットマップだけを描画してもよい。
以上のように、ここでは Delphi のヒントウィンドウをカスタマイズする方法を調べ、その機能つつみこんだコンポーネントを作成した。