Windows Vistaに全く興味がなかったわけでもないですが、メインのPCをWindows7にしたことだし・・・ということで、ほとんど何の知識もないWindows Aeroですが、ちょっと勉強がてらいじってみました。
Aeroの最大の特徴は、やはり、ウィンドウフレームにも適用されているガラス効果でしょう。各ウィンドウとガラス効果などのAero効果をデスクトップ上のウィンドウとして表示できるように合成・管理しているのが、DWM(Desktop Window Manager)ということだそうです。
クライアント向けのWindowsで、NT4からXPまでのウィンドウマネージャなどの大部分はカーネルモードで動作していたと思います。これは純粋にパフォーマンス向上のためと思いますが、Vista以降ではドライバモデルが変更され、iその一部がユーザーモードで動作するようになりました。本来のNTの思想に戻ったといえます。ビデオカードをはじめとするハードウェア自体の性能向上のおかげでしょうね・・・。
このDWM、タスクマネージャで確認するとVistaと7では、明らかにメモリ使用量が違っています。詳しくは知りませんが、なんらかの改良が施されているんだと思います。
で、DWMに関連するAPIでネットを検索しても、SDKプログラマの興味がないのか、関心がないのか、C/C++ WindowsSDKで利用できるまとまった情報(コード)が少ないです。使ってみました、という記事はいっぱい出るんですが、詳細な記事が見つからず。MSDNのAPI説明見てもいまいち分からないし・・・。
まぁ、だけど利用できるAPIは限られていて、DWMに関連するAPIで最も簡単に利用できるのが、DwmExtendFrameIntoClientAreaというAPIです。
これは、ウィンドウフレームに適用されているガラス効果をクライアントエリアに拡張するAPIなんですが、使い方は至って簡単です。ガラス効果をどれだけクライアント側に食い込ませるかをMARGINS構造体にセットして、それをウィンドウハンドルとともに渡せばOKです。
//{左,右,上,下}という風になります。 //下記例だと、上部20ピクセルが拡張されます。 MARGINS margins = {0,0,20,0}; DwmExtendFrameIntoClientArea(hWnd,&margins); //ちなみに、以下のように-1をセットすると、クライアント領域すべてがガラス効果の影響受けます。 MARGINS margins = {-1};
※dwmapi.hのインクルードと、dwmapi.libのリンクが必要です。
このようにAPI自体はカンタンに利用できる反面、C/C++とWindowsSDKのみで組んで行くには相当めんどくさい処理が必要になります。
- 単純にボタンやエディットなどのコントロールを配置すると、悲惨なことになる。
- ガラス効果上に黒い文字を描画すると、黒い文字が透明になっちゃう(^^;;;
- ガラス効果にしたクライアント領域部分は、別途常に黒く塗りつぶさないといけない。
(要するに、拡張した部分の背景色の再描画処理を別途記述しなければならないかと・・・) - DWMのデスクトップコンポジションが有効か無効かを判別してそれぞれに対する処理を行う必要がある。
などなど・・・
ガラス効果上に配置した黒く塗りつぶしたGDIアイテムは、全部透明になってしまいます(テキストとかメニューの文字とか)。要するに、色値が0x00000000(要するに黒色)の部分がガラス効果として透明なるっちゅーわけだから、当たり前と言えば当たり前ですが・・・。COLORREF型の先頭1バイトを255にすればいいのか?よくわかんないです。
これの解決方法としては、コントロールをオーナードローで描画するか、GDI+ライブラリを使用する、という選択肢がMSDNで紹介されていました。が、子コントロールの色を全部修正するなんて、メンドーだし、他の解決方法があるのかもしれません。
そのうちガラス効果関連処理をラップしたC/C++のクラスライブラリがそのうち出るかもしれないし、でないかもしれないし、すでに出ているのかも・・・。C#の情報は結構あるんですけどね・・・。
ダイアログをリソースファイルで定義してDialogBox() APIで表示させているようなプログラムの場合、最低限以下のようなウィンドウメッセージを捕まえて背景を別途処理しないといけない。
・WM_INITDIALOG
デスクトップコンポジションが有効なら、DwmExtendFrameIntoClientAreaを実行。
・WM_ERASEBKGND
ガラス効果を適用しても、ダイアログの背景用描画ブラシで上書きされてしまうので、ガラス効果を拡張した部分は黒色で、それ以外はダイアログの背景色で背景を塗りつぶす。
・WM_DWMCOMPOSITIONCHANGED
デスクトップコンポジションが有効になったり、無効になったりすると、飛んでくるので、これを捕まえて適切な処理を行う。
注意しなければならないのは、デスクトップコンポジションが有効になったらその都度、DwmExtendFrameIntoClientAreaをコールする必要があること。
INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { //上部20ピクセルを拡張 MARGINS margins = {0,0,20,0}; RECT rect = {0}; BOOL fDwmEnabled = FALSE; HBRUSH hBrush = NULL; LONG oldBottom = 0; switch(message) { case WM_INITDIALOG: DwmIsCompositionEnabled(&fDwmEnabled); if(fDwmEnabled) DwmExtendFrameIntoClientArea(hDlg,&margins); return (INT_PTR)TRUE; case WM_ERASEBKGND: DwmIsCompositionEnabled(&fDwmEnabled); GetClipBox((HDC)wParam,&rect); //デスクトップコンポジションの有効・無効によって背景に使用するブラシを決定 hBrush = fDwmEnabled ? (HBRUSH)GetStockObject(BLACK_BRUSH) : GetSysColorBrush(COLOR_BTNFACE); //クライアント領域の上部20ピクセル(ガラス効果)の背景を描画 oldBottom = rect.bottom; rect.bottom = rect.top + 20; FillRect((HDC)wParam,&rect,hBrush); //クライアント領域の残りの下部の背景を描画 hBrush = GetSysColorBrush(COLOR_BTNFACE); rect.top += 20; rect.bottom = oldBottom; FillRect((HDC)wParam,&rect,hBrush); return (INT_PTR)TRUE; case WM_DWMCOMPOSITIONCHANGED: DwmIsCompositionEnabled(&fDwmEnabled); if(fDwmEnabled) DwmExtendFrameIntoClientArea(hDlg,&margins); return (INT_PTR)0; case WM_COMMAND: //ダイアログ上のコントロールへの応答処理など if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; }
ということで、なんとかかんとか、思ったように表示できました・・・。
なんか、久しぶりにGUIなAPI触ると、何がどうだったか・・・忘却の彼方へ・・・(ーー;
■追記
MSDN(英語)にDWMなウィンドウ作成についての詳細な説明がありました。気づかなかった(ーー;;;
Custom Window Frame Using DWM