今回は、コントロールとテーマの関係について取り上げます。 まず、次の図を見てください。


通常、テーマが有効になっている場合は、 左のウインドウのように非クライアント領域(タイトルバーなど)とコントロールにテーマが適応されることを望みますが、 これまで作成したアプリケーションでは、右のウインドウのように表示されていたと思われます。 つまり、ウインドウの非クライアント領域にはテーマが適応されるのですが、 コントロールにはテーマが適応されていません。 この問題を解決するためには、描画の際にどのようなDLLが使われているのかを理解する必要があります。
まず、ウインドウの非クライアント領域の描画は、user32.dllを通じて行われます。 このDLLは、テーマが有効になっている場合はテーマを考慮して描画を行うため、 非クライアント領域はテーマの状態が常に反映されることになります。 一方、コントロールの描画は、comctl32.dllを通じて行われます。 このDLLは、バージョン5系列と6系列が存在し、 前者のDLLを使用した場合はコントロールがクラシックに描画されますが、 後者のDLLを使用した場合はテーマを考慮してコントロールが描画されます。 デフォルトでは、バージョン5系列のcomctl32.dllが使用されることになっているため、 これを6系列の使用に変更すれば、コントロールにテーマを適応させることができるはずです。 内部的な話をすると、バージョン5系列のcomctl32.dllを使用する場合でも、 プロセスのアドレス空間にはバージョン6系列のcomctl32.dllがロードされているのですが、 やはり描画にはバージョン5系列のcomctl32.dllが使用されていると思われます。
任意のバージョンのDLLを使用するためには、アプリケーションにマニフェストファイルを追加する必要があります。 マニフェストファイルには、使用するDLL(正確にはアセンブリ)の名前やバージョンを指定できます。 次に、バージョン6系列のcomctl32.dllを使用する場合におけるマニフェストファイルの形式を示します。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="CompanyName.ProductName.YourApp" type="win32" /> <description>アプリケーションの説明</description> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency> </assembly>
青字以外の部分は、変更する必要はありません。 nameは、アプリケーションの名前を指定します。 descriptionは、アプリケーションの説明文を指定します。
マニフェストファイルの名前は、EXEファイルの名前に.manifestを追加したものになります。 たとえば、EXEファイルの名前がsample.exeならば、sample.exe.manifestのようになります。 ファイルの中身については、上記コードをコピーして貼り付けるだけで問題ありません。 作成したファイルをアプリケーションに追加するには、 ファイルをVisual Studioのソリューションエクスプローラーにドロップするのが一番簡単だと思われますが、 プロジェクトのプロパティから追加する方法もよく利用されます。 次にこの手順を示します。


左側のペインから「マニフェスト ツール」の「入力と出力」を選択し、 右側のペインの「追加のマニフェスト ファイル」に$(ProjectDir)\$(TargetFileName).manifestという文字列を入力します。 マニフェストファイルは、予めプロジェクトフォルダに置いておきます。 これでOKボタンを押してビルドすれば、EXEファイルにはマニフェストファイルが埋め込まれることになります。 なお、マニフェストファイルを追加することによって得られる事実は、 あくまでバージョン6系列のcomctl32.dllを使用するという点だけであることに注意してください。 ユーザーがクラシックスタイルを使用するように設定しているのであれば、 当然ながらコントロールにテーマは反映されません。
バージョン6系列のcomctl32.dllを使用することによって、単にテーマを適応させるだけでなく、 コントロールの新しい機能を使用できるようになることがあります。 たとえば、Windows Vistaのボタンならシールドアイコンを備えたり、分割ボタンやコマンドリンクボタンを作成したりすることができます。 これらのボタンを作成する例を次に示します。
#include <windows.h> #include <commctrl.h> #define ID_BUTTON1 100 #define ID_BUTTON2 200 #define ID_BUTTON3 300 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow) { TCHAR szAppName[] = TEXT("sample"); HWND hwnd; MSG msg; WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hinst; wc.hIcon = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED); wc.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = szAppName; wc.hIconSm = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED); if (RegisterClassEx(&wc) == 0) return 0; hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL); if (hwnd == NULL) return 0; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: { HWND hwndButton1, hwndButton2, hwndButton3; hwndButton1 = CreateWindowEx(0, TEXT("BUTTON"), TEXT("ボタン"), WS_CHILD | WS_VISIBLE, 30, 30, 80, 40, hwnd, (HMENU)ID_BUTTON1, ((LPCREATESTRUCT)lParam)->hInstance, NULL); SendMessage(hwndButton1, BCM_SETSHIELD, 0, TRUE); hwndButton2 = CreateWindowEx(0, TEXT("BUTTON"), TEXT("ボタン"), WS_CHILD | WS_VISIBLE | BS_SPLITBUTTON, 30, 80, 80, 40, hwnd, (HMENU)ID_BUTTON2, ((LPCREATESTRUCT)lParam)->hInstance, NULL); hwndButton3 = CreateWindowEx(0, TEXT("BUTTON"), TEXT("ボタン"), WS_CHILD | WS_VISIBLE | BS_COMMANDLINK, 30, 130, 180, 40, hwnd, (HMENU)ID_BUTTON3, ((LPCREATESTRUCT)lParam)->hInstance, NULL); SendMessage(hwndButton3, BCM_SETNOTE, 0, (LPARAM)L"任意の文章を表示できます。"); return 0; } case WM_NOTIFY: if (((LPNMHDR)lParam)->code == BCN_DROPDOWN) MessageBox(NULL, TEXT("矢印が選択されました。"), TEXT("OK"), MB_OK); break; case WM_DESTROY: PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
1つ目のCreateWindowExでは、通常のボタンを作成しているだけですが、 その後にBCM_SETSHIELDというメッセージを送っています。 これは、ボタンにシールドアイコンを設定するためのメッセージで、 第4引数にTRUEを指定すると実際にアイコンが設定されます。 シールドアイコンは、管理者でなければ実行できない動作を伝える場合に使用します。 2つ目のCreateWindowExでは、ウインドウスタイルにBS_SPLITBUTTONを指定して、 分割ボタンを作成しています。 分割ボタンは矢印を持ち、それを選択した場合はWM_NOTIFYでBCN_DROPDOWNが送られます。 今回は、ここでMessageBoxを表示していますが、通常はメニューを表示することになります。 3つ目のCreateWindowExでは、BS_COMMANDLINKを指定してコマンドリンクボタンを作成しています。 コマンドリンクボタンは、BCM_SETNOTEメッセージを通じてボタンにコメントを設定することができます。
サイドバイサイドアセンブリ共有について |
今回の話によりcomctl32.dllが複数個存在することが分かりましたが、 よく考えるとこれは少し不思議なことであるといえます。 なぜなら、バージョンが違うといってもcomctl32という同じ名前を持っているわけですから、 名前の重複によりDLLが共存できないのではないでしょうか。 実は、こうしたバージョンの異なるDLLは、WinSxSフォルダで管理されるようになっており、 1つのDLL毎に個別の名前を持ったフォルダが作成されることになっています。 このように、バージョンの異なるDLLを同一の名前で管理し、 マニフェストファイルで使用するバージョンを決定できる仕組みをサイドバイサイドアセンブリ共有と呼びます。 また、共有されるDLLはアセンブリと呼ばれます。 マニフェストファイルでバージョン6系列の使用を明示しなかった場合は、 バージョン5系列のcomctl32.dllが使用されるわけですが、 実はそれはWinSxSフォルダに存在するものではなく、 system32フォルダに存在するcomctl32.dllが使用されることになっています。 つまり、バージョン5系列のcomctl32.dllは、system32フォルダにもWinSxSフォルダにも存在します。 ただし、Windows XPでは、バージョン5系列のcomctl32.dllはWinSxSフォルダに存在しません。 |