No.004 - 01 ドッキング・ツールバー の 構造 |
一般にドッキング・ツールバーの自前での実装は難しいとか面倒だと言われています。 今回はその基本部分を説明します。 |
・ツールバーの親ウィンドウ | ||||
MFCのドッキングツールバーはドッキングされているとき、メインウィンドウの子ウィンドウとなっていますが、浮かせるときにはフローティング用のフレーム・ウィンドウを生成しツールバーはその子ウィンドウに変更されています。 | ||||
・クライアント座標とビューウィンドウ |
||||
ツールバーは WS_CHILD スタイル を持っているので、ドッキングされているとき、 例えばユーザが座標0,0 にTextOut()しようとするとツールバーの上に文字列が表示されてしまいます。 このためMFCはビューウィンドウを用意し、ドッキングの時、フローティングの時に各ウィンドウをリサイズしています。 ユーザはビューウィンドウにボタン配置やTextOut()することによって座標系の手間がクリアになっています。 |
||||
・フローティング・ウィンドウのスタイル指定 |
||||
フローティング用フレームウィンドウはWS_CHILD スタイルを持ちません。 代わりにWS_POPUP と 拡張スタイルWS_EX_TOOLWINDOW を持ちます。 WS_CHILDを持っていると親ウィンドウのクライアント領域内にしか表示されないので、親ウィンドウの外にもきちんと表示させるためにはWS_CHILDは使えません。 CreateWindow() で WS_CHILD は指定しませんが親ウィンドウのHWNDは指定する必要があります。こうすると親ウィンドウの前面に表示されるポップアップ・ウィンドウとなります。 |
||||
|
||||
・フローティング・ウィンドウのタイトルバー |
||||
スタイル指定ではタイトルバー(キャプションバー)がは同時に青くなりません。(同時にアクティブ表示にならない) WS_CHILDを持ってないのでフォーカスが連動しないためです。 これにはプロシージャで WM_NCACTIVATE を処理して解決します。 WM_NCACTIVATE メッセージは、タイトルバー又はアイコンのアクティブ状態の表示を変更する場合に送られます。 アクティブなら WPARAM に TRUE を、非アクティブなら FALSE を代入し、DefWindowProc へ渡します。 ですが現実は常にwparam==TRUEではうまくいきません。(常にwparam==TRUEではOSがきちんと処理できない?) ある程度はきちんと表示してくれるのですが。 そこでSendMessage() を使って解決します。 アクティブウィンドウが切り替わるときは、現在のアクティブウィンドウにWM_NCACTIVATEメッセージ(非アクティブ化)を送ってから新アクティブウィンドウにWM_NCACTIVATEメッセージ(非アクティブ化)を送っています。 つまり一瞬だけ、すべてのウィンドウが非アクティブになってから、新アクティブウィンドウにアクティブ表示になる仕組みです。 以下のソースでは、ポップアップかメイン、どちらかのウィンドウにWM_NCACTIVATEメッセージが送られたら、再び各ウィンドウにWM_NCACTIVATEメッセージを再送しています。 アクティブ表示については以上ですが、MFCにおいては更にWM_NCPAINTメッセージでフローティング・ツールバーのタイトルバーを単色で塗りつぶしています。(なにも処理しないとグラデーション・バー)。 ちなみにタイトルバーの青い/青くないはアプリケーションの使用には全く影響しません。見た目だけです。 |
クリック |
ここにドッキングツールバーの基本的サンプルを示します。
メインウィンドウ内のクライアント領域(子ビューウィンドウ)をクリックするとツールバーのドッキング・フローティングが切り替わります。
未解決の仕様は多々ありますが、わかりにくくなるのでそれはまたの機会に・・・。
docking_test_01.c |
#include <windows.h> #include <commctrl.h> TBBUTTON tool_bar_buttons[] = { { STD_FILENEW, 1, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0 }, { STD_FILEOPEN, 2, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0 }, { STD_FILESAVE, 3, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0 }, { 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0 }, { STD_COPY, 4, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0 }, { STD_CUT, 5, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0 }, { STD_DELETE, 6, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0 }, { 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0 }, { VIEW_NEWFOLDER, 7, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0 }, { VIEW_PARENTFOLDER, 8, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0 } }; // タイトルバーのアクティブ化フラグ変数(メッセージの多重送信を防ぐ) int g_now_nc_activing=0; // ビューウィンドウ・プロシージャ LRESULT CALLBACK ChildViewProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { HDC hdc; PAINTSTRUCT ps; char str[]="ChildViewウィンドウ(座標0,0)"; switch (msg) { case WM_LBUTTONDOWN: SendMessage(GetParent(hwnd), WM_LBUTTONDOWN, wp, lp); // ツーバーのリサイズ break; case WM_PAINT: hdc = BeginPaint(hwnd , &ps); TextOut(hdc, 0, 0, str, lstrlen(str)); EndPaint(hwnd , &ps); break; } return DefWindowProc(hwnd, msg, wp, lp); } // ポップアップウィンドウ・プロシージャ LRESULT CALLBACK PopupProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_NCACTIVATE: // タイトルバーのアクティブ化・非アクティブ化 if (g_now_nc_activing) break; g_now_nc_activing=1; SendMessage(GetWindow(hwnd, GW_OWNER), WM_NCACTIVATE, wp, 0); g_now_nc_activing=0; break; } return DefWindowProc(hwnd, msg, wp, lp); } // メインウィンドウ・プロシージャ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { static HWND htool, popup_frame, hview; static int floating_flag=0; RECT rc, tool_rect; switch (msg) { case WM_CREATE: InitCommonControls(); htool = CreateToolbarEx( hwnd, WS_CHILD | WS_VISIBLE, 0, 6, (HINSTANCE)HINST_COMMCTRL, IDB_STD_SMALL_COLOR, tool_bar_buttons, 7, 0, 0, 0, 0, sizeof (TBBUTTON) ); popup_frame= CreateWindowEx( WS_EX_TOOLWINDOW, TEXT("popup_frame"), NULL, WS_OVERLAPPEDWINDOW | WS_POPUP | WS_VISIBLE, 300, 170, 160, 50, hwnd, NULL, ((LPCREATESTRUCT)(lp))->hInstance, NULL ); hview= CreateWindowEx( WS_EX_TOOLWINDOW, TEXT("child_view"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, NULL, ((LPCREATESTRUCT)(lp))->hInstance, NULL ); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_NCACTIVATE: // タイトルバーのアクティブ化・非アクティブ化 if (g_now_nc_activing) break; g_now_nc_activing=1; SendMessage(popup_frame, WM_NCACTIVATE, wp, 0); g_now_nc_activing=0; break; case WM_SIZE: // サイズ変更 if (!floating_flag) SendMessage(htool, WM_SIZE, wp, lp); GetClientRect(hwnd, &rc); GetWindowRect(htool, &tool_rect); if (!floating_flag) rc.top =tool_rect.bottom-tool_rect.top; SetWindowPos(hview, NULL, rc.left, rc.top, rc.right, rc.bottom-rc.top, 0); break; case WM_LBUTTONDOWN: // マウスでドッキング・フローティング切り替え if (floating_flag) SetParent(htool, hwnd); else SetParent(htool, popup_frame); floating_flag = !floating_flag; GetClientRect(hwnd, &rc); GetWindowRect(htool, &tool_rect); if (!floating_flag) rc.top =tool_rect.bottom-tool_rect.top; SetWindowPos(hview, NULL, rc.left, rc.top, rc.right, rc.bottom-rc.top, 0); break; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow ) { HWND hwnd; MSG msg; WNDCLASS winc; winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = winc.cbWndExtra = 0; winc.hInstance = hInstance; winc.hIcon = LoadIcon(NULL, IDI_APPLICATION); winc.hCursor = LoadCursor(NULL, IDC_ARROW); winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); winc.lpszMenuName = NULL; winc.lpszClassName = TEXT("main_frame"); if (!RegisterClass(&winc)) return -1; // メイン・ウィンドウ winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); winc.lpfnWndProc = ChildViewProc; winc.lpszClassName = TEXT("child_view"); if (!RegisterClass(&winc)) return -1; // ビューウィンドウ winc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); winc.lpfnWndProc = PopupProc; winc.lpszClassName = TEXT("popup_frame"); if (!RegisterClass(&winc)) return -1; // フローティング・フレーム・ウィンドウ hwnd = CreateWindow( TEXT("main_frame"), TEXT("ドッキング・ツールバーのテスト1"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 240, 180, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return -1; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } |