#analog
#norelated
#contents
* Today プラグインサンプルプログラム2 [#xa500017]
** 背景透過 [#ofd9e8e8]
[[Today プラグインサンプルプログラム]]で作ったサンプルでは、ウィンドウクラス登録で指定した黒ブラシで描画された背景を持つアイテムが表示されていました。~
この上に色々描いていってもいいのですが、せっかくToday画面には壁紙が表示されているので、その壁紙の上に色々表示させたいですよね。~
というわけで、前回黒かった部分を透過して、壁紙を表示させるようにします。~
といっても、WindowsMobileSDKのサンプル等を見ると、WM_ERASEBKGNDを処理して、シェルにTODAYM_DRAWWATERMARKメッセージで壁紙を描いてもらうという処理を行っているようですが、これをしなくても、ウィンドウクラスのhbrBackgroundをNULL指定にするだけで、とりあえずは壁紙が表示されるようです。~
恐らく、アイテムの親ウィンドウにWS_CLIPCHILDRENがついていないので、親ウィンドウが描画されるタイミングでアイテム部分まで壁紙が描画されているので、問題なく透けて見えているようです。~
ただ、なんらかの要因で親ウィンドウの更新がされずに、アイテム内部の描画だけが必要になった時には、WM_ERASEBKGNDかWM_PAINTを処理して、その中で親ウィンドウに壁紙の描画を依頼する必要があると思われます。~
実際、NULL指定だけでは、他のアプリを起動、終了させて、再度Today画面が表示された時等に、何も描画されなかったりします。~
ちらつきを抑えるという意味では、一旦親ウィンドウが背景を描いてしまうことがある以上、多少のちらつきはあるかもしれないのですが、アイテムの描画としては、ウィンドウクラスではNULLブラシを設定してWM_ERASEBKGNDでは何もせず、WM_PAINT時に裏バッファを用意しておいて、そこにTODAYM_DRAWWATERMARKで背景を描いてもらい、アイテムの描画を行った上で、表画面に描画するのがいいのかもしれませんね。~
なのでまずは、WM_ERASEBKGNDで何もしないようにします。~
背景ブラシがNULLになっていれば、実質、DefWindowProcはWM_ERASEBKGNDを受け取ったときに何もしないと思いますが、明示的にしておきます。~
プロトタイプ宣言は以下のように。~
BOOL Today_OnEraseBkgnd( HWND hwnd, HDC hdc ); // WM_ERASEBKGND処理
ウィンドウプロシージャも追加します。~
HANDLE_MSG( hwnd, WM_ERASEBKGND, Today_OnEraseBkgnd );
そして、何もせずに、背景を消去したという意味のTRUEを返します。~
BOOL Today_OnEraseBkgnd( HWND hwnd, HDC hdc )
{
return TRUE;
}
次に、WM_PAINTの処理を作り、その中でバックバッファを作成することにしましょう。~
ただし、毎回バックバッファを作り直すのは効率が悪いので、クライアント領域のサイズを保持しておき、サイズが変わることがあれば、そのサイズでバックバッファを作り直すという処理にします。~
縦画面/横画面の切り替わりで、クライアント領域のサイズが変わるはずなので、描画のタイミングでサイズチェックを行えば、問題なく検知できるはずです。~
普通のWindowsプログラムだと、モニタの切り替わりをWM_DISPLAYCHANGEで捕まえたりしますが、そういう考慮は必要なさそうですね。~
縦横切り替えの検知は、サイズ変更でしか知り得ないのかな……?~
その辺りは後々調べますか……。~
で、ちょっと調べてみたところ、縦横の切替はChangeDisplaySettingsExで行うことが出来、WM_SIZEメッセージとWM_SETTINGCHANGEメッセージで検知して処理するようです。~
Todayのアイテムについては、WM_SIZEでウィンドウサイズが変更されるので、描画だけなら、その後のWM_PAINTでよさそうですね。~
コントロールの再配置等は、WM_SIZEでやったほうがよさげです。~
バックバッファ作成のために、Todayウィンドウクラスに、以下のようなメンバを追加します。~
HDC m_hdc; // デバイスコンテキスト
HBITMAP m_hOldbmp; // ビットマップハンドル
RECT m_rect; // クライアント矩形保存用
それぞれ、表示画面と互換性のあるデバイスコンテキストを保存しておくメンバと、表示画面と互換性のあるビットマップをデバイスコンテキストに関連付けた際に、元々関連付けられていたデフォルトのビットマップが返されるので、それを後で戻すために保持しておくメンバと、サイズ変更を検知するために、作成時のクライアント矩形を覚えておくメンバになります。~
バックバッファ作成前に、GetClientRectでクライアント領域を取得し、メンバに保存してある領域とサイズが同じであれば、特に何もしません。~
サイズに変更がある場合のバックバッファ作成の流れは、解放用の関数をコールし、古いバックバッファがあれば削除した後、GetDCで表示ウィンドウのデバイスコンテキストを取得し、CreateCompatibleDCでそれと互換性のある裏画面デバイスコンテキストを作成します。~
次に、ウィンドウのデバイスコンテキストからCreateCompatibleBitmapで、表示画面と互換性のあるビットマップを作成し、SelectObjectで裏画面デバイスコンテキストに関連付けます。~
ビットマップをSelectObjectするのは、windowsx.hにSelectBitmapマクロがありますので、それを使うといいでしょう。~
作り終わったら、今回作成した領域の矩形を、メンバに保存しておきます。~
解放と作成の流れは、以下の通り。~
// バックバッファ解放
void CTodayWnd::freBkBuffer( void )
{
HBITMAP hbitmap = NULL; // ビットマップハンドル
if( m_hOldbmp ){
hbitmap = SelectBitmap( m_hdc, m_hOldbmp );
DeleteBitmap( hbitmap );
m_hOldbmp = NULL;
}
if( m_hdc ){
::DeleteDC( m_hdc );
m_hdc = NULL;
}
ZeroMemory( &m_rect, sizeof( RECT ));
}
// バックバッファ作成
bool CTodayWnd::creBkBuffer( void )
{
HDC hdc = NULL; // デバイスコンテキスト
HBITMAP hbitmap = NULL; // ビットマップハンドル
RECT rect; // クライアント矩形保存用
if( NULL == m_hwnd ){
return false;
}
// クライアント矩形取得
if( !::GetClientRect( m_hwnd, &rect )){
return false;
}
if( 0 == memcmp( &m_rect, &rect, sizeof( RECT ))){ // 領域に変化なし
return true;
}
freBkBuffer();
// ウィンドウのデバイスコンテキスト取得
hdc = ::GetDC( m_hwnd );
if( NULL == hdc ){
goto Error;
}
// コンパチデバイスコンテキスト作成
m_hdc = ::CreateCompatibleDC( hdc );
if( NULL == m_hdc ){
goto Error;
}
// コンパチビットマップ作成
hbitmap = ::CreateCompatibleBitmap( hdc, rect.right, rect.bottom );
if( NULL == hbitmap ){
goto Error;
}
// ビットマップの関連付け
m_hOldbmp = SelectBitmap( m_hdc, hbitmap );
if( NULL == m_hOldbmp ){
goto Error;
}
CopyMemory( &m_rect, &rect, sizeof( RECT ));
return true;
Error:
freBkBuffer();
if( NULL != hdc ){
::ReleaseDC( m_hwnd, hdc );
hdc = NULL;
}
return false;
}
裏画面を作り終わったら、まずはこの裏画面に、透かした背景をシェル側に描いて貰う必要があります。~
このためには、TODAYDRAWWATERMARKINFOという構造体に、アイテムのウィンドウハンドルと、描画対象のデバイスコンテキスト、アイテムウィンドウの描画矩形を設定して、TODAYM_DRAWWATERMARKというメッセージを、アイテムの親ウィンドウに発行するという手順が必要になります。
そして、背景が描かれた裏画面上に、自分で好きなものを描いて、全て描き終わったら、裏画面を表画面に転送という流れになります。~
試しに、背景にテキストを書いて、それを表画面に表示する処理を、WM_PAINTに実装してみます。~
プロトタイプ宣言は以下のように。~
void Today_OnPaint( HWND hwnd ); // WM_PAINT処理
ウィンドウプロシージャも追加します。~
HANDLE_MSG( hwnd, WM_PAINT, Today_OnPaint );
実際の処理は、以下の通り。~
void Today_OnPaint( HWND hwnd )
{
LPCTodayWnd lpCTodayWnd = NULL; // Todayウィンドウクラス
TODAYDRAWWATERMARKINFO tdwmi; // 透かし描画用構造体
PAINTSTRUCT ps; // PAINT構造体
HDC hdc = NULL; // デバイスコンテキスト
lpCTodayWnd = ( LPCTodayWnd )::GetWindowLong( hwnd, GWL_USERDATA );
if( NULL == lpCTodayWnd ){
return;
}
// バックバッファ作成
if( !lpCTodayWnd->creBkBuffer() ){
return;
}
// 裏画面に背景を描いてもらう
ZeroMemory( &tdwmi, sizeof( TODAYDRAWWATERMARKINFO ));
tdwmi.hwnd = hwnd;
tdwmi.hdc = lpCTodayWnd->getBkDC();
if( !::GetClientRect( hwnd, &tdwmi.rc )){
return;
}
if( !::SendMessage( ::GetParent( hwnd ), TODAYM_DRAWWATERMARK, 0, ( LPARAM )&tdwmi )){
return;
}
if( !::ExtTextOut( lpCTodayWnd->getBkDC(), 0, 0, ETO_CLIPPED, NULL, TEXT( "てすと" ), 3, NULL )){
return;
}
// 裏画面を表画面に転送
ZeroMemory( &ps, sizeof( PAINTSTRUCT ));
hdc = ::BeginPaint( hwnd, &ps );
if( NULL == hdc ){
return;
}
::BitBlt( hdc, tdwmi.rc.left, tdwmi.rc.top, tdwmi.rc.right, tdwmi.rc.bottom, lpCTodayWnd->getBkDC(), 0, 0, SRCCOPY );
::EndPaint( hwnd, &ps );
}
これで、以下のような表示になるはずです。~
#ref(test.png,left,nowrap,てすと)
背景のデバイスコンテキストの背景透過モードをONにしていないので、テキストの色抜きがされていないですし、フォントの種類も色もデフォルトなので、見栄えは悪いですが……。~
テキスト描画は、TextOutがWindowsMobileでは使えないので、ExtTextOutを使用しています。~
テキストを描くくらいでしたら、これで問題ないと思いますが、例えば他の画像を、背景の上にアンチエイリアス付きで描画したいとか、部分Blendしてアニメーションしたいとかなった場合は、表示画面と互換性のあるビットマップではなく、メモリアドレスが取れるフルカラーのDIBSectionを作る等して、ピクセル単位でのBlendをする等、工夫が必要になってくるでしょう。~
マスク画像を用意しておいて、お手軽にSRCPAINT、SRCANDを使用してもよさげです。~
とりあえず、ここまでのソースをつけておきます。~
#ref(背景透過表示ソース.zip,left,nowrap,背景透過表示ソース)