Delphi のコンポーネントパレットの Standard ページで最も頻繁に使われるコンポーネントの一つに TPanel クラスがある。 前章では、本稿ではじめて汎用ウィンドウクラスとして TSDIMainWindow クラスを作成した。 パネルは、VCLでは幾つかのイベントハンドラが装備されているが、本来、他のコントロールを配置するためのコンテナウィンドウである。ここでは、メッセージを処理するイベントハンドラを持たない、本来のコンテナウィンドウとしてのパネルを汎用子ウィンドウとして作成し、そのラッパークラスを作成する。また、メインウィンドウなどの親ウィンドウのサイズがユーザによって変更されたときに、パネルや他のコントロールを自動的に配置し直すクラスを作成してみよう。
| 第11章 パネルとアレンジャー |
| 11−1 パネルをコードでつくる |
| 11−2 パネルのラッパークラス |
| 11−3 パネルの配置 |
| 11−4 グリッドアレンジャークラス |
| 11−5 ボーダーアレンジャークラス |
| 11−6 メッセージトラッパ |
| 11−7 まとめ |
パネルを汎用の子ウィンドウとして作成しよう。 子ウィンドウであるから、ウィンドウオブジェクトは親ウィンドウが破棄されるときに自動的に破棄される。したがって、第1章で説明した Generic プログラムでは必須だった WM_DESTROY メッセージも処理する必要がないので、ウィンドウ関数ははじめから DefWindowProc( ) を指定して作ろう。 それでは早速作り始めよう。
以下に APIPanel01.dpr の全リストを示す。
program APIPanel01;
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;
hPanel: HWND;
Btn1: TAPIButton;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
function CreatePanel(hParent:HWND):HWND;
var
wc: TWndClass;
begin
wc.lpszClassName := 'APIPanel';
wc.lpfnWndProc := @DefWindowProc;
wc.style := CS_VREDRAW or CS_HREDRAW;
wc.hInstance := hInstance;
wc.hIcon := 0;
wc.hCursor := LoadCursor(0,IDC_ARROW);
wc.hbrBackground := (COLOR_BTNFACE+1);
wc.lpszMenuName := nil;
wc.cbClsExtra := 0;
wc.cbWndExtra := 0;
RegisterClass( wc );
result := CreateWindowEx(WS_EX_CONTROLPARENT,
'APIPanel',
'APIPanel',
WS_CHILD or WS_VISIBLE or WS_CLIPSIBLINGS,
50,50,120,100,
hParent,
0,
hInstance,
nil);
end;
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure Btn1Click(var Msg: TMessage);
begin
ChangeWindowPos(hPanel,10,10);
ChangeWindowSize(hPanel,200,200);
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
hPanel := CreatePanel(MainWnd.Handle);
Btn1 := CreateButton(hPanel,10,10,100,40,'Button 1',Btn1Click);
with MainWnd do begin
ClientWidth := 360;
ClientHeight := 260;
Color := clrWhite;
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.
実行すると、パネルとその上にボタンがつくられる。見やすいように、メインウィンドウの背景色を白にしている。ボタンをクリックするとパネルの位置とサイズが変更される。このときの様子を右図に示した。
コードを見てみよう。パネルは、ヘルパ関数 CreatePanel( ) をつかって作成される。 ここでは、クラス属性としてウィンドウ関数を DefWindowProc( ) 、背景色をシステムカラー BtnFace としている。 また、ウィンドウ属性として、拡張スタイルに WS_EX_CONTROLPARENT 、スタイルに WS_CHILD or WS_VISIBLE or WS_CLIPSIBLINGS を指定している。 これで、パネルの原型がつくられた。次節では、このようなパネルのラッパークラスを作成しよう。
前節でコードでつくったパネルは、ウィンドウ関数に DefWindowProc( ) を設定した汎用子ウィンドウであった。このラッパークラスを前章で作成した汎用ウィンドウの基本属性をもつ TAPIWindow3 から派生して APIWindow.pas に作ろう。
最初に、APIWindow.pas に TAPIPanel クラスを作成しよう。宣言部を以下に示す。
//---------- TAPIPanel ---------------------------------------------- TEdgeStyle = (esNone,esRaised,esSunken); TAPIPanel = class(TAPIWindow3) private FOuterEdge: TEdgeStyle; FInnerEdge: TEdgeStyle; FEdgeWidth: integer; function GetDimension: TRect; procedure SetDimension(value: TRect); function GetXPos: integer; procedure SetXPos(value: integer); function GetYPos: integer; procedure SetYPos(value: integer); procedure SetOuterEdge(value: TEdgeStyle); procedure SetInnerEdge(value: TEdgeStyle); procedure SetEdgeWidth(value: integer); protected procedure APIClassParams(var WC:TWndClass); override; procedure APICreateParams(var CP:TCreateParamsEx); override; public procedure DefaultHandler(var Message); override; property Dimension : TRect read GetDimension write SetDimension; property XPos: integer read GetXPos write SetXPos; property YPos: integer read GetYPos write SetYPos; property OuterEdge: TEdgeStyle read FOuterEdge write SetOuterEdge; property InnerEdge: TEdgeStyle read FInnerEdge write SetInnerEdge; property EdgeWidth: integer read FEdgeWidth write SetEdgeWidth; end; function CreatePanel(hParent:HWND;x,y,cx,cy:integer):TAPIPanel;
ここでは APIClassParams( ) と APICreateParams( ) をオーバーライドして、クラス属性とウィンドウ属性を書き換える。 位置とサイズのプロパティーとして Dimension 、XPos 、YPos を設けている。VCLの TPanel のエッジスタイルをまねるために OuterEdge 、InnerEdge 、EdgeWidth のプロパティーを設けた。 実装部を以下に示す。
//--------------- TAPIPanel ----------------------------------------------
function PanelWndProc(hWindow: HWND; Msg: UINT; WParam: WPARAM;
LParam: LPARAM): LRESULT; stdcall;
var
WPMsg: TMessage;
Wnd: TAPIPanel;
pCS: PCreateStruct;
begin
Result := 0;
if Msg = WM_CREATE then begin
pCS := Pointer(LParam);
SetProp(hWindow,'OBJECT',integer(pCS^.lpCreateParams));
TAPIPanel(pCS^.lpCreateParams).FHandle := hWindow;
end;
Wnd := TAPIPanel(GetProp(hWindow,'OBJECT'));
case Msg of
{------------------ WM_PAINT,WMERASEBKGND ---------------------}
WM_PAINT,WM_ERASEBKGND: begin
WPMsg.Msg := Msg;
WPMsg.WParam := WParam;
WPMsg.LParam := LParam;
WPMsg.Result := Result;
if Assigned(Wnd) then begin
Wnd.Dispatch(WPMsg);
if WPMsg.result <> 0 then Result := WPMsg.result;
end;
end;
{------------------ WM_DESTROY --------------------------------}
WM_DESTROY: begin
if Assigned(Wnd) then begin
Wnd.Free;
end;
end;
else Result := DefWindowProc( hWindow, Msg, wParam, lParam );
end; //case
end;
procedure TAPIPanel.APIClassParams(var WC:TWndClass);
begin
WC.lpszClassName := 'APIPanel';
WC.lpfnWndProc := @PanelWndProc;
WC.style := CS_VREDRAW or CS_HREDRAW;
WC.hInstance := hInstance;
WC.hIcon := 0;
WC.hCursor := LoadCursor(0,IDC_ARROW);
WC.hbrBackground := ( COLOR_BTNFACE+1 );
WC.lpszMenuName := nil;
WC.cbClsExtra := 0;
WC.cbWndExtra := 0;
FOuterEdge := esRaised;
FInnerEdge := esNone;
FEdgeWidth := 2;
If Assigned(FOnClassParams) then FOnClassParams(WC);
end;
procedure TAPIPanel.APICreateParams(var CP:TCreateParamsEx);
begin
CP.dwExStyle := WS_EX_CONTROLPARENT;
CP.lpClassName := 'APIPanel';
CP.lpWindowName := 'APIPanel';
CP.dwStyle := WS_CHILD or WS_VISIBLE or WS_CLIPSIBLINGS;
CP.X := 0;
CP.Y := 0;
CP.nWidth := 0;
CP.nHeight := 0;
CP.hWndParent := FParent;
CP.hMenu := 0;
CP.hInstance := hInstance;
CP.lpParam := self;
If Assigned(FOnWindowParams) then FOnWindowParams(CP);
end;
procedure TAPIPanel.DefaultHandler(var Message);
var
AMsg:TMessage;
r: TRect;
DC: HDC;
ps: TPaintStruct;
begin
AMsg := TMessage(message);
case AMsg.Msg of
WM_PAINT:begin
DC := BeginPaint(FHandle,ps);
GetClientRect(FHandle,r);
case FOuterEdge of
esRaised:DrawEdge(DC,r,BDR_RAISEDOUTER or BDR_RAISEDINNER,
BF_RECT);
esSunken:DrawEdge(DC,r,BDR_SUNKENOUTER or BDR_SUNKENINNER,
BF_RECT);
end;
InflateRect(r,-FEdgeWidth,-FEdgeWidth);
case FInnerEdge of
esRaised:DrawEdge(DC,r,BDR_RAISEDOUTER or BDR_RAISEDINNER,
BF_RECT);
esSunken:DrawEdge(DC,r,BDR_SUNKENOUTER or BDR_SUNKENINNER,
BF_RECT);
end;
EndPaint(FHandle,ps);
end;
WM_ERASEBKGND:TMessage(message).Result := EraseBkgnd(FHandle,
FColor,AMsg.WParam);
end; // case
end;
function TAPIPanel.GetDimension: TRect;
begin
result := SetDim(XPos,YPos,Width,Height);
end;
procedure TAPIPanel.SetDimension(value: TRect);
begin
MoveWindow(FHandle,value.left, value.top,
value.right, value.bottom, true);
end;
function TAPIPanel.GetXPos: integer;
var
r: TRect;
p: TPoint;
begin
GetWindowRect(FHandle,r);
p.x := r.Left; p.y := r.Top;
ScreenToClient(FParent,p);
result := p.x;
end;
procedure TAPIPanel.SetXPos(value: integer);
begin
ChangeWindowPos(FHandle,value,YPos);
end;
function TAPIPanel.GetYPos: integer;
var
r: TRect;
p: TPoint;
begin
GetWindowRect(FHandle,r);
p.x := r.Left; p.y := r.Top;
ScreenToClient(FParent,p);
result := p.y;
end;
procedure TAPIPanel.SetYPos(value: integer);
begin
ChangeWindowPos(FHandle,XPos,value);
end;
procedure TAPIPanel.SetOuterEdge(value: TEdgeStyle);
begin
FOuterEdge := value;
InvalidateRect(FHandle,nil,true);
end;
procedure TAPIPanel.SetInnerEdge(value: TEdgeStyle);
begin
FInnerEdge := value;
InvalidateRect(FHandle,nil,true);
end;
procedure TAPIPanel.SetEdgeWidth(value: integer);
begin
FEdgeWidth := value;
InvalidateRect(FHandle,nil,true);
end;
function CreatePanel(hParent:HWND;x,y,cx,cy:integer):TAPIPanel;
begin
result := TAPIPanel.Create(hParent,'');
result.DoCreate;
result.Dimension := SetDim(x,y,cx,cy);
end;APIClassParams( ) では、ウィンドウ関数に PanelWndProc( ) を指定した他は前節でコードで作ったパネルと同じクラス属性を設定している。 敬称メソッドを呼び出さずに、完全に書き換えている。 APICreateParams( ) も前節と同じ設定である。 PanelWndProc( ) では、WM_CREATE のときにウィンドウオブジェクトにプロパティーを設定し、ハンドルプロパティーを初期化している。 また WM_PAINT と WM_ERASEBKGND メッセージのときだけパスカルオブジェクトに配送している。 そして、WM_DESTROY のときにパスカルオブジェクトを破棄している。
DefaultHandler( ) では、WM_PAINT の処理として5−2節でスピードボタンの外観を描くために使った DrawEdge( ) APIを使って、3D的な表示をしている。 WM_ERASEBKGND の処理では、 Color プロパティーを使って背景色を描画している。
TAPIPanel クラスをテストするプログラムを APIPane02.dpr に書こう。以下に全リストを示す。
program APIPanel02;
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;
PL1, PL2, PL3, PL4, PL5: TAPIPanel;
Btn1: TAPIButton;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure Btn1Click(var Msg: TMessage);
begin
DestroyWindow(PL4.Handle);
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
PL1 := CreatePanel(hWindow,10,10,100,100);
PL2 := CreatePanel(hWindow,120,10,100,100);
PL3 := CreatePanel(hWindow,230,10,100,100);
PL4 := CreatePanel(hWindow,10,120,200,120);
PL5 := CreatePanel(PL4.Handle,10,10,150,100);
Btn1 := CreateButton(PL5.Handle,20,20,100,40,'APIButton',Btn1Click);
with MainWnd do begin
ClientWidth := 360;
ClientHeight := 260;
Center;
end;
end;
procedure OnTest(hWindow: HWND);
begin
PL1.OuterEdge := esSunken;
PL2.InnerEdge := esSunken;
PL3.OuterEdge := esSunken;
PL3.EdgeWidth := 3;
PL3.InnerEdge := esRaised;
PL4.Color := clrPurple;
PL5.EdgeWidth := 5;
PL5.InnerEdge := esSunken;
PL5.Color := clrGreen;
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.
実行すると、五枚のパネルとボタンが一つ作られる。 Test! メニューを選択すると、 右図のようにエッジスタイルと色が変更される。 ボタンを押すと、 自分がのっているパネルの下の方が削除される。
パネルは、 コントロールを配置するウィンドウである。 もし、ユーザーがメインウィンドウのサイズを変更したとしても、 それに追随してコントロールを配置できることが望ましい。VCLの TPanel には、パネル自身を親ウィンドウに対してどのように配置するのかを決定する TControl から継承した Align プロパティーがある。 この節では、 メインウィンドウの WM_SIZE メッセージを処理して、コードでパネルの配置を処理することを試みる。
Arranger01.dpr の全リストを以下に示す。
program Arranger01;
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;
PL: array[0..3] of TAPIPanel;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure MainSize(var Msg: TMessage);
var
w,h: integer;
begin
w := Msg.LParamLo div 3;
h := Msg.LParamHi div 2;
PL[0].Dimension := SetDim(0,0,w,h);
PL[1].Dimension := SetDim(w,0,w*2,h);
PL[2].Dimension := SetDim(0,h,w*2,h);
PL[3].Dimension := SetDim(w*2,h,w,h);
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
i: integer;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
for i := 0 to 3 do PL[i] := CreatePanel(hWindow,0,0,0,0);
with MainWnd do begin
Trap(WM_SIZE,MainSize);
ClientWidth := 360;
ClientHeight := 260;
Center;
end;
Randomize;
end;
procedure OnTest(hWindow: HWND);
var
i: integer;
begin
for i := 0 to 3 do
PL[i].Color := RGB(Random(256),Random(256),Random(256));
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.

実行すると、左上図のように4枚のパネルが配置される。メインウィンドウのサイズを変更しても相対的な配置は保たれる。Test! メニューを選択するとパネルの色がランダムに変更される。このときの様子を右上図に示した。
MainCreate( ) ハンドラで4枚のパネルが作られている。 Trap( ) メソッドで WM_SIZE メッセージを MainSize( ) ハンドラで処理することを依頼する。 MainSize( ) ハンドラを見てみよう。
procedure MainSize(var Msg: TMessage); var w,h: integer; begin w := Msg.LParamLo div 3; h := Msg.LParamHi div 2; PL[0].Dimension := SetDim(0,0,w,h); PL[1].Dimension := SetDim(w,0,w*2,h); PL[2].Dimension := SetDim(0,h,w*2,h); PL[3].Dimension := SetDim(w*2,h,w,h); end;
WM_SIZE の Msg.LParamLo と Msg.LParamHi からクライアント領域の幅と高さを取り出し、幅を三等分、高さを二等分したものを単位として、各パネルの位置とサイズを決定している。 このような配置方法は JAVA に見られるものでグリッドレイアウトと呼ばれている。 ここでは、レイアウトのかわりにアレンジャーと言うことにしよう。 これで、サイズ変更に追随することができるわけだ。
前節のグリッドアレンジャーの機能を包み込んだクラスを作成してみよう。 親ウィンドウの WM_SIZE メッセージを捕獲してイベントを公開できればよいので、TSubClass から直接派生して、UtilClass.pas に作成することにしよう。
最初に、UTilClass.pas に TGridArranger クラスを作成しよう。 宣言部を以下に示す。
//------------- TGridArranger ---------------------------------------------- TGridArranger = class(TSubClass) private FNumCol: integer; FNumRow: integer; FXMargin: integer; FYMargin: integer; FXSep: integer; FYSep: integer; FOnSize: TNotifyMessage; public constructor Create(hParent: HWND); override; procedure DefaultHandler(var Message);override; function Arrange(Row,Col: integer): TRect; function CenterArrange(Row,Col:integer;Dim: TRect): TRect; property NumCol: integer read FNumCol write FNumCol; property NumRow: integer read FNumRow write FNumRow; property XMargin: integer read FXMargin write FXMargin; property YMargin: integer read FYMargin write FYMargin; property XSep: integer read FXSep write FXSep; property YSep: integer read FYSep write FYSep; property OnSize: TNotifyMessage read FOnSize write FOnSize; end;
NumRow と NumCol プロパティーは、格子状に分割するときの行と列の数である。 XMargin と YMargin は、親ウィンドウの左右と上下の余白の値、XSep と YSep は、配置されるコントロール同士の隙間の幅と高さである。 Arrange( ) メソッドは、行と列を指定するとその Dimension を返してくれる。 CenterArrange( ) メソッドは、コントロールのサイズを変えずに位置だけを格子の中心に揃えるような Dimension を返してくれる。 これらのメソッドは、OnSize イベントハンドラの中で使われる。
実装部を以下に示す。
//------------- TGridArranger ----------------------------------------------
constructor TGridArranger.Create(hParent: HWND);
begin
inherited Create(hParent);
Dispatcher.TrapMessage(WM_SIZE,self);
FNumCol := 2;
FNumRow := 2;
FXMargin := 0;
FYMargin := 0;
FXSep := 1;
FYSep := 1;
end;
procedure TGridArranger.DefaultHandler(var Message);
var
AMsg: TMessage;
begin
AMsg := TMessage(Message);
case AMsg.Msg of
WM_SIZE:begin
if Assigned(FOnSize) then begin
AMsg.Msg := UINT(self); // sender
OnSize(AMsg);
end;
end;
end; // case
end;
function TGridArranger.Arrange(Row,Col: integer): TRect;
var
CR: TRect;
begin
GetClientRect(FParent,CR);
result.Right := (CR.Right-2*FXMargin-FXSep*(FNumCol-1)) div FNumCol;
result.Bottom := (CR.Bottom-2*FYMargin-FYSep*(FNumRow-1)) div FNumRow;
result.Left := FXMargin+(result.Right+FXSep)*Row;
result.Top := FYMargin+(result.Bottom+FYSep)*Col;
end;
function TGridArranger.CenterArrange(Row,Col:integer;Dim: TRect): TRect;
var
CR: TRect;
begin
CR := Arrange(Row,Col);
result := Dim;
if CR.Right < Dim.Right then
result.Left := CR.Left
else
result.Left := (CR.Right-Dim.Right) div 2 + CR.Left;
if CR.Bottom < Dim.Bottom then
result.Top := CR.Top
else
result.Top := (CR.Bottom-Dim.Bottom) div 2 + CR.Top;
end;コンストラクタでは、Dispatcher に WM_SIZE メッセージを送ってくれるよう依頼している。 DefaultHandler ではこのメッセージを捕まえて OnSize ハンドラを呼び出している。 他の二つのメソッドについては、説明の必要はないだろう。設定されたプロパティーの値をつかって、格子の Dimension を計算しているだけだ。
これでこのクラスは完成したので、インスタンス生成関数を作ろう。宣言部は、
function CreateGridArranger(hParent:HWND;iRow,iCol,XMrgn,YMrgn,
XSp,YSp:integer; sOnSize:TNotifyMessage): TGridArranger;であり、実装部は、
function CreateGridArranger(hParent:HWND;iRow,iCol,XMrgn,YMrgn,
XSp,YSp:integer; sOnSize:TNotifyMessage): TGridArranger;
begin
result := TGridArranger.Create(hParent);
with result do begin
NumRow := iRow;
NumCol := iCol;
XMargin := XMrgn;
YMargin := YMrgn;
XSep := XSp;
YSep := YSp;
OnSize := sOnSize;
end;
end;となる。このクラスをテストするプログラムを Arranger02.dpr に作成しよう。 以下に全リストを示す。
program Arranger02;
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;
PL: array[0..2] of TAPIPanel;
Btn: array[0..2] of TAPIButton;
GA1,GA2: TGridArranger;
L1: TAPILabel;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure GA1Size(var Msg: TMessage);
var
r: TRect;
begin
PL[0].Dimension := GA1.Arrange(0,0);
r := GA1.Arrange(1,0);
PL[1].Dimension := SetDim(r.Left,r.Top,r.Right,r.Bottom*2+GA1.YSep);
PL[2].Dimension := GA1.Arrange(0,1);
end;
procedure GA2Size(var Msg: TMessage);
var
i: integer;
begin
for i := 0 to 2 do
Btn[i].Dimension := GA2.Arrange(0,i);
L1.Dimension := GA2.CenterArrange(0,3,L1.Dimension);
end;
procedure BtnClick(var Msg: TMessage);
begin
PL[TAPIButton(Msg.Msg).Data].Color := RGB(Random(256),
Random(256),Random(256));
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
i: integer;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
for i := 0 to 2 do PL[i] := CreatePanel(hWindow,0,0,0,0);
for i := 0 to 2 do begin
Btn[i] := CreateButton(PL[1].Handle,0,0,0,0,
'Button '+AIntToStr(i),BtnClick);
Btn[i].Data := i;
end;
L1 := CreateLabel(PL[1].Handle,10,10,120,25,'CenterArrange');
GA1 := CreateGridArranger(hWindow,2,2,5,5,2,2,GA1Size);
GA2 := CreateGridArranger(PL[1].Handle,4,1,10,10,2,2,GA2Size);
with MainWnd do begin
ClientWidth := 360;
ClientHeight := 260;
Center;
end;
Randomize;
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.
実行すると、二つのグリッドアレンジャーが作られる。 一つ目は、メインウィンドウを縦横に4分割し、三枚のパネルを配置している。 二つ目は、 2番目のパネルを4行1列に分割して、上から三つにボタンを配置し、一番下ののグリッドにラベルを CenterArrange している。 この様子を右図に示す。 ボタンを押すと対応するパネルの色がランダムに変更される。サイズを変更しても相対は位置は変わらないことを確認できるだろう。
前節のグリッドアレンジャーより TPanel の Align プロパティーに近い配置を実現するボーダーアレンジャークラスを作成しよう。 このアレンジャーは、親ウィンドウを上下左右と残りの中央の五つに分割するものだ。上下配置については高さを指定し、幅は親ウィンドウのマージン以外の幅と同じになる。 左右配置については、幅だけを指定し、高さは上下配置に挟まれた高さによって決まる。 中央配置は、 残り全部を覆う。 もちろん、幅や高さをゼロに指定すると5分割のうちの一部だけにコントロールを配置することもできる。 例えば、上下配置だけとか、左と中央だけとかである。 それでは早速、UtilClass.pas に作成しよう。
最初に UtilClass.pas に TBorderArranger クラスを作成する。宣言部を以下に示す。
//------------- TBorderArranger ---------------------------------------------- TBorderPos = (bpUpper,bpLower,bpLeft,bpRight,bpCenter); TBorderArranger = class(TSubClass) private FUpperHeight: integer; FLowerHeight: integer; FLeftWidth: integer; FRightWidth: integer; FXMargin: integer; FYMargin: integer; FXSep: integer; FYSep: integer; FOnSize: TNotifyMessage; public constructor Create(hParent: HWND); override; procedure DefaultHandler(var Message);override; function Arrange(BorderPos:TBorderPos): TRect; property UpperHeight: integer read FUpperHeight write FUpperHeight; property LowerHeight: integer read FLowerHeight write FLowerHeight; property LeftWidth: integer read FLeftWidth write FLeftWidth; property RightWidth: integer read FRightWidth write FRightWidth; property XMargin: integer read FXMargin write FXMargin; property YMargin: integer read FYMargin write FYMargin; property XSep: integer read FXSep write FXSep; property YSep: integer read FYSep write FYSep; property OnSize: TNotifyMessage read FOnSize write FOnSize; end;
上下配置の高さと左右配置の幅のプロパティーがある他は、TGridArranger とほぼ同じである。 実装部を以下に示す。
//------------- TBorderArranger ----------------------------------------------
constructor TBorderArranger.Create(hParent: HWND);
begin
inherited Create(hParent);
Dispatcher.TrapMessage(WM_SIZE,self);
FUpperHeight := 0;
FLowerHeight := 0;
FLeftWidth := 0;
FRightWidth := 0;
FXMargin := 0;
FYMargin := 0;
FXSep := 1;
FYSep := 1;
end;
procedure TBorderArranger.DefaultHandler(var Message);
var
AMsg: TMessage;
begin
AMsg := TMessage(Message);
case AMsg.Msg of
WM_SIZE:begin
if Assigned(FOnSize) then begin
AMsg.Msg := UINT(self); // sender
OnSize(AMsg);
end;
end;
end; // case
end;
function TBorderArranger.Arrange(BorderPos:TBorderPos): TRect;
var
CR: TRect;
begin
GetClientRect(FParent,CR);
case BorderPos of
bpUpper: begin
result.Left := FXMargin;
result.Top := FYMargin;
result.Right := CR.Right-FXMargin*2;
result.Bottom := FUpperHeight;
end;
bpLower: begin
result.Left := FXMargin;
result.Top := CR.Bottom-FYMargin-FLowerHeight;
result.Right := CR.Right-FXMargin*2;
result.Bottom := FLowerHeight;
end;
bpLeft: begin
result.Left := FXMargin;
result.Top := FYMargin+FUpperHeight+FYSep;
result.Right := FLeftWidth;
result.Bottom := CR.Bottom-FUpperHeight-FLowerHeight-FYMargin*2-FYSep*2;
end;
bpRight: begin
result.Left := CR.Right-FXMargin-FRightWidth;
result.Top := FYMargin+FUpperHeight+FYSep;
result.Right := FRightWidth;
result.Bottom := CR.Bottom-FUpperHeight-FLowerHeight-FYMargin*2-FYSep*2;
end;
bpCenter: begin
result.Left := FXMargin+FLeftWidth+XSep;
result.Top := FYMargin+FUpperHeight+YSep;
result.Right := CR.Right-FLeftWidth-FRightWidth-FXSep*2-FXMargin*2;
result.Bottom := CR.Bottom-FUpperHeight-FLowerHeight-FYMargin*2-FYSep*2;
end;
end;
end;実装部については説明の必要はないだろう。 最後にインスタンス生成関数をつくる。宣言部は、
function CreateBorderArranger(hParent:HWND;UH,LH,LW,RW,XMrgn,YMrgn,
XSp,YSp:integer; sOnSize:TNotifyMessage): TBorderArranger;であり、実装部は、
function CreateBorderArranger(hParent:HWND;UH,LH,LW,RW,XMrgn,YMrgn,
XSp,YSp:integer; sOnSize:TNotifyMessage): TBorderArranger;
begin
result := TBorderArranger.Create(hParent);
with result do begin
UpperHeight := UH;
LowerHeight := LH;
LeftWidth := LW;
RightWidth := RW;
XMargin := XMrgn;
YMargin := YMrgn;
XSep := XSp;
YSep := YSp;
OnSize := sOnSize;
end;
end;である。
それではこのクラスをテストするプログラムを Arranger03.dpr に作成する。 以下に全リストを示す。
program Arranger03;
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;
Pnl: array[0..4] of TAPIPanel;
Btn: array[0..4] of TAPIButton;
BA: TBorderArranger;
GA: TGridArranger;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure BtnClick(var Msg: TMessage);
begin
Pnl[TAPIButton(Msg.Msg).Data].Color := RGB(Random(256),
Random(256),Random(256));
end;
procedure OnBASize(var Msg: TMessage);
var
i: integer;
begin
for i := 0 to 4 do
Pnl[i].Dimension := BA.Arrange(TBorderPos(i));
end;
procedure OnGASize(var Msg: TMessage);
var
i: integer;
begin
for i := 0 to 4 do
Btn[i].Dimension := GA.Arrange(0,i);
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
i: integer;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
for i := 0 to 4 do Pnl[i] := CreatePanel(hWindow,0,0,0,0);
for i := 0 to 4 do begin
Btn[i] := CreateButton(Pnl[3].Handle,0,0,0,0,
'Button '+AIntToStr(i),BtnClick);
Btn[i].Data := i;
end;
BA := CreateBorderArranger(hWindow,20,40,50,120,3,3,0,0,OnBASize);
GA := CreateGridArranger(Pnl[3].Handle,6,1,5,5,3,3,OnGASize);
with MainWnd do begin
ClientWidth := 360;
ClientHeight := 260;
Center;
end;
Randomize;
end;
procedure OnTest(hWindow: HWND);
begin
with Pnl[4] do begin
EdgeWidth := 4;
InnerEdge := esSunken;
end;
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.
実行すると右の図のようになる。 MainCreate( ) ハンドラでパネルとボタンを五個ずつつくり、グリッドアレンジャーとボーダーアレンジャーを一つずつ作っている。 ボーダーアレンジャーでパネルを配置し、 グリッドアレンジャーで左配置のパネルにボタンを配置している。 ボタンを押すと対応するパネルの色がランダムに変更される。 Test! メニューで中央配置のパネルのエッジスタイルが変更される。
グリッドアレンジャーとボーダーアレンジャーを組み合わせるといろいろな配置ができるようになる。 右の図のような配置にするにはどうしたらよいか、考えてほしい。
前節までに作成した二つのアレンジャークラスは、特定のウィンドウオブジェクトの WM_SIZE メッセージを捕捉して処理する非可視のクラスであった。 特定のウィンドウオブジェクトのウィンドウ関数に送られる任意のメッセージを捕捉してイベントハンドラのかたちで公開できれば、例えば一切のイベントを公開していない TAPIPanel や、ボタン類やリストボックスのような標準コントロールでも公開していないメッセージを処理することができるようになるだろう。 この節では、 TSubClass から派生し、特定のウィンドウオブジェクトから任意のメッセージを任意個捕捉して、イベントハンドラのかたちで呼び出す小さなクラス TMessageTrapper を作成しよう。
まず UtilClass.pas に、TMessageTrapper クラスを作成しよう。 宣言部を以下に示す。
//------------- MessageTrapper --------------------------------- TMessageTrapper = class(TSubClass) private FTrapMessage: TIntegerList; FHandler: TIntegerList; FNumTrap: integer; public constructor Create(hParent: HWND); override; destructor Destroy; override; procedure DefaultHandler(var Message);override; procedure TrapMessage(TrapMsg: UINT; Handler: TNotifyMessage); end;
private 部の FTrapMessage は捕捉するメッセージを、FHandler は対応する TNotifyMessage 型のイベントハンドラのアドレスを、FNumTrap は捕捉するメッセージの数を、それぞれ保持するためのものである。 実装部を以下に示す。
//------------- MessageTrapper ---------------------------------
constructor TMessageTrapper.Create(hParent: HWND);
begin
inherited Create(hParent);
FTrapMessage := TIntegerList.Create(1,1);
FHandler := TIntegerList.Create(1,1);
end;
destructor TMessageTrapper.Destroy;
begin
FTrapMessage.Free;
FHandler.Free;
inherited Destroy;
end;
procedure TMessageTrapper.DefaultHandler(var Message);
var
AMsg: TMessage;
i: integer;
begin
AMsg := TMessage(Message);
for i := 0 to FNumTrap-1 do
if AMsg.Msg = DWORD(FTrapMessage[i]) then
if FHandler[i] <> 0 then begin
TNotifyMessage(FHandler[i])(AMsg);
if AMsg.Result <> 0 then
TMessage(Message).result := AMsg.Result;
end;
end;
procedure TMessageTrapper.TrapMessage(TrapMsg: UINT;Handler: TNotifyMessage);
begin
FTrapMessage.Add(TrapMsg);
FHandler.Add(integer(@Handler));
Dispatcher.TrapMessage(TrapMsg,self);
inc(FNumTrap);
end;コンストラクタでは、二つの TIntegerList 型のフィールド変数を生成し、デストラクタで破棄している。 TrapMessage( ) メソッドでは、捕捉するメッセージとハンドラを記憶してから Dispatcher に配送してくれるよう依頼している。 DefaultHandler( ) では、メッセージに対応したイベントハンドラを呼び出しているだけだ。 これで TMessageTrapper クラスは完成したので MessageTrapper.dpr にテストプログラムを書こう。 以下に全リストを示す。
program MessageTrapper;
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,L2: TAPILabel;
PL: TAPIPanel;
Btn: TAPIButton;
MT1,MT2,MT3: TMessageTrapper;
//------------------------------------------------------------
// Helper Handlers
//------------------------------------------------------------
//------------------------------------------------------------
// Object Event Handlers
//------------------------------------------------------------
procedure MainMouseMove(var Msg: TMessage);
begin
L1.Text := 'X = '+AIntToStr(Msg.LParamLo)+#9+
'Y = '+AIntToStr(Msg.LParamHi);
end;
procedure PanelMouseMove(var Msg: TMessage);
begin
L2.Text := 'X = '+AIntToStr(Msg.LParamLo)+#9+
'Y = '+AIntToStr(Msg.LParamHi);
end;
procedure PanelLButtonUp(var Msg: TMessage);
begin
PL.Color := clrGreen;
end;
procedure PanelRButtonUp(var Msg: TMessage);
begin
MainWnd.Close;
end;
procedure ButtonMouseMove(var Msg: TMessage);
begin
Btn.Caption := 'X = '+AIntToStr(Msg.LParamLo)+
' Y = '+AIntToStr(Msg.LParamHi);
end;
//------------------------------------------------------------
// Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
hWindow: HWND;
begin
hWindow := MainWnd.Handle;
MakeInitMenu(hWindow);
L1 := CreateLabel(hWindow,10,10,200,30,'Label');
PL := CreatePanel(hWindow,10,50,200,100);
L2 := CreateLabel(PL.Handle,10,10,180,30,'LabelOnPanel');
Btn := CreateButton(hWindow,10,160,200,90,'Button',nil);
MT1 := TMessageTrapper.Create(hWindow);
MT1.TrapMessage(WM_MOUSEMOVE,MainMouseMove);
MT2 := TMessageTrapper.Create(PL.Handle);
MT2.TrapMessage(WM_MOUSEMOVE,PanelMouseMove);
MT2.TrapMessage(WM_LBUTTONUP,PanelLButtonUp);
MT2.TrapMessage(WM_RBUTTONUP,PanelRButtonUp);
MT3 := TMessageTrapper.Create(Btn.Handle);
MT3.TrapMessage(WM_MOUSEMOVE,ButtonMouseMove);
with MainWnd do begin
ClientWidth := 360;
ClientHeight := 260;
Center;
end;
end;
procedure OnTest(hWindow: HWND);
begin
PL.XPos := 150;
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.
実行すると右図のようになる。 MainCreate( ) で、パネルとボタンをつくり、さらに三個のメッセージトラッパをつくっている。それぞれ、メインウィンドウとパネルとボタンの WM_MOUSEMOVE メッセージを捕捉し、マウス座標をラベルやテキストに表示している。 ボタンもウィンドウであることが実感できるだろう。 さらにパネルでは、 WM_LBUTTONUP メッセージを捕捉して背景色を変更し、WM_RBUTTONUP ではプログラムを終了している。
ここで作成した TMessageTrapper クラスは、もちろん RADでの Form1 やその他のウィンドウオブジェクトに対しても正常に動作する。試してほしい。
前章では、メインウィンドウのラッパークラスを作成した。 続いて、本章では、 汎用子ウィンドウとしてパネルのラッパークラス TAPIPanel を作成した。 また、パネルや他のコントロールを配置するコードを包み込んで、グリッドアレンジャー TGridArranger とボーダーアレンジャー TBorderArranger クラスを作成しテストした。 さらに、特定のウィンドウオブジェクトの任意のメッセージを捕捉してイベントハンドラを呼び出す TMessageTrapper クラスを作成した。
ここまでのプログラムファイル