Vista以降のテーマを用いた「アニメーション」描画のやり方
Vista以降の標準コントロールでは、テーマ(一部を除く)を有効にしているとマウスカーソルを合わせるとじわりとハイライトし、カーソルを別の場所に移動するとフェードアウトするように元の状態に戻るような描画がなされます(クロスフェード)。
アニメーションを気にしない場合は、DrawThemeBackground関数(英語)などを使うだけでテーマを用いた描画が可能なのですが、他のコントロールに合わせてアニメーションを入れたい場合は、BeginBufferedAnimation関数(英語)やそれに関係する関数を使って描画を行う必要があります。
ただし描画側でアニメーションのためのタイマーを自力で作成する必要は無く、アニメーション前と後の(絵の)状態を描画してあげるだけでAPI側が自動的にアニメーションを行ってくれるため、わりと簡単にアニメーションを実現することが出来ます。
以下はWM_PAINT時にボタンの描画(と仮定)を行う際にアニメーション処理を加えたコードの例です。(具体的なボタンの描画は「DrawMyButton」というユーザー定義の描画関数が行うとします。)
HWND hWnd; // ウィンドウのハンドル BOOL bPushed; // ボタンが押されているかどうか BOOL bDisabled; // ボタンが無効になっているかどうか BOOL bHover; // マウスポインタがボタン上にあるかどうか BOOL bOldPushed; // bPushedが変更される直前の値 BOOL bOldDisabled; // bDisabledが変更される直前の値 BOOL bOldHover; // bHoverが変更される直前の値 HDC hDC; PAINTSTRUCT ps; hDC = BeginPaint(hWnd, &ps); // アニメーション実行中のタイマーから呼び出されたかどうか調べる // (タイマー内から呼び出された場合は自動的に描画が行われる) if (!BufferedPaintRenderAnimation(hWnd, hDC)) { // ボタンの領域を取得する RECT rcClient; GetClientRect(hWnd, &rcClient); // 古い状態と新しい状態からフラグを設定する int iFromState, iToState; if (bOldPushed) iFromState = PBS_PRESSED; else if (bOldDisabled) iFromState = PBS_DISABLED; else iFromState = (bOldHover ? PBS_HOT : PBS_NORMAL); if (bPushed) iToState = PBS_PRESSED; else if (bDisabled) iToState = PBS_DISABLED; else iToState = (bHover ? PBS_HOT : PBS_NORMAL); // 状態が変化しているか調べる if (iFromState != iToState) { // 2012/02/06更新 - この呼び出しは不要でした。 //// 実行中のアニメーションを止める(実行中の場合止めないと予期しない表示に…) //BufferedPaintStopAllAnimations(hWnd); // ボタンのテーマオブジェクトを取得する HTHEME hTheme = OpenThemeData(hWnd, L"Button"); BP_ANIMATIONPARAMS animParams = { sizeof(BP_ANIMATIONPARAMS) }; animParams.style = BPAS_LINEAR; // 現在はこのフラグのみ使用可能 if (hTheme) { // フェードする時間を取得する GetThemeTransitionDuration(hTheme, BP_PUSHBUTTON, iFromState, iToState, TMT_TRANSITIONDURATIONS, &animParams.dwDuration); CloseThemeData(hTheme); } else { // テーマが取得できなかったのでフェードする時間を適当に設定する // (0に設定するとフェードせずにすぐ切り替えることが出来る) animParams.dwDuration = (bHover) ? 200 : 800; } // アニメーションバッファを作成する HDC hDCFrom = NULL, hDCTo = NULL; HANIMATIONBUFFER hab = BeginBufferedAnimation(hWnd, hDC, &rcClient, BPBF_COMPATIBLEBITMAP, NULL, &animParams, &hDCFrom, &hDCTo); if (hab) { // 作成に成功した場合、hDCFromとhDCToにそれぞれ // 変更前後のグラフィックを描画する // (hDCFromやhDCToがNULLになる場合もある) if (hDCFrom) DrawMyButton(hDCFrom, &rcClient, iFromState); if (hDCTo) DrawMyButton(hDCTo, &rcClient, iToState); // 描画が終了したらバッファを解放してアニメーションを開始する EndBufferedAnimation(hab, TRUE); } else { // バッファが作成できなかった場合は普通の方法で描画を行う DrawMyButton(hDC, &rcClient, iToState); } // 古いフラグを更新する bOldPushed = bPushed; bOldDisabled = bDisabled; bOldHover = bHover; } else { // 状態変化がない場合は普通の方法で描画を行う DrawMyButton(hDC, &rcClient, iToState); } } EndPaint(hWnd, &ps);
※ BeginBufferedPaint関数という(BeginBufferedAnimationと引数が若干似ている)描画時に便利な関数がありますが、これとBeginBufferedAnimation関数を同時に利用すると上手くアニメーションを行えない場合があります。詳しい追跡調査が出来ていませんが、BeginBufferedPaint関数を使う場合は注意する必要があります。
参考:
- BeginBufferedAnimation関数(英語)のサンプルコード
- バッファリングアニメーション (The So-Software Studio) - ATL/WTLを使った場合
最終更新日: 2011/06/02