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.