ここでは、すべての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のページで検索をかけるのがよいでしょう。