執筆者: エディフィストラーニング株式会社 矢嶋 聡

動作確認環境: Visual Studio 2010 (Visual C++ 2010 Express も可能)


Windows Animation Manager を使用すると、アニメーション (刻々と変化するグラフィックス表現) をより簡潔に実装できるようになります。

一般に、Windows Animation Manager を使用しない場合、アニメーションを実現するには、1 コマのフレームを描画する時間の間隔や、アニメーションの変化に必要な座標や色の情報を、開発者自身がプログラム コードを記述して直接管理します。この方法では、特にスムーズな変化や加速度を伴う変化を表現する際に、時間間隔の管理やデータ更新に手間がかかります。しかし、Windows Animation Manager を使用すれば、フレームの時間間隔の管理は自動的に行われ、描画に必要なデータも、時間とともに自動的に変化するようになります。この結果、開発者が実装するコードは、Windows Animation Manager によって発生されるイベントに呼応して、1 つのフレームを描画する単純なロジックになります。さらに、この仕組みは特定のグラフィックス テクノロジには依存していないので、Direct2D、Direct3D、GDI、GDI+ など、様々なグラフィックス テクノロジのもとで利用できます。

Note:
Windows Animation Manager のアニメーションには、Application-Driven Animation と Timer-Driven Animation の 2 種類の形態があります。ここでは、Timer-Driven Animation の例を示します。これらの 2 種類の特徴を含む Windows Animation Manager の全般的な情報は、Windows Animation Manager を参照してください。

なお、Windows Animation Manager は COM ベースの API なので、COM に関する基礎知識が必要です。

以下の例では、ウィンドウをクリックすると、ウィンドウの背景がスムーズに白色から水色 (または、その逆) に変化します。ここでは、Windows Animation Manager を使用したプログラムの全体構成を示すのが目的なので、グラフィックスは GDI を使用しています。また、以下のサンプル コードでは、Visual C++ 2010 (Visual Studio 2010) の「Win32 プロジェクト」を、既定オプション (Windows アプリケーション) のまま新規作成したのち、コードを追加したものです。プロジェクト名を「AppAnim」とするのならば、AppAnim.cpp に以下のコードを追加してください。

追加する部分は、「★[begin]」から「★[end]」までの囲まれたコード ブロックです。追加すべきコード ブロックは、以下のサンプルの冒頭のほか、_tWinMain 関数、InitInstance 関数、および、WndProc 関数の中にあります。

例 1. C++

C++
#include "stdafx.h" 
#include "AppAnim.h" 
 
//★[begin] 
#include <UIAnimation.h> 
 
class CAnimationWindow; 
 
class CTimerEventHandlerImpl : public IUIAnimationTimerEventHandler //←[1] 
{ 
private: 
    DWORD m_dwRef; 
    CAnimationWindow  *m_pTargetWindow; 
public: 
    static HRESULT CreateInstance(CAnimationWindow *pWnd, 
                                  IUIAnimationTimerEventHandler **ppObj); 
    CTimerEventHandlerImpl(CAnimationWindow *pWnd)  
        : m_dwRef(0), m_pTargetWindow(pWnd) {} 
    //IUnknownインターフェイスのメソッド 
    virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppvObj); 
    virtual ULONG   __stdcall AddRef(); 
    virtual ULONG   __stdcall Release(); 
    //IUIAnimationTimerEventHandlerインターフェイスのメソッド 
    virtual HRESULT __stdcall OnPreUpdate(); 
    virtual HRESULT __stdcall OnPostUpdate(); 
    virtual HRESULT __stdcall OnRenderingTooSlow(UINT32 framesPerSecond); 
}; 
 
class CAnimationWindow //←[2] 
{ 
private: 
    // このクラスが管理するウィンドウハンドル 
    HWND m_hWnd;       //←[3] 
    // アニメーションコンポーネント 
    IUIAnimationManager *m_pAnimationManager; //←[4] 
    IUIAnimationTimer *m_pAnimationTimer; 
    IUIAnimationTransitionLibrary *m_pTransitionLibrary; 
    // アニメーション変数 
    IUIAnimationVariable *m_pAnimationVariableRed; //←[5] 
 
public: 
    CAnimationWindow(HWND hWnd); 
    ~CAnimationWindow(); 
 
    void ChangeColor(); 
    void Invalidate(); 
    void DrawClientArea(HDC hDC, const RECT &rcPaint); 
    void OnDestroy(); 
}; 
 
HRESULT CTimerEventHandlerImpl::CreateInstance(CAnimationWindow *pWnd, //←[6] 
                                               IUIAnimationTimerEventHandler **ppObj) 
{ 
    *ppObj = new CTimerEventHandlerImpl(pWnd); 
    if(*ppObj == NULL) 
        return E_OUTOFMEMORY; 
    (*ppObj)->AddRef(); 
    return S_OK; 
} 
 
ULONG __stdcall CTimerEventHandlerImpl::AddRef() //←[7] 
{ 
    return ++m_dwRef; 
} 
 
HRESULT __stdcall CTimerEventHandlerImpl::QueryInterface(REFIID riid, void **ppvObj) //←[8] 
{ 
    //引数の検証 
    if(ppvObj == NULL) return E_POINTER; 
    //IIDの問い合わせ処理 
    if(riid == IID_IUnknown) 
        *ppvObj = this; 
    else if(riid == IID_IUIAnimationTimerEventHandler) 
        *ppvObj = this; 
    else 
    { 
        *ppvObj = NULL; 
        return E_NOINTERFACE; 
    } 
    //参照カウンターの加算 
    ((IUnknown*)(*ppvObj))->AddRef(); 
    //処理成功 
    return S_OK; 
} 
 
ULONG __stdcall CTimerEventHandlerImpl::Release() //←[9] 
{ 
    if(--m_dwRef == 0) 
    { 
        delete this; 
        return 0; 
    } 
    return m_dwRef; 
} 
 
HRESULT __stdcall CTimerEventHandlerImpl::OnPreUpdate() 
{ 
    return S_OK; 
} 
 
HRESULT __stdcall CTimerEventHandlerImpl::OnPostUpdate() //←[10] 
{ 
    m_pTargetWindow->Invalidate();  //←[11] 
    return S_OK; 
} 
 
HRESULT __stdcall CTimerEventHandlerImpl::OnRenderingTooSlow(UINT32 framesPerSecond) 
{ 
    return S_OK; 
} 
 
#define COLOR_MIN (0) 
#define COLOR_MAX (255) 
 
CAnimationWindow::CAnimationWindow(HWND hWnd) //←[12] 
    : m_hWnd(hWnd),  
      m_pAnimationManager(NULL), m_pAnimationTimer(NULL), m_pTransitionLibrary(NULL), 
      m_pAnimationVariableRed(NULL) 
{ 
    //アニメーションコンポーネントの初期化 
    HRESULT hr; 
    hr = ::CoCreateInstance(  // Animation Manager ←[13] 
             CLSID_UIAnimationManager, NULL, CLSCTX_INPROC_SERVER, 
             IID_IUIAnimationManager, (void**) &m_pAnimationManager); 
    if( !SUCCEEDED(hr) ) return; 
 
    hr = ::CoCreateInstance(  // Animation Timer ←[14] 
             CLSID_UIAnimationTimer, NULL, CLSCTX_INPROC, 
             IID_IUIAnimationTimer, (void**)&m_pAnimationTimer); 
    if( !SUCCEEDED(hr) ) return; 
 
    hr = ::CoCreateInstance(  // Animation Transition Library ←[15] 
             CLSID_UIAnimationTransitionLibrary, NULL, CLSCTX_INPROC, 
             IID_IUIAnimationTransitionLibrary, (void**)&m_pTransitionLibrary); 
    if( !SUCCEEDED(hr) ) return; 
     
    IUIAnimationTimerUpdateHandler *pTimerUpdateHandler = NULL; 
    hr = m_pAnimationManager->QueryInterface( //←[16] 
             IID_IUIAnimationTimerUpdateHandler, (void**)&pTimerUpdateHandler); 
    if( !SUCCEEDED(hr) ) return; 
 
    hr = m_pAnimationTimer->SetTimerUpdateHandler( //←[17] 
                 pTimerUpdateHandler, UI_ANIMATION_IDLE_BEHAVIOR_DISABLE); 
    pTimerUpdateHandler->Release(); 
    if( !SUCCEEDED(hr) ) return; 
 
    IUIAnimationTimerEventHandler *pTimerEventHandler = NULL; 
    hr = CTimerEventHandlerImpl::CreateInstance(this, &pTimerEventHandler); //←[18] 
    if( !SUCCEEDED(hr) ) return; 
 
    hr = m_pAnimationTimer->SetTimerEventHandler(pTimerEventHandler); //←[19] 
    pTimerEventHandler->Release(); 
    if( !SUCCEEDED(hr) ) return; 
 
    //アニメーション変数の作成と初期化 
    hr = m_pAnimationManager->CreateAnimationVariable(  //←[20] 
             (DOUBLE)COLOR_MAX, &m_pAnimationVariableRed); 
    if(!SUCCEEDED(hr)) return; 
    hr = m_pAnimationVariableRed->SetLowerBound(COLOR_MIN); 
    if(!SUCCEEDED(hr)) return; 
    hr = m_pAnimationVariableRed->SetUpperBound(COLOR_MAX); 
    if(!SUCCEEDED(hr)) return; 
 
    //色変化のアニメーション初回起動 
    ChangeColor(); //←[21] 
} 
 
CAnimationWindow::~CAnimationWindow() 
{ 
    //アニメーション変数 
    if (m_pAnimationVariableRed) 
    { 
        m_pAnimationVariableRed->Release(); 
        m_pAnimationVariableRed = NULL; 
    } 
    //アニメーションコンポーネント 
    if (m_pAnimationManager) 
    { 
        m_pAnimationManager->Release(); 
        m_pAnimationManager = NULL; 
    } 
    if (m_pAnimationTimer) 
    { 
        m_pAnimationTimer->Release(); 
        m_pAnimationTimer = NULL; 
    } 
    if (m_pTransitionLibrary) 
    { 
        m_pTransitionLibrary->Release(); 
        m_pTransitionLibrary = NULL; 
    } 
} 
 
void CAnimationWindow::ChangeColor() //←[22] 
{ 
    HRESULT hr; 
 
    // 現在の色の取得 
    INT32 currentRed; 
    int targetRed; 
    hr = m_pAnimationVariableRed->GetIntegerValue(&currentRed);    //←[23] 
    if( !SUCCEEDED(hr) ) return; 
    targetRed = (currentRed == COLOR_MAX) ? COLOR_MIN : COLOR_MAX; //←[24] 
 
    //ストーリーボードの作成 
    IUIAnimationStoryboard *pStoryboard = NULL; 
    hr = m_pAnimationManager->CreateStoryboard(&pStoryboard); //←[25] 
    if ( SUCCEEDED(hr) ) 
    { 
        //アニメーション変数のためにトランジッション(遷移)の作成 
        const UI_ANIMATION_SECONDS DURATION = 0.5; 
        const DOUBLE ACCELERATION_RATIO = 0.5; 
        const DOUBLE DECELERATION_RATIO = 0.5; 
        IUIAnimationTransition *pTransitionRed; 
        hr = m_pTransitionLibrary->CreateAccelerateDecelerateTransition( //←[26] 
                 DURATION, targetRed, ACCELERATION_RATIO, DECELERATION_RATIO, 
                 &pTransitionRed); 
        if( SUCCEEDED(hr) ) 
        { 
            //トランジッションをストーリーボートに追加する 
            hr = pStoryboard->AddTransition(m_pAnimationVariableRed, pTransitionRed); //←[27] 
            if( SUCCEEDED(hr) ) 
            { 
                //現在の時刻を取得して、ストーリーボードのスケジューリングを行う 
                UI_ANIMATION_SECONDS secondsNow; 
                hr = m_pAnimationTimer->GetTime(&secondsNow); //←[28] 
                if ( SUCCEEDED(hr) ) 
                { 
                    hr = pStoryboard->Schedule(secondsNow);  //←[29] 
                } 
            } 
            pTransitionRed->Release(); 
        } 
        pStoryboard->Release(); 
    } 
} 
 
void CAnimationWindow::Invalidate() //←[30] 
{ 
    ::InvalidateRect(m_hWnd, NULL, FALSE); //←[31] 
} 
 
void CAnimationWindow::DrawClientArea(HDC hDC, const RECT &rcPaint) //←[32] 
{ 
    INT32 red; 
    HRESULT hr = m_pAnimationVariableRed->GetIntegerValue( &red ); //←[33] 
    if ( SUCCEEDED(hr) ) 
    { 
        COLORREF color = RGB(red, 255255); 
        HBRUSH brush = ::CreateSolidBrush(color); 
        ::FillRect(hDC, &rcPaint, brush); 
        ::DeleteObject(brush); 
    } 
} 
 
void CAnimationWindow::OnDestroy() 
{ 
    //タイマーの解除 
    if (m_pAnimationTimer != NULL) 
    { 
        m_pAnimationTimer->SetTimerEventHandler(NULL); 
    } 
} 
 
CAnimationWindow *pAnimationWnd; 
 
//★[end] 
 
#define MAX_LOADSTRING 100 
 
// [省略] 
 
int APIENTRY _tWinMain(HINSTANCE hInstance, 
                     HINSTANCE hPrevInstance, 
                     LPTSTR    lpCmdLine, 
                     int       nCmdShow) 
{ 
    UNREFERENCED_PARAMETER(hPrevInstance); 
    UNREFERENCED_PARAMETER(lpCmdLine); 
 
    //★[begin] 
    HRESULT hr = ::CoInitialize(NULL);  //←[34] 
    if(hr != S_OK) return hr; 
    pAnimationWnd = NULL; 
    //★[end] 
 
    // [省略] 
 
    //★[begin] 
    delete pAnimationWnd; 
    ::CoUninitialize();  //←[35] 
    //★[end] 
 
    return (int) msg.wParam; 
} 
 
// [省略] 
 
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 
{ 
 
    // [省略] 
 
    //★[begin] 
    pAnimationWnd = new CAnimationWindow(hWnd); //←[36] 
    //★[end] 
 
    ShowWindow(hWnd, nCmdShow); 
    UpdateWindow(hWnd); 
 
    return TRUE; 
} 
 
// [省略] 
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
 
    // [省略] 
 
    //★[begin] ←[37] 
    case WM_PAINT: //←[38] 
        hdc = BeginPaint(hWnd, &ps); 
        if(pAnimationWnd != NULL) 
        { 
            pAnimationWnd->DrawClientArea(hdc, ps.rcPaint); //←[39] 
        } 
        EndPaint(hWnd, &ps); 
        break; 
    case WM_LBUTTONDOWN: 
        if(pAnimationWnd != NULL) 
        { 
            pAnimationWnd->ChangeColor(); 
        } 
        break; 
    case WM_DESTROY: 
        if(pAnimationWnd != NULL) 
        { 
            pAnimationWnd->OnDestroy(); 
        } 
        PostQuitMessage(0); 
        break; 
    //★[end] 
    default: 
        return DefWindowProc(hWnd, message, wParam, lParam); 
    } 
    return 0} 
 
 
 

例 1 において、主なオブジェクトの関係を示すと、次図のようになります。

図 1

図 1. 主なオブジェクトの相互関係

今回のサンプル コードは図 1 の右側に該当し、主なクラスとして (a) の CAnimationWindow クラスと (b) CTimerEventHandlerImpl クラスが定義されています。それぞれ、例 1 の [2] と [1] に記述されています。

このうち、CAnimationWindow クラスは、アニメーション機能を伴うウィンドウを表すクラスです。[3] のメンバー変数 m_hWnd には、このクラスが管理するウィンドウ ハンドルが代入されます。このサンプルでは、メイン ウィンドウのハンドルが代入され、メイン ウィンドウを管理します。

CAnimationWindow クラスの役割としては、図 1 の (1) に示すように Windows Animation Manager の各種オブジェクトを利用して、アニメーションの制御を行います。このため、例 2 の [4] 以降の 3 つのメンバー変数には、各オブジェクトにアクセスするためのポインターが定義されています。これらのオブジェクトは COM オブジェクトなので、この 3 つの変数は COM インターフェイス ポインターです。

また、アプリケーションの起動時に、InitInstance 関数の中の [36] で CAnimationWindow のインスタンスが 1 つ作成され、アプリケーション実行中は常駐します。そして、ウィンドウ プロシージャー (WndProc 関数) の中の、[37] から始まるコード ブロックでは、WM_PAINT、WM_LBUTTONDOWN、および、WM_DESTROY の各イベントに応じて、CAnimationWindow インスタンスの各種動作が実行されます。

一方、図 1 の (b) の CTimerEventHandlerImpl クラスは、この図の (3) に示すとおり、Windows Animation Manager 側の Animation Timer オフジェクトから、1 つのフレームを描画するタイミングでイベントの通知を受けるクラスです。その際には、COM インターフェイスを介して、CTimerEventHandlerImpl クラスのメソッドが呼び出されます。このため、例 1 の [1] にある CTimerEventHandlerImpl クラスの定義には、IUIAnimationTimerEventHandler という名前の COM インターフェイスを実装するように記述しています。このインターフェイスは、Windows Animation Manager に定義された COM インターフェイスです。

次に、必要となる主な手順を確認します。

Windows Animation Manager は、COM ベースのオブジェクトを提供するので、COM を利用可能にするため、[34] と [35] では、それぞれ CoInitialize 関数と CoUninitialize 関数を呼び出して、COM の初期化と後処理を実行しています。

[12] の CAnimationWindow クラスのコンストラクターでは、このウィンドウ クラスで必要となるアニメーションのために、Windows Animation Manager の各種オブジェクトのインスタンス化や初期化を行っています。たとえば、[13]、[14]、および、[15] では、CoCreateInstance 関数を呼び出して、必要な COM オブジェクトを用意しています。

[13] でインスタンス化する Animation Manager は、Windows Animation Manager の中核となるオブジェクトです。また、[14] の Animation Timer は、今回の Timer-Driven Application で必要となるタイマーであり、1 フレーム分の描画が必要になると、イベントを通知するオブジェクトです。[15] の Animation Transition Library は、アニメーションの変化を表す遷移オブジェクト (Transition オブジェクト) を作成するために使用します。

また、[16] と [17] の一連の手順は、Animation Manager と Animation Timer とを接続する手順であり、Animation Timer を使用する上で必要です。

さらに、[18] では CTimerEventHandlerImpl クラスに実装されたヘルパー関数 CreateInstance を呼び出して、このクラスのインスタンスを作成しています。そして、このインスタンスは [19] の SetTimerEventHandler メソッドの引数として渡され、これによって Animation Timer からのイベントを受け取れるようになります。

そして、[20] では CreateAnimationVariable メソッドを呼び出して、「アニメーション変数」を作成しています。このアニメーション変数とは、Windows Animation Manager のもとで時間とともに自動的に変化する変数です。ここでは、ウィンドウの背景色を変化させるので、背景色の RGB の 3 原色のうち、赤色の変化を表す変数として利用します。この変数は、[20] メソッドの 2 番目の引数として渡されたポインター m_pAnimationVariableRed で参照できるようになります。

コンストラクターの最後の処理として、[21] では ChangeColor メソッドを呼び出して、アニメーションを 1 回実行しています。ChangeColor メソッドは [22] に定義してあります。このメソッドは、ウィンドウをクリックした際などにも呼び出されます。メソッドの内部では、アニメーションを実行すべく、1 回分のスケジューリングを行います。図 1 でいうと (2) に当たります。

[22] のこの ChangeColor メソッドでは、背景色の赤色成分を変化させるアニメーションのスケジューリングを行っています。このために、[23] では GetIntegerValue メソッドを呼び出して、現在のアニメーション変数の値を変数 currentRed に格納し、[24] ではアニメーションによる変化後の値を決定し、変数 targetRed に代入しています。つまり、現在の currentRed から targetRed へと色が変化するアニメーションをこのあと作成します。

変化を行うアニメーションのためには、1 つの変化を表す遷移オブジェクト (Transition) と、1 つ以上の遷移オブジェクトを取りまとめるストーリー ボード オブジェクト (Storyboard) が必要です。そのため、[25] では CreateStoryboard メソッドを呼び出してストーリー ボードを作成し、[26] では遷移オブジェクトを作成します。さらに、[27] では、AddTransition メソッドを呼び出してストーリー ボードに遷移オブジェクトを追加します。

最後に、ストーリー ボードをスケジューリングする手順が、[28] と [29] です。ストーリー ボードがスケジューリングされることで、アニメーションが開始されます。

ここでのアニメーションの開始という意味は、Animation Timer によって、1 フレームごとの描画のタイミングのイベントが発生するようになるということです。このイベントに呼応した 1 フレーム分の描画は、プログラマーの責任です。1 フレームの描画の流れは、図 1 の (3) から (5) です。ここで、例 1 のソース コードに当てはめて確認してみましょう。

Animation Timer がイベントを通知するために、IUIAnimationTimerEventHandler インターフェイスを介して、CTimerEventHandlerImpl クラスのいくつかのメソッドを呼び出します。CTimerEventHandlerImpl クラスは、COM オブジェクトであり、IUnknown インターフェイスも実装しています。[7]、[8]、および、[9] にある AddRef、QueryInterface、Release の各メソッドは、一般的な COM オブジェクトの実装です。

イベント通知の際には、[10] の OnPostUpdate メソッドが呼び出され、[11] にあるように、CAnimationWindow オブジェクトの Invalidate メソッドを呼び出します。これは図 1 でいうと (4) の部分です。

さらに、呼び出された [30] の Invalidate メソッドでは、[31] のように Win32 API の InvalidateRect 関数を呼び出し、結果として、WM_PAINT イベントが発生します。すると、[38] のコード部分が実行され、[39] では 1 フレーム分の描画を行う DrawClientArea メソッドを呼び出します。DrawClientArea メソッドは[32] に定義されています。結局、[30] の Invalidate メソッドから間接的に[32] の DrawClientArea メソッドを呼び出しています。これは図 1 でいうと (5) の部分です。これで 1 フレーム分の描画ができるようになりました。

なお、[32] の DrawClientArea メソッドでは、GDI の関数を使用して、ウィンドウの背景を描画します。このとき、赤色成分の値を取得するために、[33] のように GetIntegerValue メソッドを呼び出して、アニメーション変数の値を参照しています。アニメーション変数の値は、Windows Animation Manager 側で自動的に更新されるので、単純にアニメーション変数の値を参照して描画するだけで、背景色が変化する状況を表現できます。

これで一通りの流れの確認が済みました。これを実行して、メイン ウィンドウのクライアント領域をクリックすると、水色から白色 (またはその逆) に色がスムーズに変化します。変化を伴う色のデータの管理や、フレームの描画間隔は、Windows Animation Manager が管理してくれます。

Note:
以下のアドレスでは、ここで取り上げた Timer-Driven Animation に関して、より高度な実装のサンプル コードを入手できます。このアドレスで入手できるサンプル TimerDriven.zip では、GDI+ を用いた例になっています。また、このサンプルでは Animation Timer から IUIAnimationTimerEventHandler インターフェイスを介してイベントを受け取る COM オブジェクトに関して、IUnknown インターフェイスの実装など、あまり変化しないルーチンワークの部分もあるので、このルーチンワークの部分を予め実装した CUIAnimationTimerEventHandlerBase<T> テクプレート クラスがヘルパー クラスして用意されています。


Code Recipe Visual C++ デベロッパー センター

ページのトップへ