Delphi のコンポーネントパレットの Standard ページのうち、本稿でこれまでに扱ってこなかったのは TMainMenu 、 TPopupMenu と TRadioGroup である。 メニュー関連のクラスについては、次章で調べることにしているので、本章では、 親ウィンドウの任意の矩形領域を格子状に区切り、複数のコントロールを作成して配置し、メッセージを一括して処理することを考えることにしよう。 このようなグループ化は、ラジオボタンだけではなく、プッシュボタンやチェックボックスについても行う。 グループ化すると、複数のコントロールを配置する視覚上の効果の他に、簡単に多数のコントロールのインスタンスを作る事ができること、メッセージ処理を一元化できることなどメリットは大きい。
| 第20章 コントロールグループ |
| 20−1 ボタングループをコードでつくる |
| 20−2 プッシュボタングループクラス |
| 20−3 チェックグループクラス |
| 20−4 ラジオグループクラス |
| 20−5 まとめ |
本稿のようなコードベースのプログラミングでは、プッシュボタンを五個作るときでも、作成関数を五回、メッセージハンドラを五個書かなければならない。 また、ウィンドウに整列配置するためにも、頭の中で配置座標を暗算で計算しなければならない。 もし、ボタングループのようなコントロールがあれば、インスタンスを作って、ボタンの個数を指定し、メッセージハンドラを一つ作るだけで良いことになるだろう。 複数のボタンの配置は、クラスがプロパティーを参照して自動的に行ってくれる。 すでに、第11章 パネルとアレンジャー では、グリッドアレンジャーとボーダーアレンジャーを作成して、パネルやコントロールの配置を自動化した。 このアレンジャークラスは、もっぱら親ウィンドウの WM_SIZE メッセージを捕まえて配置を自動化していた。 ここでは、クライアント領域の任意の矩形領域を縦に区切って複数のプッシュボタンを作成して配置し、これらのボタンが発行する WM_COMMAND メッセージを一括して処理することを試みる。 それでは早速コードを書いてみよう。
以下に、BtnGroup01.dpr の全リストを示す。
program BtnGroup01;
uses
windows,
messages,
UtilFunc in 'UtilFunc.pas',
UtilClass in 'UtilClass.pas',
GDIObject in 'GDIObject.pas',
APIWindow in 'APIWindow.pas',
APIControl in 'APIControl.pas';
//------------------------------------------------------------
// Global Types, Constants and Variables
//------------------------------------------------------------
var
MainWnd: TSDIMainWindow;
Dimension : TRect;
Interval:integer;
Handles:TIntegerList;
ID_GROUPBTN: integer = 10000;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
function CreateBtn:HWND;
begin
result := CreateWindow('BUTTON',
'GroupBtn',
WS_CHILD or WS_VISIBLE or
BS_PUSHBUTTON,
0,0,0,0,
MainWnd.Handle,
ID_GROUPBTN,
hInstance,nil);
inc(ID_GROUPBTN);
end;
procedure ArrangeBtn;
var
i: integer;
x,y,w,h: integer;
begin
if Handles.Count = 0 then exit;
x := Dimension.Left;
w := Dimension.Right;
h := (Dimension.Bottom - Interval*(Handles.Count-1)) div Handles.Count;
for i := 0 to Handles.Count-1 do begin
y := Dimension.Top + (h+Interval)*i;
MoveWindow(Handles[i],x,y,w,h,true);
end;
end;
procedure SetCaptions(indx:integer;cp:string);
begin
if (indx > -1) and (indx < Handles.Count) then
SetWindowText(Handles[indx],PChar(cp));
end;
procedure AddBtn;
begin
Handles.Add(CreateBtn);
ArrangeBtn;
end;
procedure RemoveBtn;
var
FH: TIntegerList;
i:integer;
begin
FH := TIntegerList.Create(0,10);
for i := 0 to Handles.Count-2 do FH.Add(Handles[i]);
DestroyWindow(Handles[Handles.Count-1]);
FH.CopyTo(Handles);
FH.Free;
end;
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure BtnCommand(Msg:TMessage);
var
BtnNo:integer;
begin
if (Msg.WParamHi = BN_CLICKED) then begin
BtnNo := Handles.Search(Msg.LParam);
case BtnNo of
0:begin
Dimension.Left := Dimension.Left +10;
ArrangeBtn;
end;
1:begin
Dimension.Top := Dimension.Top +10;
ArrangeBtn;
end;
2:begin
Dimension.Right := Dimension.Right +10;
ArrangeBtn;
end;
3:begin
Dimension.Bottom := Dimension.Bottom +10;
ArrangeBtn;
end;
4:begin
AddBtn;
SetCaptions(Handles.Count-1,'Added Btn');
ArrangeBtn;
end;
5:begin
RemoveBtn;
ArrangeBtn;
end;
end;
end;
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
Dimension := SetDim(100,10,120,200);
Interval := 2;
Handles := TIntegerList.Create(0,10);
with MainWnd do begin
ClientWidth := 360;
ClientHeight := 300;
Center;
end;
end;
procedure OnTest(hWindow: HWND);
var
i:integer;
begin
for i := 0 to 5 do
Handles.Add(CreateBtn);
SetCaptions(0,'Move X');
SetCaptions(1,'Move Y');
SetCaptions(2,'Inc W');
SetCaptions(3,'Inc H');
SetCaptions(4,'Add a Btn');
SetCaptions(5,'Remove a Btn');
ArrangeBtn;
end;
procedure MainCommand(var Msg: TMessage);
begin
BtnCommand(Msg);
Msg.Msg := MainWnd.Handle;
InitMenuCommand(Msg,OnTest);
end;
procedure MainDestroy(var Msg: TMessage);
begin
Handles.Free;
PostQuitMessage(0);
end;
//------------------------------------------------------------
// Main Procedure
//------------------------------------------------------------
begin
MainWnd := TSDIMainWindow.Create(0,GetExeName);
with MainWnd do begin
OnCreate := MainCreate;
OnDestroy := MainDestroy;
OnCommand := MainCommand;
end;
MainWnd.DoCreate;
Halt(MessageLoopNormal);
end.
実行して、 Test! メニューをクリックすると右図のように6個のボタンが作られる。一番上の 'Move X' のボタンを押すと左に10ピクセル、'Move Y' ボタンでは下に10ピクセル移動する。'Inc W' では幅が、'Inc H' では高さが10ピクセル増加する。'Add a Btn' ではボタンが一つ追加され、'Remove a Btn' ではボタンが一つ削除される。
コードを見てみよう。 グローバル変数として以下のものが宣言されている。
Dimension : TRect; Interval:integer; Handles:TIntegerList; ID_GROUPBTN: integer = 10000;
このうち、Dimension はボタングループ全体が占める矩形領域を、左上の点の座標(Left,Top)と幅と高さ(Right,Bottom)で保持する TRect 型の変数である。 Interval は、配置されるボタンの間隔を保持する。 TIntegerList 型の Handles は、作成されたボタンのハンドルを保持する。その Count プロパティーから個数が分かる。 ID_GROUPBTN は、作成されるボタンのコントロールIDの初期値である。 これらの値は、 MainCreate( ) ハンドラで初期化される。
Dimension := SetDim(100,10,120,200); Interval := 2; Handles := TIntegerList.Create(0,10);
実際にボタンを作っている OnTest( ) ハンドラを見てみよう。
procedure OnTest(hWindow: HWND);
var
i:integer;
begin
for i := 0 to 5 do
Handles.Add(CreateBtn);
SetCaptions(0,'Move X');
SetCaptions(1,'Move Y');
SetCaptions(2,'Inc W');
SetCaptions(3,'Inc H');
SetCaptions(4,'Add a Btn');
SetCaptions(5,'Remove a Btn');
ArrangeBtn;
end;ここでは、CreateBtn 手続きでボタンを作りそのハンドルを Handles に格納している。 そして、キャプションを設定し、実際にボタンを配置している。 CreateBtn 手続きを見てみよう。
function CreateBtn:HWND;
begin
result := CreateWindow('BUTTON',
'GroupBtn',
WS_CHILD or WS_VISIBLE or
BS_PUSHBUTTON,
0,0,0,0,
MainWnd.Handle,
ID_GROUPBTN,
hInstance,nil);
inc(ID_GROUPBTN);
end;ここでは、6−1 ボタンコントロールを使ってみる 節と同じ方法でボタンを CreateWindow( ) APIで作っている。 TAPIButton クラスも使用できるが、メッセージを一括処理したいのでここでは、裸のボタンコントロールを作成している。 キャプションを設定する SetCaption( ) 手続きは、SetWindowText( ) API を使っているだけだ。作ったボタンを配置する ArrangeBtn 手続きを見てみよう。
procedure ArrangeBtn;
var
i: integer;
x,y,w,h: integer;
begin
if Handles.Count = 0 then exit;
x := Dimension.Left;
w := Dimension.Right;
h := (Dimension.Bottom - Interval*(Handles.Count-1)) div Handles.Count;
for i := 0 to Handles.Count-1 do begin
y := Dimension.Top + (h+Interval)*i;
MoveWindow(Handles[i],x,y,w,h,true);
end;
end;ここでは、Dimension と Interval と Handles.Count を参照して矩形領域を縦に格子状に区切り MoveWindow( ) APIを使ってボタンを配置している。
function MoveWindow(hWnd: HWND;
X, Y,
nWidth, nHeight: Integer;
bRepaint: BOOL): BOOL; stdcall;この関数は、左上の点の座標と幅と高さを指定して、ウィンドウを移動する。bRepaint に true を指定すると移動後に再描画する。これで、ボタングループができあがる。 ボタンが押されると 第6章 で示したように、親ウィンドウに WM_COMMAND が発行される。 ここでは、MainCommand( ) ハンドラから BtnCommand( ) にこのメッセージを転送してもらっている。
procedure BtnCommand(Msg:TMessage);
var
BtnNo:integer;
begin
if (Msg.WParamHi = BN_CLICKED) then begin
BtnNo := Handles.Search(Msg.LParam);
case BtnNo of
0:begin
Dimension.Left := Dimension.Left +10;
ArrangeBtn;
end;
1:begin
Dimension.Top := Dimension.Top +10;
ArrangeBtn;
end;
2:begin
Dimension.Right := Dimension.Right +10;
ArrangeBtn;
end;
3:begin
Dimension.Bottom := Dimension.Bottom +10;
ArrangeBtn;
end;
4:begin
AddBtn;
SetCaptions(Handles.Count-1,'Added Btn');
ArrangeBtn;
end;
5:begin
RemoveBtn;
ArrangeBtn;
end;
end;
end;
end;このように、Msg.LParam には、このメッセージを生成したボタンのウィンドウハンドルが設定されているので、 Handles の Search( ) メソッドをつかって何番目のボタンであるかすぐに分かる。このようにして6個のボタンすべてのメッセージをこのハンドラで処理できる。
前節のコードで示した、ウィンドウの任意のクライアント領域の矩形領域を格子状に区切ってプッシュボタンを作って配置し、メッセージを一括処理するクラスを作成しよう。これは、単に前節のコードをクラスにまとめれば良いのですぐに作ることができるが、その前に、次節以降でチェックボックスやラジオボタンもグループ化したいので、 これらのコントロールをグループ化するだけのクラスを作り、それから派生して、実際にコントロールグループのクラスを作成しよう。 このシナリオにしたがって、この節では、 TControlGroup クラスを作成し、これから派生して TBtnGroup クラスを作成しよう。 前節のコードでは、WM_COMMAND メッセージを MainCreate( ) から転送してもらっていたが、 このメッセージを自前で捕まえるために、TControlGroup は TSubClass から派生することにしよう。 そうすると、WM_COMMAND は Dispatcher から自動的に送られ、パスカルオブジェクトは親の廃棄のタイミングで自動的に削除される。
最初に UtilClass.pas に TControlGroup と TBtnGroup クラスを作成しよう。 宣言部を以下に示す。
//------------------ TControlGroup --------------------------------------- TControlGroup = class(TSubClass) private FVertical: Boolean; FDim: TRect; FCtrlCount: integer; FHandles: TIntegerList; FInterval: integer; FFontHandle: HFONT; procedure SetVertical(value:Boolean); procedure SetInterval(value:integer); procedure SetCtrlCount(count: integer);virtual; function GetHandles(i:integer):HWND; procedure SetDimension(value: TRect); function GetXPos: integer; procedure SetXPos(value: integer); function GetYPos: integer; procedure SetYPos(value: integer); function GetWidth: integer; procedure SetWidth(value: integer); function GetHeight: integer; procedure SetHeight(value: integer); function GetEnable: Boolean; procedure SetEnable(value:Boolean); function GetVisible: Boolean; procedure SetVisible(value: Boolean); procedure SetFontHandle(fth:HFONT); protected function CreateCtrl:HWND;virtual; procedure ArrangeCtrl;virtual; public constructor Create(hParent: HWND); override; destructor Destroy; override; property Vertical: Boolean read FVertical write SetVertical; property Dimension : TRect read FDim write SetDimension; property Interval:integer read FInterval write SetInterval; property CtrlCount: integer read FCtrlCount write SetCtrlCount; property Handles[i:integer]:HWND read GetHandles; property XPos: integer read GetXPos write SetXPos; property YPos: integer read GetYPos write SetYPos; property Width: integer read GetWidth write SetWidth; property Height: integer read GetHeight write SetHeight; property Enable: Boolean read GetEnable write SetEnable; property Visible: Boolean read GetVisible write SetVisible; property FontHandle: HFONT read FFontHandle write SetFontHandle; end; //------------------ TBtnGroup ------------------------------------ TBtnGroup = class(TControlGroup) private FOnClick: TNotifyMessage; function GetCaptions(indx:integer):string; procedure SetCaptions(indx:integer;cp:string); protected function CreateCtrl:HWND;override; public procedure DefaultHandler(var AMessage); override; property Captions[i:integer]:string read GetCaptions write SetCaptions; property OnClick: TNotifyMessage read FOnClick write FOnClick; end;
TControlGroup クラスでは、矩形領域を保持する Dimension プロパティーと、その個別のプロパティーとして XPos、YPos、Width、Height をもっている。Handles と Interval プロパティーは前節のコードと同じである。 Verical はデフォルトで true であり、矩形領域に縦にコントロールが配置される。 false にすると横に並んで配置される。 TBtnGroup クラスでは、メッセージを受け取る DefaultHandler( ) とキャプションとイベント OnClick が追加されている。 CreateCtrl メソッドをオーバーライドして実際にプッシュボタンコントロールを作成する。 実装部を以下に示す。
//------------------ TControlGroup ---------------------------------------
var
ID_GROUPCTRL: integer = 10000;
constructor TControlGroup.Create(hParent: HWND);
begin
inherited Create(hParent);
Dispatcher.TrapMessage(WM_COMMAND,self);
FVertical := true;
FCtrlCount := 0;
FInterval := 2;
FHandles := TIntegerList.Create(0,10);
end;
destructor TControlGroup.Destroy;
begin
CtrlCount := 0;
FHandles.Free;
inherited Destroy;
end;
function TControlGroup.CreateCtrl:HWND;
begin
result := 0;
end;
procedure TControlGroup.ArrangeCtrl;
var
i: integer;
x,y,w,h: integer;
begin
if FCtrlCount = 0 then exit;
if FVertical then begin
x := FDim.Left;
w := FDim.Right;
h := (FDim.Bottom - FInterval*(FCtrlCount-1)) div FCtrlCount;
for i := 0 to FCtrlCount-1 do begin
y := FDim.Top + (h+FInterval)*i;
MoveWindow(FHandles[i],x,y,w,h,true);
end;
end else begin
y := FDim.Top;
h := FDim.Bottom;
w := (FDim.Right - FInterval*(FCtrlCount-1)) div FCtrlCount;
for i := 0 to FCtrlCount-1 do begin
x := FDim.Left + (w+FInterval)*i;
MoveWindow(FHandles[i],x,y,w,h,true);
end;
end;
end;
procedure TControlGroup.SetVertical(value:Boolean);
begin
FVertical := value;
ArrangeCtrl;
end;
procedure TControlGroup.SetInterval(value:integer);
begin
FInterval := value;
ArrangeCtrl;
end;
procedure TControlGroup.SetCtrlCount(count: integer);
var
i:integer;
FH: TIntegerList;
begin
if count = FCtrlCount then exit;
if count > FCtrlCount then begin
for i := 1 to count-FCtrlCount do FHandles.Add(CreateCtrl);
FCtrlCount := count;
ArrangeCtrl;
end else
if count > 0 then begin
FH := TIntegerList.Create(0,10);
for i := 0 to count-1 do FH.Add(FHandles[i]);
for i := count to CtrlCount-1 do DestroyWindow(FHandles[i]);
FH.CopyTo(FHandles);
FH.Free;
FCtrlCount := count;
ArrangeCtrl;
end else begin // count = 0 or minus
for i := 0 to FCtrlCount-1 do DestroyWindow(FHandles[i]);
FHandles.Clear;
FCtrlCount := 0;
end;
end;
function TControlGroup.GetHandles(i:integer):HWND;
begin
if i > FHandles.Count-1 then
result := 0
else
result := FHandles[i];
end;
procedure TControlGroup.SetDimension(value: TRect);
begin
FDim := value;
ArrangeCtrl;
end;
function TControlGroup.GetXPos: integer;
begin
result := FDim.Left;
end;
procedure TControlGroup.SetXPos(value: integer);
begin
FDim.Left := value;
ArrangeCtrl;
end;
function TControlGroup.GetYPos: integer;
begin
result := FDim.Top;
end;
procedure TControlGroup.SetYPos(value: integer);
begin
FDim.Top := value;
ArrangeCtrl;
end;
function TControlGroup.GetWidth: integer;
begin
result := FDim.Right;
end;
procedure TControlGroup.SetWidth(value: integer);
begin
FDim.Right := value;
ArrangeCtrl;
end;
function TControlGroup.GetHeight: integer;
begin
result := FDim.Bottom;
end;
procedure TControlGroup.SetHeight(value: integer);
begin
FDim.Bottom := value;
ArrangeCtrl;
end;
function TControlGroup.GetEnable: Boolean;
begin
result := Boolean(IsWindowEnabled(FHandles[0]));
end;
procedure TControlGroup.SetEnable(value:Boolean);
var
i:integer;
begin
for i := 0 to FCtrlCount-1 do EnableWindow(FHandles[i],value);
end;
function TControlGroup.GetVisible: Boolean;
begin
result := Boolean(IsWindowVisible(FHandles[0]));
end;
procedure TControlGroup.SetVisible(value: Boolean);
var
i:integer;
begin
if value then
for i := 0 to FCtrlCount-1 do ShowWindow(FHandles[i],SW_SHOWNORMAL)
else
for i := 0 to FCtrlCount-1 do ShowWindow(FHandles[i],SW_HIDE);
end;
procedure TControlGroup.SetFontHandle(fth:HFONT);
var
i:integer;
begin
FFontHandle := fth;
if FCtrlCount > 0 then
for i := 0 to FCtrlCount-1 do
SendMessage(FHandles[i],WM_SETFONT,fth,1);
end;
//------------------ TBtnGroup ------------------------------------
procedure TBtnGroup.DefaultHandler(var AMessage);
var
AMsg:TMessage;
BtnNo:integer;
begin
AMsg := TMessage(AMessage);
case AMsg.Msg of
WM_COMMAND:
if (AMsg.WParamHi = BN_CLICKED) then begin
BtnNo := FHandles.Search(AMsg.LParam);
if BtnNo <> -1 then
if Assigned(FOnClick) then begin
AMsg.Msg := UINT(self); // Sender
AMsg.WParam := BtnNo;
OnClick(AMsg);
end;
end;
end; // case
end;
function TBtnGroup.CreateCtrl:HWND;
begin
result := CreateWindow('BUTTON',
'GroupBtn',
WS_CHILD or WS_VISIBLE or
BS_PUSHBUTTON,
0,0,100,30,
Parent,
ID_GROUPCTRL,
hInstance,nil);
if FFontHandle <> 0 then
SendMessage(result,WM_SETFONT,FFontHandle,1);
inc(ID_GROUPCTRL);
end;
function TBtnGroup.GetCaptions(indx:integer):string;
var
s: string;
begin
if (indx > -1) and (indx < FCtrlCount) then begin
SetLength(s,50);
SetString(result,PChar(s),GetWindowText(FHandles[indx],PChar(s),50));
end else
result := 'No Index';
end;
procedure TBtnGroup.SetCaptions(indx:integer;cp:string);
begin
if (indx > -1) and (indx < FCtrlCount) then
SetWindowText(FHandles[indx],PChar(cp));
end;TControlGroup のコンストラクタでは、WM_COMMAND メッセージを送ってくれるよう Dispatcher に依頼している。 実際にコントロールを作成しているのは、個数を設定する SetCtrlCount( ) アクセスメソッドで CreateCtrl メソッドを呼び出して作成している。したがって、個数を指定して動的にコントロールの個数を変更できる。 TBtnGroup クラスの DefaultHandler( ) では WM_COMMAND を処理して、何番目のボタンが押されたかを WParam に設定して OnClick イベントを呼び出している。 これでクラスが完成したので、このクラスの作成関数を作ろう。 宣言部を示す。
function CreateBtnGroup(hParent:HWND;Vert:Boolean; x,y,cx,cy: integer;
Btns:integer;sOnClick:TNotifyMessage): TBtnGroup;実装部を以下に示す。
function CreateBtnGroup(hParent:HWND;Vert:Boolean; x,y,cx,cy: integer;
Btns:integer;sOnClick:TNotifyMessage): TBtnGroup;
begin
result := TBtnGroup.Create(hParent);
with result do begin
FVertical := Vert;
FDim := SetDim(x,y,cx,cy);
OnClick := sOnClick;
CtrlCount := Btns;
end;
end;それでは、BtnGroup02.dpr にこのクラスをテストするプログラムを書こう。 以下に全リストを示す。
program BtnGroup02;
uses
windows,
messages,
UtilFunc in 'UtilFunc.pas',
UtilClass in 'UtilClass.pas',
GDIObject in 'GDIObject.pas',
APIWindow in 'APIWindow.pas',
APIControl in 'APIControl.pas';
//------------------------------------------------------------
// Global Types, Constants and Variables
//------------------------------------------------------------
var
MainWnd: TSDIMainWindow;
BG1,BG2: TBtnGroup;
ft: TAPICFont;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure BG1Click(var Msg: TMessage);
begin
case Msg.WParam of
0: BG1.XPos := BG1.XPos + 10;
1: BG1.YPos := BG1.YPos + 10;
2: BG1.Width := BG1.Width + 10;
3: BG1.Height := BG1.Height + 10;
4: BG1.Interval := BG1.Interval + 2;
5: BG1.CtrlCount := BG1.CtrlCount + 1;
6: BG1.CtrlCount := BG1.CtrlCount - 1;
7: BG1.FontHandle := ft.Handle;
8: BG1.Free;
end;
end;
procedure BG2Click(var Msg: TMessage);
begin
case Msg.WParam of
0: MainWnd.Color := clrBtnFace;
1: MainWnd.Color := clrRed;
2: MainWnd.Color := clrGreen;
3: MainWnd.Color := clrBlue;
end;
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
BG1 := CreateBtnGroup(hWindow,true,10,50,150,300,9,BG1Click);
BG1.Captions[0] := 'Change XPos';
BG1.Captions[1] := 'Change YPos';
BG1.Captions[2] := 'Change Width';
BG1.Captions[3] := 'Change Height';
BG1.Captions[4] := 'Change Interval';
BG1.Captions[5] := 'Add a Button';
BG1.Captions[6] := 'Remove a Button';
BG1.Captions[7] := 'SetFont';
BG1.Captions[8] := 'SelfDestroy';
BG2 := CreateBtnGroup(hWindow,false,10,5,350,30,4,BG2Click);
BG2.Captions[0] := 'BtnFace';
BG2.Captions[1] := 'Red';
BG2.Captions[2] := 'Green';
BG2.Captions[3] := 'Blue';
ft := TAPICFont.Create;
with ft do begin
Name := 'MS P明朝';
Size := 11;
GetHandle;
end;
with MainWnd do begin
ClientWidth := 400;
ClientHeight := 450;
Center;
end;
end;
procedure OnTest(hWindow: HWND);
begin
BG1.Enable := not BG1.Enable;
BG2.Visible := not BG2.Visible;
end;
procedure MainCommand(var Msg: TMessage);
begin
Msg.Msg := MainWnd.Handle;
InitMenuCommand(Msg,OnTest);
end;
procedure MainDestroy(var Msg: TMessage);
begin
ft.free;
PostQuitMessage(0);
end;
//------------------------------------------------------------
// Main Procedure
//------------------------------------------------------------
begin
MainWnd := TSDIMainWindow.Create(0,GetExeName);
with MainWnd do begin
OnCreate := MainCreate;
OnDestroy := MainDestroy;
OnCommand := MainCommand;
end;
MainWnd.DoCreate;
Halt(MessageLoopNormal);
end.
実行して、 'SetFont' ボタンを押したときの様子を右図に示す。 縦配置の BG1 では、キャプション通りの動作を確認できるだろう。 横配置の BG2 では、ウィンドウの背景色を変更している。 Test! では、BG1 の有効性と BG2 の可視性を反転している。この例のように、多数のボタンを簡単に作成でき、メッセージを一括処理できることは、特にコードベースのプログラミングでは非常にありがたい事である。
この節では、チェックボックスグループのクラス TCheckGroup を TControlGroup から派生して作成しよう。 チェックボックスは、プッシュボタンと同じく OnClick イベントを発生するが、チェック状態の有無を保持している事だけがプッシュボタンとの相違点である。 そこで、チェック状態を取得・設定するプロパティー Checked が追加されることだけが前節との違いである。 それでは早速 TCheckGroup クラスを作成しよう。
最初に UtilClass.pas に TCheckGroup クラスを作成しよう。 宣言部を以下に示す。
//------------------ TCheckGroup ------------------------------------ TCheckGroup = class(TControlGroup) private FOnClick: TNotifyMessage; function GetChecked(indx:integer):Boolean; procedure SetChecked(indx:integer;value:Boolean); function GetCaptions(indx:integer):string; procedure SetCaptions(indx:integer;cp:string); protected function CreateCtrl:HWND;override; public procedure DefaultHandler(var AMessage); override; property Checked[i:integer]:Boolean read GetChecked write SetChecked; property Captions[i:integer]:string read GetCaptions write SetCaptions; property OnClick: TNotifyMessage read FOnClick write FOnClick; end;
ここでは、先述のように TBtnGroup クラスに Checked プロパティーが加わっているだけである。 実装部を以下に示す。
//------------------ TCheckGroup ------------------------------------
procedure TCheckGroup.DefaultHandler(var AMessage);
var
AMsg:TMessage;
BtnNo:integer;
begin
AMsg := TMessage(AMessage);
case AMsg.Msg of
WM_COMMAND:
if (AMsg.WParamHi = BN_CLICKED) then begin
BtnNo := FHandles.Search(AMsg.LParam);
if BtnNo <> -1 then
if Assigned(FOnClick) then begin
AMsg.Msg := UINT(self); // Sender
AMsg.WParam := BtnNo;
OnClick(AMsg);
end;
end;
end; // case
end;
function TCheckGroup.CreateCtrl:HWND;
begin
result := CreateWindow('BUTTON',
'GroupCheck',
WS_CHILD or WS_VISIBLE or
BS_AUTOCHECKBOX,
0,0,100,30,
Parent,
ID_GROUPCTRL,
hInstance,nil);
if FFontHandle <> 0 then
SendMessage(result,WM_SETFONT,FFontHandle,1);
inc(ID_GROUPCTRL);
end;
function TCheckGroup.GetChecked(indx:integer):Boolean;
begin
if (indx > -1) and (indx < FCtrlCount) then
result := Boolean(SendMessage(FHandles[indx],BM_GETCHECK,0,0)=1)
else
result := false;
end;
procedure TCheckGroup.SetChecked(indx:integer;value:Boolean);
begin
if (indx > -1) and (indx < FCtrlCount) then
if value then
SendMessage(FHandles[indx],BM_SETCHECK,BST_CHECKED,0)
else
SendMessage(FHandles[indx],BM_SETCHECK,BST_UNCHECKED,0);
end;
function TCheckGroup.GetCaptions(indx:integer):string;
var
s: string;
begin
if (indx > -1) and (indx < FCtrlCount) then begin
SetLength(s,50);
SetString(result,PChar(s),GetWindowText(FHandles[indx],PChar(s),50));
end else
result := 'No Index';
end;
procedure TCheckGroup.SetCaptions(indx:integer;cp:string);
begin
if (indx > -1) and (indx < FCtrlCount) then
SetWindowText(FHandles[indx],PChar(cp));
end;ここでは、Checked プロパティーのアクセスメソッドで BM_GETCHECK と BM_SETCHECK メッセージを SendMessage( ) で送って取得・設定している。 これでクラスが完成したのでインスタンス作成関数を作ろう。 宣言部を以下に示す。
function CreateCheckGroup(hParent:HWND;Vert:Boolean; x,y,cx,cy: integer;
Chcks:integer;sOnClick:TNotifyMessage): TCheckGroup;実装部を以下に示す。
function CreateCheckGroup(hParent:HWND;Vert:Boolean; x,y,cx,cy: integer;
Chcks:integer;sOnClick:TNotifyMessage): TCheckGroup;
begin
result := TCheckGroup.Create(hParent);
with result do begin
FVertical := Vert;
FDim := SetDim(x,y,cx,cy);
OnClick := sOnClick;
CtrlCount := Chcks;
end;
end;それでは、このクラスをテストするプログラムを CheckGroup.dpr に作成しよう。以下に全リストを示す。
program CheckGroup;
uses
windows,
messages,
UtilFunc in 'UtilFunc.pas',
UtilClass in 'UtilClass.pas',
GDIObject in 'GDIObject.pas',
APIWindow in 'APIWindow.pas',
APIControl in 'APIControl.pas';
//------------------------------------------------------------
// Global Types, Constants and Variables
//------------------------------------------------------------
var
MainWnd: TSDIMainWindow;
L1:TAPILabel;
CG1,CG2: TCheckGroup;
GB: TAPIGroupBox;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
procedure CheckState;
var
s:string;
i:integer;
begin
s := ' Check State '+#13#13;
for i := 0 to 4 do
if CG1.Checked[i] then
s := s+' '+AIntToStr(i)+' : Yes'+#13
else
s := s+' '+AIntToStr(i)+' : No'+#13;
L1.Text := s;
end;
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure CG1Click(var Msg: TMessage);
begin
CheckState;
end;
procedure CG2Click(var Msg: TMessage);
var
i:integer;
begin
for i := 0 to 4 do CG1.Checked[i] := CG2.Checked[i];
CheckState;
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
i:integer;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
L1 := Createlabel(hWindow,10,5,120,160,'');
CG1 := CreateCheckGroup(hWindow,true,160,30,80,120,5,CG1Click);
for i := 0 to 4 do CG1.Captions[i] := 'Check '+AIntToStr(i);
GB := CreateGroupBox(hWindow,260,5,120,160,'Check Group');
CG2 := CreateCheckGroup(GB.Handle,true,15,25,80,120,5,CG2Click);
for i := 0 to 4 do CG2.Captions[i] := 'Check '+AIntToStr(i);
with MainWnd do begin
ClientWidth := 400;
ClientHeight := 180;
Center;
end;
end;
procedure OnTest(hWindow: HWND);
begin
MessageBeep($FFFFFFFF);
end;
procedure MainCommand(var Msg: TMessage);
begin
Msg.Msg := MainWnd.Handle;
InitMenuCommand(Msg,OnTest);
end;
procedure MainDestroy(var Msg: TMessage);
begin
PostQuitMessage(0);
end;
//------------------------------------------------------------
// Main Procedure
//------------------------------------------------------------
begin
MainWnd := TSDIMainWindow.Create(0,GetExeName);
with MainWnd do begin
OnCreate := MainCreate;
OnDestroy := MainDestroy;
OnCommand := MainCommand;
end;
MainWnd.DoCreate;
Halt(MessageLoopNormal);
end.
実行すると、右図のようにメインウィンドウとグループボックスを親とした二つのチェックグループが作成される。 マウスでクリックするたびに真ん中のチェックグループのチェック状態がラベルに表示される。 右側のチェックグループをクリックするとチェック状態が真ん中のチェックグループにコピーされる。
ラジオボタンのグループは、相互排他的にチェックマークを付けられるので、チェックボックスよりは幾分複雑なコントロールである。 7−2 ラジオボタン 節では、EnumChildWindows( ) APIを使って同じグループに属するラジオボタンを検索していたが、 ここでは、Handles プロパティーで同じグループに属するすべてのハンドルを管理しているので、コードで相互排他的にチェックマークを付けるのは簡単である。 それでは早速作成しよう。
最初に、通常のラジオボタンをグループ化した TRadioGroup クラスと、それから派生してプッシュライクなラジオボタンをグループ化した TPushRadioGroup クラスを UtilClass.pas に作成する。 宣言部を以下に示す。
//------------------ TRadioGroup ------------------------------------ TRadioGroup = class(TControlGroup) private FCheckIndex:integer; FOnClick: TNotifyMessage; function GetCaptions(indx:integer):string; procedure SetCaptions(indx:integer;cp:string); protected function CreateCtrl:HWND;override; procedure UnCheckAll; public procedure DefaultHandler(var AMessage); override; procedure SetCheck(indx:integer); property CheckIndex:integer read FCheckIndex; property Captions[i:integer]:string read GetCaptions write SetCaptions; property OnClick: TNotifyMessage read FOnClick write FOnClick; end; //------------------ TPushRadioGroup -------------------------------- TPushRadioGroup = class(TRadioGroup) protected function CreateCtrl:HWND;override; end;
SetCheck メソッドは、特定のインデックスのラジオボタンをチェックする。CheckIndex プロパティーは、グループの中で現在チェックされているボタンのインデックスを保持している。 実装部を以下に示す。
//------------------ TRadioGroup ------------------------------------
procedure TRadioGroup.DefaultHandler(var AMessage);
var
AMsg:TMessage;
BtnNo:integer;
begin
AMsg := TMessage(AMessage);
case AMsg.Msg of
WM_COMMAND:
if (AMsg.WParamHi = BN_CLICKED) then begin
BtnNo := FHandles.Search(AMsg.LParam);
if BtnNo <> -1 then begin
SetCheck(BtnNo);
if Assigned(FOnClick) then begin
AMsg.Msg := UINT(self); // Sender
AMsg.WParam := BtnNo;
OnClick(AMsg);
end;
end;
end;
end; // case
end;
function TRadioGroup.CreateCtrl:HWND;
begin
result := CreateWindow('BUTTON',
'GroupRadio',
WS_CHILD or WS_VISIBLE or
BS_RADIOBUTTON,
0,0,100,30,
Parent,
ID_GROUPCTRL,
hInstance,nil);
if FFontHandle <> 0 then
SendMessage(result,WM_SETFONT,FFontHandle,1);
inc(ID_GROUPCTRL);
end;
procedure TRadioGroup.UnCheckAll;
var
i:integer;
begin
for i := 0 to CtrlCount-1 do
SendMessage(FHandles[i],BM_SETCHECK,0,0);
end;
procedure TRadioGroup.SetCheck(indx:integer);
begin
if (indx > -1) and (indx < FCtrlCount) then begin
UnCheckAll;
SendMessage(FHandles[indx],BM_SETCHECK,1,0);
FCheckIndex := indx;
end;
end;
function TRadioGroup.GetCaptions(indx:integer):string;
var
s: string;
begin
if (indx > -1) and (indx < FCtrlCount) then begin
SetLength(s,50);
SetString(result,PChar(s),GetWindowText(FHandles[indx],PChar(s),50));
end else
result := 'No Index';
end;
procedure TRadioGroup.SetCaptions(indx:integer;cp:string);
begin
if (indx > -1) and (indx < FCtrlCount) then
SetWindowText(FHandles[indx],PChar(cp));
end;
//------------------ TPushRadioGroup --------------------------------
function TPushRadioGroup.CreateCtrl:HWND;
begin
result := CreateWindow('BUTTON',
'GroupRadio',
WS_CHILD or WS_VISIBLE or
BS_RADIOBUTTON or BS_PUSHLIKE,
0,0,100,30,
Parent,
ID_GROUPCTRL,
hInstance,nil);
if FFontHandle <> 0 then
SendMessage(result,WM_SETFONT,FFontHandle,1);
inc(ID_GROUPCTRL);
end;ここでは、UnCheckAll で全てのチェックをはずし、 SetCheck( ) で特定のラジオボタンのみチェックマークを付けている。 TPushRadioGroup クラスでは、CreateCtrl をオーバーライドしてコントロールスタイルに BS_PUSHLIKE を追加しているだけである。 両クラスのインスタンス生成関数を作ろう。 宣言を以下に示す。
function CreateRadioGroup(hParent:HWND;Vert:Boolean; x,y,cx,cy: integer;
Radios:integer;sOnClick:TNotifyMessage): TRadioGroup;
function CreatePushRadioGroup(hParent:HWND;Vert:Boolean; x,y,cx,cy: integer;
Radios:integer;sOnClick:TNotifyMessage): TPushRadioGroup;実装部を以下に示す。
function CreateRadioGroup(hParent:HWND;Vert:Boolean; x,y,cx,cy: integer;
Radios:integer;sOnClick:TNotifyMessage): TRadioGroup;
begin
result := TRadioGroup.Create(hParent);
with result do begin
FVertical := Vert;
FDim := SetDim(x,y,cx,cy);
OnClick := sOnClick;
CtrlCount := Radios;
end;
end;
function CreatePushRadioGroup(hParent:HWND;Vert:Boolean; x,y,cx,cy: integer;
Radios:integer;sOnClick:TNotifyMessage): TPushRadioGroup;
begin
result := TPushRadioGroup.Create(hParent);
with result do begin
FVertical := Vert;
FDim := SetDim(x,y,cx,cy);
OnClick := sOnClick;
CtrlCount := Radios;
end;
end;それでは、RadioGroup.dpr にこれらのクラスをテストするプログラムを作ろう。 以下に全リストを示す。
program RadioGroup;
uses
windows,
messages,
UtilFunc in 'UtilFunc.pas',
UtilClass in 'UtilClass.pas',
GDIObject in 'GDIObject.pas',
APIWindow in 'APIWindow.pas',
APIControl in 'APIControl.pas';
//------------------------------------------------------------
// Global Types, Constants and Variables
//------------------------------------------------------------
var
MainWnd: TSDIMainWindow;
L1:TAPILabel;
RG1: TRadioGroup;
GB: TAPIGroupBox;
PR1: TPushRadioGroup;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
procedure CheckState;
var
s:string;
begin
s := ' Check State '+#13#13;
s := s+' CheckIndex = '+AIntToStr(RG1.CheckIndex);
L1.Text := s;
end;
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure RG1Click(var Msg: TMessage);
begin
PR1.setCheck(Msg.WParam);
CheckState;
end;
procedure PR1Click(var Msg: TMessage);
begin
RG1.setCheck(Msg.WParam);
CheckState;
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
i:integer;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
L1 := Createlabel(hWindow,10,70,170,100,'');
GB := CreateGroupBox(hWindow,200,50,120,160,'Radio Group');
RG1 := CreateRadioGroup(GB.Handle,true,15,25,80,120,5,RG1Click);
for i := 0 to 4 do RG1.Captions[i] := 'Radio '+AIntToStr(i);
RG1.SetCheck(1);
PR1 := CreatePushRadioGroup(hWindow,false,5,5,430,30,5,PR1Click);
PR1.interval := 2;
for i := 0 to 4 do PR1.Captions[i] := 'PushR '+AIntToStr(i);
RG1.SetCheck(1);
with MainWnd do begin
ClientWidth := 450;
ClientHeight := 250;
Center;
end;
end;
procedure OnTest(hWindow: HWND);
begin
//MessageBeep($FFFFFFFF);
RG1.SetCheck(2);
CheckState;
end;
procedure MainCommand(var Msg: TMessage);
begin
Msg.Msg := MainWnd.Handle;
InitMenuCommand(Msg,OnTest);
end;
procedure MainDestroy(var Msg: TMessage);
begin
PostQuitMessage(0);
end;
//------------------------------------------------------------
// Main Procedure
//------------------------------------------------------------
begin
MainWnd := TSDIMainWindow.Create(0,GetExeName);
with MainWnd do begin
OnCreate := MainCreate;
OnDestroy := MainDestroy;
OnCommand := MainCommand;
end;
MainWnd.DoCreate;
Halt(MessageLoopNormal);
end.
実行すると、右図のように縦配置の通常ラジオボタングループと横配置のプッシュラジオグループが作成される。 どちらをクリックしても他方にチェック状態がコピーされ、そのときのチェックインデックスがラベルに表示される。
本章では、 プッシュボタン、チェックボックス、ラジオボタンをグループ化して生成・配置するクラスを TSubClass から派生して作成した。 グループ化する事により、メッセージ処理が一括して行えるようになった。この他に、エディットやエディットとラベルの組み合わせなどをグループ化したクラスも有益だろう。 これらの作成は、読者にお任せする。
ここまでのプログラムファイル