March 15th, 2013

Playing with the Windows Animation Manager: Moving lots of stuff around

We saw last time a sample program that moved a circle around. Today I’ll try to build the classic demo of animating a lot of objects in a list.

This isn’t the prettiest code, but I wanted to make as few changes as possible. Start with the Timer-Driven Animation, and make these changes to the Main­Window.h header file.

struct Item
{
    IUIAnimationVariable *m_pAnimationVariableX;
    IUIAnimationVariable *m_pAnimationVariableY;
    Gdiplus::Color m_color;
};

class MainWindow { …

// HRESULT ChangeColor( // DOUBLE red, // DOUBLE green, // DOUBLE blue // ); HRESULT ChangePos();

… private:

static const int ItemCount = 100; static const int ItemWidth = 40; static const int ItemHeight = 40; static int XFromIndex(int index) { return (index % 10) * 50; } static int YFromIndex(int index) { return (index / 10) * 50; }

// IUIAnimationVariable *m_pAnimationVariableRed; // IUIAnimationVariable *m_pAnimationVariableGreen; // IUIAnimationVariable *m_pAnimationVariableBlue; Item m_Items[ItemCount]; };

From the changes in the header file, I think you see where this is going. Instead of just having one item on the screen, I’m going to put a hundred.

Here are the changes to Main­Window.cpp. First, we need to null out our pointers at construction and clean them up at destruction. (This sample program does not use smart pointers, so I won’t either.)

CMainWindow::CMainWindow() :
    m_hwnd(NULL),
    m_pAnimationManager(NULL),
    m_pAnimationTimer(NULL),
    m_pTransitionLibrary(NULL)// ,
    // m_pAnimationVariableRed(NULL),
    // m_pAnimationVariableGreen(NULL),
    // m_pAnimationVariableBlue(NULL)
{
    for (int i = 0; i < ItemCount; i++)
    {
        m_Items[i].m_pAnimationVariableX = NULL;
        m_Items[i].m_pAnimationVariableY = NULL;
    }
}

CMainWindow::~CMainWindow() { // Animated Variables // SafeRelease(&m_pAnimationVariableRed); // SafeRelease(&m_pAnimationVariableGreen); // SafeRelease(&m_pAnimationVariableBlue); for (int i = 0; i < ItemCount; i++) { SafeRelease(&m_Items[i].m_pAnimationVariableX); SafeRelease(&m_Items[i].m_pAnimationVariableY); }

… }

Next we get rid of the initial animation.

HRESULT CMainWindow::Initialize(
    HINSTANCE hInstance                            
    )
{
    …
                // Fade in with Red
                // hr = ChangeColor(COLOR_MAX, COLOR_MIN, COLOR_MIN);
    …
}

As you might expect, the Create­Animation­Variables method changed completely, since it now has to create the variables for each item. But the basic idea is the same: Create each variable with the appropriate initial value. (It’s also a lot shorter!)

HRESULT CMainWindow::CreateAnimationVariables()
{
    HRESULT hr = S_OK;

for (int i = 0; SUCCEEDED(hr) && i < ItemCount; i++) { m_Items[i].m_color = Color( static_cast<BYTE>(RandomFromRange(COLOR_MIN, COLOR_MAX)), static_cast<BYTE>(RandomFromRange(COLOR_MIN, COLOR_MAX)), static_cast<BYTE>(RandomFromRange(COLOR_MIN, COLOR_MAX)) ); hr = m_pAnimationManager->CreateAnimationVariable( XFromIndex(i), &m_Items[i].m_pAnimationVariableX ); if (SUCCEEDED(hr)) { hr = m_pAnimationManager->CreateAnimationVariable( YFromIndex(i), &m_Items[i].m_pAnimationVariableY ); } }

return hr; }

The Draw­Background method is becoming increasingly inaccurately-named, since in addition to drawing the background, we also draw the foreground!

HRESULT CMainWindow::DrawBackground(
    Graphics &graphics,
    const RectF &rectPaint
    )
{
    SolidBrush brushBackground(Color(255, 255, 255));
    HRESULT hr = HrFromStatus(graphics.FillRectangle(
            &brushBackground,
            rectPaint
            ));

for (int i = 0; SUCCEEDED(hr) && i < ItemCount; i++) { INT32 x; hr = m_Items[i].m_pAnimationVariableX->GetIntegerValue( &x ); if (SUCCEEDED(hr)) { INT32 y; hr = m_Items[i].m_pAnimationVariableY->GetIntegerValue( &y ); if (SUCCEEDED(hr)) { SolidBrush brush(m_Items[i].m_color); RectF rectItem( static_cast<REAL>(x), static_cast<REAL>(y), static_cast<REAL>(ItemWidth), static_cast<REAL>(ItemHeight)); hr = HrFromStatus(graphics.FillRectangle( &brush, rectItem )); } } }

return hr; }

We change what happens when you click the left mouse button. Instead of changing the color, we shuffle the items randomly.

HRESULT CMainWindow::OnLButtonDown()
{
    HRESULT hr = ChangePos();

return hr; }

And now the money function: Shuffling the items and animating them to their new locations.

HRESULT CMainWindow::ChangePos()
{
    const UI_ANIMATION_SECONDS DURATION = 0.5;
    const DOUBLE ACCELERATION_RATIO = 0.5;
    const DOUBLE DECELERATION_RATIO = 0.5;

// Assign final locations randomly int Destination[ItemCount]; Destination[0] = 0; for (int i = 1; i < ItemCount; i++) { int j = rand() % (i + 1); Destination[i] = Destination[j]; Destination[j] = i; }

// Create a storyboard

IUIAnimationStoryboard *pStoryboard = NULL; HRESULT hr = m_pAnimationManager->CreateStoryboard( &pStoryboard ); if (SUCCEEDED(hr)) { for (int i = 0; SUCCEEDED(hr) && i < ItemCount; i++) { // Create transitions for the position animation variables

IUIAnimationTransition *pTransitionX; hr = m_pTransitionLibrary->CreateAccelerateDecelerateTransition( DURATION, XFromIndex(Destination[i]), ACCELERATION_RATIO, DECELERATION_RATIO, &pTransitionX );

if (SUCCEEDED(hr)) { IUIAnimationTransition *pTransitionY; hr = m_pTransitionLibrary->CreateAccelerateDecelerateTransition( DURATION, YFromIndex(Destination[i]), ACCELERATION_RATIO, DECELERATION_RATIO, &pTransitionY );

// Delete “blue” transition

if (SUCCEEDED(hr)) { // Add transitions to the storyboard

hr = pStoryboard->AddTransition( m_Items[i].m_pAnimationVariableX, pTransitionX ); if (SUCCEEDED(hr)) { hr = pStoryboard->AddTransition( m_Items[i].m_pAnimationVariableY, pTransitionY ); // Delete “blue” transition // Move “Schedule” out of the loop }

pTransitionY->Release(); }

pTransitionX->Release(); } }

// Scheduling code moved outside the loop if (SUCCEEDED(hr)) { // Get the current time and schedule the storyboard for play

UI_ANIMATION_SECONDS secondsNow; hr = m_pAnimationTimer->GetTime( &secondsNow ); if (SUCCEEDED(hr)) { hr = pStoryboard->Schedule( secondsNow ); } }

pStoryboard->Release(); }

return hr; }

It looked like a lot of code, but really wasn’t. The only real change was to add the shuffling code and to put a loop around the code that generates the transitions and adds them to the storyboard.

And there you have it, a program that smoothly animates 100 items each time you click on the window. For me, the fun thing to do is to just click repeatedly on the window and watch the items swirl around like a swarm of insects.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.