ここでは、すべてのWin32アプリケーションの基本となるプロジェクトを作成します。
Hello World!アプリケーションと同様にプロジェクトを作成し、以下のコードを入力します。
Generic.dprというファイル名で保存すれば完成です。
program Generic;
{$WARN SYMBOL_PLATFORM OFF} //CmdShowの警告回避
uses
Windows, Messages;
////////////////////////////////////////////////////////////////////////////////
// Main Window Procedure
function MainWndProc(wnd: HWND; msg: UINT; wp: WPARAM;
lp: LPARAM): LRESULT; stdcall;
begin
Result := 0;
case msg of
WM_DESTROY: PostQuitMessage(0);
else
Result := DefWindowProc(wnd, msg, wp, lp);
end;
end;
////////////////////////////////////////////////////////////////////////////////
// Main
const
CW_USEDEFAULT = Integer($80000000); //Windowsユニットの宣言では警告が出る
WC_MAIN = 'GenericMain';
var
wc: TWndClass;
wnd: HWND;
msg: TMsg;
begin
wc.style := CS_VREDRAW or CS_HREDRAW;
wc.lpfnWndProc := @MainWndProc;
wc.cbClsExtra := 0;
wc.cbWndExtra := 0;
wc.hInstance := HInstance;
wc.hIcon := LoadIcon(0, IDI_APPLICATION);
wc.hCursor := LoadCursor(0, IDC_ARROW);
wc.hbrBackground := COLOR_BTNFACE + 1;
wc.lpszMenuName := nil;
wc.lpszClassName := WC_MAIN;
RegisterClass(wc);
wnd := CreateWindow(
WC_MAIN,
'Generic',
WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
HWND_DESKTOP,
HMENU(0),
HInstance,
nil
);
ShowWindow(wnd, CmdShow);
while GetMessage(msg, 0, 0, 0) do begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
ExitCode := msg.wParam;
end.
完成したらコンパイルして実行してみましょう。何も乗せていないTFormのようなウィンドウが表示されたはずです。サイズ変更など、基本的なウィンドウ操作ができることを確認してください。
TFormの場合は400KBを超えましたが、この実行バイナリは管理人の環境では8.5KBでした。KOL systemユニットとUPXを使えば4KBまで小さくできました。
コードの内容を簡単に解説すると、
という流れになります。Win32APIについてより詳細な情報を知りたければ関連リンクを参照してください。
Delphi特有の注意事項としては次のものがあります。
もう少しアプリケーションらしくするためにエディットコントロールを子ウィンドウとして追加します。
Genericプロジェクトを次のように編集してください。
program Generic;
{$WARN SYMBOL_PLATFORM OFF}
uses
Windows, Messages;
var
hEdit: HWND;
////////////////////////////////////////////////////////////////////////////////
// Message Handler
procedure MainCreate(wnd: HWND);
const
EDIT_STYLE =
WS_CHILD or WS_VISIBLE or WS_HSCROLL or WS_VSCROLL or
ES_AUTOHSCROLL or ES_AUTOVSCROLL or
ES_LEFT or ES_MULTILINE or ES_NOHIDESEL;
begin
hEdit := CreateWindowEx(
WS_EX_CLIENTEDGE, 'EDIT', '', EDIT_STYLE,
0, 0, 100, 100, wnd, HMENU(0), HInstance, nil
);
end;
////////////////////////////////////////////////////////////////////////////////
// Main Window Procedure
function MainWndProc(wnd: HWND; msg: UINT; wp: WPARAM;
lp: LPARAM): LRESULT; stdcall;
begin
Result := 0;
case msg of
WM_DESTROY: PostQuitMessage(0);
WM_CREATE: MainCreate(wnd);
WM_SIZE: MoveWindow(hEdit, 0, 0, LOWORD(lp), HiWord(lp), True);
else
Result := DefWindowProc(wnd, msg, wp, lp);
end;
end;
////////////////////////////////////////////////////////////////////////////////
// Main
(同じなので省略)
WM_CREATEメッセージが送られた(メインウィンドウが作成された)ときにエディットコントロールを作成し、hEditグローバル変数にハンドルを代入しています。WM_SIZEメッセージが送られたとき(ウィンドウサイズが変更されたとき)エディットコントロールのサイズをメインウィンドウのクライアント領域いっぱいになるように変更しています。
これだけでファイルを開く・保存などは一切できませんが右クリックメニューを備えたメモ帳らしきものが出来上がります。
さらにもう少しアプリケーションらしくするためにメニューを追加します。
Genericプロジェクトを次のように編集してください。
program Generic;
{$WARN SYMBOL_PLATFORM OFF}
uses
Windows, Messages;
const
IDM_UNDO = 100;
IDM_CUT = 101;
IDM_COPY = 102;
IDM_PASTE = 103;
IDM_SELECTALL = 104;
var
hEdit: HWND;
////////////////////////////////////////////////////////////////////////////////
// Message Handler
procedure MainNCCreate(wnd: HWND);
var
popup: HMENU;
begin
SetMenu(wnd, CreateMenu());
popup := CreatePopupMenu();
AppendMenu(GetMenu(wnd), MF_STRING or MF_POPUP, popup, '編集(&E)');
AppendMenu(popup, MF_STRING, IDM_UNDO, '元に戻す(&U)' + #$9 + 'Ctrl+Z');
AppendMenu(popup, MF_SEPARATOR, 0, nil);
AppendMenu(popup, MF_STRING, IDM_CUT, '切り取り(&T)' + #$9 + 'Ctrl+X');
AppendMenu(popup, MF_STRING, IDM_COPY, 'コピー(&C)' + #$9 + 'Ctrl+C');
AppendMenu(popup, MF_STRING, IDM_PASTE, '貼り付け(&P)' + #$9 + 'Ctrl+V');
AppendMenu(popup, MF_SEPARATOR, 0, nil);
AppendMenu(popup, MF_STRING, IDM_SELECTALL, 'すべて選択(&A)' + #$9 + 'Ctrl+A');
end;
procedure MainCreate(wnd: HWND);
const
EDIT_STYLE =
WS_CHILD or WS_VISIBLE or WS_HSCROLL or WS_VSCROLL or
ES_AUTOHSCROLL or ES_AUTOVSCROLL or
ES_LEFT or ES_MULTILINE or ES_NOHIDESEL;
begin
hEdit := CreateWindowEx(
WS_EX_CLIENTEDGE, 'EDIT', '', EDIT_STYLE,
0, 0, 100, 100, wnd, HMENU(0), HInstance, nil
);
end;
////////////////////////////////////////////////////////////////////////////////
// Main Window Procedure
function MainWndProc(wnd: HWND; msg: UINT; wp: WPARAM;
lp: LPARAM): LRESULT; stdcall;
begin
Result := 0;
case msg of
WM_DESTROY: PostQuitMessage(0);
WM_DESTROY: PostQuitMessage(0);
WM_NCCreate: begin
MainNCCreate(wnd);
Result := DefWindowProc(wnd, msg, wp, lp);
end;
WM_CREATE: MainCreate(wnd);
WM_SIZE: MoveWindow(hEdit, 0, 0, LOWORD(lp), HiWord(lp), True);
WM_COMMAND:
case LOWORD(wp) of
IDM_UNDO: SendMessage(hEdit, WM_UNDO, 0, 0);
IDM_CUT: SendMessage(hEdit, WM_CUT, 0, 0);
IDM_COPY: SendMessage(hEdit, WM_COPY, 0, 0);
IDM_PASTE: SendMessage(hEdit, WM_PASTE, 0, 0);
IDM_SELECTALL: SendMessage(hEdit, EM_SETSEL, 0, -1);
end;
else
Result := DefWindowProc(wnd, msg, wp, lp);
end;
end;
////////////////////////////////////////////////////////////////////////////////
// Main
(同じなので省略)
WM_NCCREATEメッセージが送られたときにCreateMenu関数でメインメニューを作成し、SetMenu関数でメインウィンドウと関連付けています。次にCreatePopupMenu関数でサブメニューを作成し、GetMenu関数で取得したメインメニューにAppendMenu関数を使ってサブメニューを追加しています。その後同じくAppendMenu関数を使って個々のメニュー項目を追加しています。
WM_CREATEメッセージが送られたときにメニューを作成・追加すると、起動したときにメニューがエディットコントロールに隠されてしまいます。これはおそらくWM_NCCREATEメッセージがWM_NCCALCSIZEメッセージが送られる(クライアント領域が計算される)前に送られるのに対し、WM_CREATEメッセージはWM_NCCALCSIZEメッセージより後に送られるためと思われます。
通常、ウィンドウプロシージャでメッセージを処理するとき(DefWindowProc関数に渡さないとき)は戻り値として0を返すのですが、WM_NCCREATEメッセージの場合、0を返すとウィンドウを破棄してしまいます。他にもタイトルバー文字列を初期化するなどの動作をこのメッセージで行うので、WM_NCCREATEメッセージが送られたときはメッセージをDefWindowProc関数に渡して戻り値を返しています。
メニューが選択されたときはメニューの親ウィンドウにWM_COMMANDメッセージが送られるので、ここでメニューが選択されたときの動作を記述します。LOWORD(wp)にメニューを作成したときに指定したID値が格納されているので、どのメニューが選択されたのかわかります。このID値はどんな値でもかまいませんが、固有であること、特にIDOKやIDCANCELなどの定義済みID値と重複しないことに気をつけてください。これらの定義済みID値は1〜11程度の値を割り振られているようですので、管理人は通常100から順番にID値を割り振るようにしています。
エディットコントロールに「元に戻す」「コピー」などの動作をさせるのは、特定のメッセージをエディットコントロールに送るだけで済みます。
実行イメージは次のようになります。(ウィンドウサイズを変更しています。)

メニューにショートカットキーを表示するのにキャプションに#$9(タブ文字)をはさんで"Ctrl+Z"や"Ctrl+C"などを指定しましたが、これだけでショートカットキーがはたらくようになるわけではありません。Ctrl+ZやCtrl+Cを押すと「元に戻す」や「コピー」の動作が行われるのは、エディットコントロールの標準の動作によるものです。Ctrl+Aを押しても「すべて選択」の動作が行われないことを確認してください。
このようなショートカットキーの動作、つまりある組み合わせのキーを押すと対応する機能を実行するものをアクセラレータと呼び、このキーの組み合わせと機能の対応表をアクセラレータテーブルと呼びます。
ここでは「すべて選択」機能のCtrl+Aのショートカットキーをアクセラレータテーブルを使って実装してみます。
GenricプロジェクトのMainの部分のみを以下のように編集してください。
program Generic;
(同じなので省略)
////////////////////////////////////////////////////////////////////////////////
// Main
const
CW_USEDEFAULT = Integer($80000000);
WC_MAIN = 'GenericMain';
ACCEL_COUNT = 1;
var
wc: TWndClass;
wnd: HWND;
msg: TMsg;
table: array[0..ACCEL_COUNT - 1] of TAccel;
accel: HACCEL;
begin
wc.style := CS_VREDRAW or CS_HREDRAW;
wc.lpfnWndProc := @MainWndProc;
wc.cbClsExtra := 0;
wc.cbWndExtra := 0;
wc.hInstance := HInstance;
wc.hIcon := LoadIcon(0, IDI_APPLICATION);
wc.hCursor := LoadCursor(0, IDC_ARROW);
wc.hbrBackground := COLOR_BTNFACE + 1;
wc.lpszMenuName := nil;
wc.lpszClassName := WC_MAIN;
RegisterClass(wc);
wnd := CreateWindow(
WC_MAIN,
'Generic',
WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
HWND_DESKTOP,
HMENU(0),
HInstance,
nil
);
table[0].fVirt := FCONTROL or FVIRTKEY;
table[0].key := Ord('A');
table[0].cmd := IDM_SELECTALL;
accel := CreateAcceleratorTable(table, ACCEL_COUNT);
ShowWindow(wnd, CmdShow);
while GetMessage(msg, 0, 0, 0) do begin
if TranslateAccelerator(wnd, accel, msg) = 0 then begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
end;
DestroyAcceleratorTable(accel);
ExitCode := msg.wParam;
end.
アクセラレータテーブルの使い方は以下のようなものです。
今回、ウィンドウ・メニュー・アクセラレータテーブルの作成方法について解説しました。この方法だと、たとえば子ウィンドウを大量に作成するときなどはかなり面倒なことになります。実はWindowsはリソースという仕組みを使って、簡単にウィンドウ・メニュー・アクセラレータを作る方法を用意しており、むしろこちらの方法が一般的です。
しかし、リソースを使う方法でもWindowsの内部では今回解説した方法と同様な処理が行われているはずです。リソースの仕組みを理解するうえでも役立つと思い、あえてリソースを使わない方法を紹介しました。
Win32APIについて概要を知りたければ、次のページが参考になります。
個々のAPIについて知りたければ、MSDNのページで検索をかけるのがよいでしょう。