March, 2013

  • The Old New Thing

    Raymond's highly scientific predictions for the 2013 NCAA men's basketball tournament

    • 12 Comments

    Once again, it's time for Raymond to come up with an absurd, arbitrary criterion for filling out his NCAA bracket.

    This year, I look at the number of fans of the basketball team's official Facebook page, or one tenth of the number of fans of the school's athletic department, whichever is greater. The fraction 1/10 is completely arbitrary, but that's what makes this algorithm highly scientific.

    Notes on this year's algorithm:

    • Counting fans also includes people who hatewatch the team. I accept this, because if a lot of people hate a team, it's probably because they're "too good."
    • There are over a hundred thousand fans of the stub page for North Carolina Basketball. Too bad they don't count.
    • Others who don't count: Fans of unofficial pages. Now, you might think that this is an unscientific exclusion, becaus a fan of an unofficial page is still a fan. On the other hand, if an unofficial page out-draws the official page, then I blame the school for doing a bad job. Also: highly scientific.
    • Syracuse has not one but two spam pages for their basketball team. (You can tell it's a spam page because it has no meaningful content and was set up by a person selling SEO services.) Both of them have more fans than the official page, one of them by a factor of over 100×.
    • Some schools have a Facebook icon on their Athletics page, but it links to ESPN. I guess I know who their sponsor is.
    • Montana's Facebook icon links to the Facebook home page. Somebody wasn't paying attention in "Web design 101" class.

    Once the field has been narrowed to four teams, the results are determined by a coin flip. (I should have done this when the field reduced to eight teams rather than four, but I messed up and am too lazy to fix it. That's what make this highly scientific.)

    Update:

    • Correct predictions are in green.
    • Incorrect predictions are in red.
    • (!) marks upsets correctly predicted.
    • (*) marks upsets predicted but did not take place.
    • (x) marks actual upsets not predicted.

    Opening Round Games

    Middle Tennessee(2,509) Saint Mary's
    (2,654)
    Saint Mary's(2,654)
    Liberty(3,281) Liberty
    (3,281)
    North Carolina A&T(4,324a)
    Boise State(26,559a) Boise State
    (2,655)
    La Salle 1(3,010a)
    James Madison(247) James Madison
    (247)
    Long Island(755a)

    Group 1

    1Louisville(45,835) Louisville
    (45,835)
    Louisville
    (45,835)
    Oregon
    (50,525) (*)
    Duke
    (82,675)
    16Liberty(3,281)
    8Colorado State(1,915) Missouri
    (35,712)
    9Missouri(357,124a)
    5Oklahoma State(3,428) Oregon
    (50,525) (!)
    Oregon
    (50,525)
    12Oregon(505,253a)
    4Saint Louis(4,209a) New Mexico State
    (690) (*)
    13New Mexico State(6,902a)
    6Memphis(64,967a) Memphis
    (6,496)
    Michigan State
    (69,455)
    Duke
    (82,675)
    11Saint Mary's(2,654)
    3Michigan State(69,455) Michigan State
    (69,455)
    14Valparaiso(3,664)
    7Creighton(8,175) Cincinnati
    (12,660) (*)
    Duke
    (82,675)
    10Cincinnati(126,600a)
    2Duke(82,675) Duke
    (82,675)
    15Albany(4,060a)

    Group 2

    1Kansas(126,999) Kansas
    (126,999)
    Kansas
    (126,999)
    Kansas
    (126,999) (x)
    Kansas
    (126,999)
    16Western Kentucky(7,513a)
    8North Carolina(686,843a) North Carolina
    (68,684)
    9Villanova(33,889)
    5VCU(17,954a) Akron
    (2,245) (*)
    Michigan
    (43,385)
    12Akron(2,245)
    4Michigan(433,854a) Michigan
    (43,385)
    13South Dakota State(867)
    6UCLA(14,376) UCLA
    (14,376) (x)
    Florida
    (108,604)
    Florida
    (108,604)
    11Minnesota(10,389)
    3Florida(1,086,046a) Florida
    (108,604)
    14Northwestern State(2,299)
    7San Diego State(18,304a) Oklahoma
    (39,465) (*)
    Oklahoma
    (39,465)
    10Oklahoma(394,654a)
    2Georgetown(28,997a) Georgetown
    (2,899) (x)
    15Florida Gulf Coast(2,120)

    Group 3

    1Indiana(132,015) Indiana
    (132,015)
    Indiana
    (132,015)
    Indiana
    (132,015)
    Indiana
    (132,015)
    16James Madison(247)
    8NC State(10,409) NC State
    (10,409)
    9Temple(538)
    5UNLV(5,822) UNLV
    (5,822) (x)
    Montana
    (7,702)
    12California(1,001)
    4Syracuse(2,528) Montana
    (7,702) (*)
    13Montana(77,020a)
    6Butler(87,481a) Butler
    (8,748)
    Marquette
    (18,379)
    Illinois
    (77,222)
    11Bucknell(1,596)
    3Marquette(18,379) Marquette
    (18,379)
    14Davidson(523)
    7Illinois(77,222) Illinois
    (77,222)
    Illinois
    (77,222) (x)
    10Colorado(7,961)
    2Miami(11,061) Miami
    (11,061)
    15Pacific(4,850a)

    Group 4

    1Gonzaga(3,698) Gonzaga
    (3,698)
    Wichita State
    (10,446) (!)
    Wisconsin
    (26,509)
    Ohio State
    (33,536)
    16Southern(3,884a)
    8Pittsburgh(86,187a) Wichita State
    (10,446)
    9Wichita State(10,446)
    5Wisconsin(26,509) Wisconsin
    (26,509) (x)
    Wisconsin
    (26,509)
    12Mississippi(3,198)
    4Kansas State(46,355a) Kansas State
    (4,635) (x)
    13Boise State(26,559a)
    6Arizona(36,578a) Arizona
    (3,657)
    Arizona
    (3,657)
    Ohio State
    (33,536)
    11Belmont(3,956a)
    3New Mexico(97) Harvard
    (778) (!)
    14Harvard(7,781a)
    7Notre Dame(254,683a) Notre Dame
    (25,468) (x)
    Ohio State
    (33,536)
    10Iowa State(13,968)
    2Ohio State(33,536) Ohio State
    (33,536)
    15Iona(2,549a)

    Finals

    Duke Indiana
    Indiana
    Indiana
    Kansas Ohio State
    Ohio State
  • The Old New Thing

    Microspeak: Science project

    • 31 Comments

    A science project is a feature that is really cool and challenging from a technological standpoint but is way overkill for the end-user scenario at hand.

    Back in the late 1990's, a bunch of us cooked up this idea for a networked screen saver that ran at night after most people had gone home from work. You told it the physical location and compass orientation of everybody's monitor. The networked screen saver created a virtual red bouncing ball that traveled around the building in three dimensions. The screen saver showed a viewport into the three-dimensional virtual world that contained the bouncing ball.

    This is a clear example of a science project: Nobody's going to sit there and type the positions and orientations of every computer monitor in the building. Even if we wrote this screen saver, nobody would actually use it. Most of the enjoyment is in actually writing the screen saver than in actually running it.

    One type of science project has high set-up costs for low benefit, like our bouncing ball screen saver.

    Another type of science project requires hardware that very few people have right now. For example, "If you have a tablet connected to at least two touch-enabled external monitors, then you can..."

    A third type of science project is simply trying to solve a problem that nobody really considers to be a problem. You're doing it just for the Gee Whiz factor. For example, "If you have a pair of Bluetooth headphones, and you walk back into range of your computer, the computer can automatically unpause your music." Yeah, I guess you could do that, but it also means that while you are away from your computer, you're walking around looking like an idiot because you're wearing headphones.

    Now, there may be an actual useful feature hiding inside a science project, but until you find that feature and bring it to the surface, what you basically have is a science project.

  • The Old New Thing

    It may be your birthday, but why stop at just the day? Think big!

    • 21 Comments

    I had the pleasure of meeting a friend of a friend who is an odd, quirky sort, in a wholly endearing sort of way. When her birthday comes around, she isn't satisfied to have just one day of celebration. Or even a week. No, she takes the entire month. The entire month of March is open season for taking her to dinner, getting her a gift, or otherwise celebrating what an awesome person she is.

    Every year I receive an automated reminder, "X's birthday: all day." And I say to myself, "What, just the day?"

    Bonus chatter: Her father is from Turkey, and her mother is Swiss (so she's a deli sandwich, half turkey, half swiss, har-har). For her birthday one year, I knitted her a pot holder which was the Swiss flag on one side and the Turkish flag on the other. It was a fortunate coincidence that the two countries have the same color scheme on their flags.

  • The Old New Thing

    Manipulating the positions of desktop icons

    • 12 Comments

    Today's little program demonstrates how you can manipulate the positions of desktop icons.

    The entire program is just scaffolding to get us far enough that we can call IFolder­View::Get­Item­Position and IFolder­View::Select­And­Position­Items.

    First, we adapt the code we saw some time ago that extracts the IFolder­View from a window.

    Reminder: These "Little Programs" do no error checking because they are intended as demonstrations, not production-ready applications.

    void FindDesktopFolderView(REFIID riid, void **ppv)
    {
     CComPtr<IShellWindows> spShellWindows;
     spShellWindows.CoCreateInstance(CLSID_ShellWindows);
    
     CComVariant vtLoc(CSIDL_DESKTOP);
     CComVariant vtEmpty;
     long lhwnd;
     CComPtr<IDispatch> spdisp;
     spShellWindows->FindWindowSW(
         &vtLoc, &vtEmpty,
         SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
    
     CComPtr<IShellBrowser> spBrowser;
     CComQIPtr<IServiceProvider>(spdisp)->
         QueryService(SID_STopLevelBrowser,
                      IID_PPV_ARGS(&spBrowser));
    
     CComPtr<IShellView> spView;
     spBrowser->QueryActiveShellView(&spView);
    
     spView->QueryInterface(riid, ppv);
    }
    

    The Find­Desktop­Folder­View function takes the code from that earlier article and uses it to extract the shell view for the desktop. Everything here should look familiar (just in a different costume), aside from the call to Find­Window­SW, because we are looking for a specific window by location rather than just enumerating through all of them.

    The first parameter to Find­Window­SW. is the folder we are looking for. In our case, we are looking for the desktop.

    The second parameter is reserved and must be VT_EMPTY.

    The third parameter describes the types of windows we are looking for. We use the special SWC_DESKTOP flag (available starting in Windows Vista) to say, "Hey, I know the desktop isn't the sort of thing people think of when they go looking for Explorer windows, but I know what I'm talking about, so let me have it."

    The fourth parameter receives the window handle, which is of no interest to us, but the parameter is mandatory, so we have to give it something.

    The fifth parameter specifies the search options. We use SWFO_NEED­DISPATCH to say, "Please return the IDispatch in the sixth parameter." And the sixth parameter is where we want the IDispatch to be returned.

    Okay, we already have enough to be able to enumerate all the desktop icons and print their names and locations.

    #define UNICODE
    #define _UNICODE
    #include <windows.h>
    #include <shlobj.h>
    #include <exdisp.h>
    #include <shlwapi.h>
    #include <atlbase.h>
    #include <atlalloc.h>
    #include <stdio.h>
    
    // CCoInitialize incorporated by reference
    
    int __cdecl wmain(int argc, wchar_t **argv)
    {
     CCoInitialize init;
     CComPtr<IFolderView> spView;
     FindDesktopFolderView(IID_PPV_ARGS(&spView));
     CComPtr<IShellFolder> spFolder;
     spView->GetFolder(IID_PPV_ARGS(&spFolder));
    
     CComPtr<IEnumIDList> spEnum;
     spView->Items(SVGIO_ALLVIEW, IID_PPV_ARGS(&spEnum));
     for (CComHeapPtr<ITEMID_CHILD> spidl;
          spEnum->Next(1, &spidl, nullptr) == S_OK;
          spidl.Free()) {
      STRRET str;
      spFolder->GetDisplayNameOf(spidl, SHGDN_NORMAL, &str);
      CComHeapPtr<wchar_t> spszName;
      StrRetToStr(&str, spidl, &spszName);
    
      POINT pt;
      spView->GetItemPosition(spidl, &pt);
      
      wprintf(L"At %4d,%4d is %ls\n", pt.x, pt.y, spszName);
     }
     return 0;
    }
    

    After getting the IFolder­View, we also ask for the corresponding IShell­Folder. This isn't actually necessary for enumerating the icons, but it lets us print their names.

    We ask the view for its Items enumeration, then proceed to enumerate each of the items. For each item, we ask the IShell­Folder for its name, and we ask the IFolder­View for its position. Then we print the results.

    Okay, that was neat, but you can do more than just query the positions. You can also modify them.

    int __cdecl wmain(int argc, wchar_t **argv)
    {
     CCoInitialize init;
     CComPtr<IFolderView> spView;
     FindDesktopFolderView(IID_PPV_ARGS(&spView));
    
     CComPtr<IEnumIDList> spEnum;
     spView->Items(SVGIO_ALLVIEW, IID_PPV_ARGS(&spEnum));
     for (CComHeapPtr<ITEMID_CHILD> spidl;
          spEnum->Next(1, &spidl, nullptr) == S_OK;
          spidl.Free()) {
      POINT pt;
      spView->GetItemPosition(spidl, &pt);
      pt.x += (rand() % 5) - 2;
      pt.y += (rand() % 5) - 2;
    
     PCITEMID_CHILD apidl[1] = { spidl };
     spView->SelectAndPositionItems(
         1, apidl, &pt, SVSI_POSITIONITEM);
     }
     return 0;
    }
    

    This time, instead of printing the item's name and position, we jiggle the icon position by a few pixels randomly, then set the jiggled coordinates as the new position.

    Turn off Auto arrange icons and Align icons to grid on the desktop, and then run this program. Hey, look, your icons shifted randomly by a few pixels.

    For extra hijinx, drop a call to spView->Set­Current­Folder­Flags(FWF_AUTO­ARRANGE | FWF_SNAP­TO­GRID, 0) before you enter the loop (to programmatically turn off auto-arrange and snap-to-grid), then put this program in a loop, and slip it onto a friend's (or enemy's) computer.

    More seriously, we can we put the two pieces together to make a program that saves and restores desktop icon positions.

    Second reminder: These "Little Programs" do no error checking because they are intended as demonstrations, not production-ready applications.

    void SavePositions(IFolderView *pView, PCWSTR pszFile)
    {
     CComPtr<IStream> spStream;
     SHCreateStreamOnFileEx(pszFile, STGM_CREATE | STGM_WRITE,
         FILE_ATTRIBUTE_NORMAL, TRUE, nullptr, &spStream);
     CComPtr<IEnumIDList> spEnum;
     pView->Items(SVGIO_ALLVIEW, IID_PPV_ARGS(&spEnum));
     for (CComHeapPtr<ITEMID_CHILD> spidl;
          spEnum->Next(1, &spidl, nullptr) == S_OK;
          spidl.Free()) {
      IStream_WritePidl(spStream, spidl);
      POINT pt;
      pView->GetItemPosition(spidl, &pt);
      IStream_Write(spStream, &pt, sizeof(pt));
     }
    }
    

    The Save­Positions function enumerates all the icons in a view and writes their identities and positions to a file.

    void RestorePositions(IFolderView *pView, PCWSTR pszFile)
    {
     CComPtr<IStream> spStream;
     SHCreateStreamOnFileEx(pszFile, STGM_READ,
         FILE_ATTRIBUTE_NORMAL, FALSE, nullptr, &spStream);
     POINT pt;
     for (CComHeapPtr<ITEMID_CHILD> spidl;
          SUCCEEDED(IStream_ReadPidl(spStream, &spidl)) &&
          SUCCEEDED(IStream_Read(spStream, &pt, sizeof(pt)));
          spidl.Free()) {
      PCITEMID_CHILD apidl[1] = { spidl };
      pView->SelectAndPositionItems(1, apidl, &pt, SVSI_POSITIONITEM);
     }
    }
    

    The Restore­Positions function does the reverse. It reads the identities and positions from the file and calls IFolder­View::Select­And­Position­Items to move the item to its previously-saved position.

    int __cdecl wmain(int argc, wchar_t **argv)
    {
     if (argc != 3) {
      wprintf(L"Usage: %ls save filename\n"
              L"       %ls restore filename\n", argv[0], argv[0]);
      return 0;
     }
     CCoInitialize init;
    
     CComPtr<IFolderView> spView;
     FindDesktopFolderView(IID_PPV_ARGS(&spView));
    
     if (wcscmp(argv[1], L"save") == 0) {
      SavePositions(spView, argv[2]);
     } else if (wcscmp(argv[1], L"restore") == 0) {
      RestorePositions(spView, argv[2]);
     }
     return 0;
    }
    

    And all that's left is to write the main program that calls either the Save­Positions or Restore­Positions function based on the command line parameters.

    Exercise: Discuss what happens if you rename an item on the desktop, and then try to restore its position. What could be done to address this?

  • The Old New Thing

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

    • 5 Comments

    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.

  • The Old New Thing

    Playing with the Windows Animation Manager: Fixing a sample

    • 12 Comments

    Windows 7 provides a component known as the Windows Animation Manager, known to some people by its acronym WAM, pronounced "wham". There are some nice sample programs for WAM on MSDN, but for some reason, the authors of the samples decided to animate the three color components of a resultant color.

    Because apparently the authors of those sample programs can look at a color and say, "Oh, clearly the red component of this color increases gradually at first, then speeds up its rate of increase, and then slows back down until it reaches its final value; while simultaneously the blue component is doing the opposite, but over a shorter time span, and the green component is remaining fixed."

    Today's exercise is to fix the sample program so you can actually see and understand what WAM is doing, rather than just watching psychedelic colors change and saying, "Gee, that's pretty."

    But first, some background:

    Windows Animation is a component which manipulates variables. A variable is a number which varies over time. You tell Windows Animation things like "I would like you to animate this variable from 1 to 10 over the next 7 seconds." You can then interrogate the variable for its current value, and it might say "Right now, the value is 6."

    The idea is that each of these variables is connected to some visual property, like the position of an object. When you paint the object, you consult the current value of the variable to find out where you should draw it.

    One of the annoying bits about Windows Animation is that you have to set up a bunch of stuff just to get things started. You need an animation manager, which is the object that runs the show. You also need an animation timer whose job is to tell the animation manager what time it is. (Under normal circumstances, you would use the default timer, which records real-world time, but you might want to replace it with a special timer for debugging that runs at half-speed, or maybe one which varies its speed based on how fast you clap.)

    Okay, back to fixing the sample.

    Start with the Timer-Driven Animation and make these changes:

                    // disable the initial animation
                    // Fade in with Red
                    // hr = ChangeColor(COLOR_MAX, COLOR_MIN, COLOR_MIN);
    
    HRESULT CMainWindow::DrawBackground(
        Graphics &graphics,
        const RectF &rectPaint
        )
    {
        // Get the RGB animation variable values
    
        INT32 red;
        HRESULT hr = m_pAnimationVariableRed->GetIntegerValue(
            &red
            );
        if (SUCCEEDED(hr))
        {
            INT32 green;
            hr = m_pAnimationVariableGreen->GetIntegerValue(
                &green
                );
            if (SUCCEEDED(hr))
            {
                INT32 blue;
                hr = m_pAnimationVariableBlue->GetIntegerValue(
                    &blue
                    );
                if (SUCCEEDED(hr))
                {
                    // Replace the drawing code as follows
                    SolidBrush brushBackground(Color(255, 255, 255));
                    hr = HrFromStatus(graphics.FillRectangle(
                        &brushBackground,
                        rectPaint
                        ));
    
                    SolidBrush brushCircle(Color(0, 0, 0));
                    hr = HrFromStatus(graphics.FillEllipse(
                        &brushCircle,
                        red, green, 10, 10
                        ));
                }
            }
        }
    
        return hr;
    }
    

    Instead of drawing a psychedelic background color, I draw a small circle using the old red value as the x-coordinate, and the old green value as the y-coordinate. I didn't rename the variables or get rid of the unused blue variable because I wanted to make as few changes as possible.

    Run this program, and click to make the circle move. Observe that when the circle moves, it starts slowly, then accelerates, and then decelerates as it gets closer to its final location. What's more, if you click while the circle is still moving, the circle demonstrates inertia as it turns to head toward its new target location.

    I bet you never noticed the acceleration, deceleration, or inertia in the original background-color version.

    With a little bit of work, you can make the sample even more interesting by making the circle go to where you clicked. It looks like a lot of work when I spell it out below, but most of it consists of deleting code.

    First, do a search/replace and rename m_pAnimationVariableRed to m_pAnimationVariableX, and rename m_pAnimationVariableGreen to m_pAnimationVariableY. Delete m_pAnimationVariableBlue entirely, as well as any references to it. I decided to just bite the bullet and deal with the consequences of renaming/deleting variables.

    Now we can simplify the CMain­Window::Create­Animation­Variables method so all it does is create the two coordinate variables.

    HRESULT CMainWindow::CreateAnimationVariables()
    {
        HRESULT hr = m_pAnimationManager->CreateAnimationVariable(
            0,
            &m_pAnimationVariableX
            );
        if (SUCCEEDED(hr))
        {
            hr = m_pAnimationManager->CreateAnimationVariable(
                0,
                &m_pAnimationVariableY
                );
        }
    
        return hr;
    }
    

    We want the circle to move when you click the mouse, so let's do that. Delete CMain­Window::On­LButton­Down and change the window procedure so that clicks move the circle.

    LRESULT CALLBACK CMainWindow::WndProc(
        HWND hwnd,
        UINT uMsg,
        WPARAM wParam,
        LPARAM lParam
        )
    {
        ...
            case WM_LBUTTONDOWN:
                {
                    pMainWindow->ChangePos(
                        (SHORT)LOWORD(lParam),
                        (SHORT)HIWORD(lParam)
                     );
                }
                return MESSAGE_PROCESSED;
        ...
    }
    

    And rename the member function Change­Color to Change­Pos, and instead of taking red and green, have it take x and y.

    HRESULT CMainWindow::ChangePos(
        INT x,
        INT y
        )
    {
        const UI_ANIMATION_SECONDS DURATION = 0.5;
        const DOUBLE ACCELERATION_RATIO = 0.5;
        const DOUBLE DECELERATION_RATIO = 0.5;
    
        // Create a storyboard
    
        IUIAnimationStoryboard *pStoryboard = NULL;
        HRESULT hr = m_pAnimationManager->CreateStoryboard(
            &pStoryboard
            );
        if (SUCCEEDED(hr))
        {
            // Create transitions for the position animation variables
    
            IUIAnimationTransition *pTransitionX;
            hr = m_pTransitionLibrary->CreateAccelerateDecelerateTransition(
                DURATION,
                x,
                ACCELERATION_RATIO,
                DECELERATION_RATIO,
                &pTransitionX
                );
            if (SUCCEEDED(hr))
            {
                IUIAnimationTransition *pTransitionY;
                hr = m_pTransitionLibrary->CreateAccelerateDecelerateTransition(
                    DURATION,
                    y,
                    ACCELERATION_RATIO,
                    DECELERATION_RATIO,
                    &pTransitionY
                    );
                // delete former "blue" transition
                if (SUCCEEDED(hr))
                {
                    // Add transitions to the storyboard
    
                    hr = pStoryboard->AddTransition(
                        m_pAnimationVariableX,
                        pTransitionX
                        );
                    if (SUCCEEDED(hr))
                    {
                        hr = pStoryboard->AddTransition(
                            m_pAnimationVariableY,
                            pTransitionY
                            );
                        // delete former "blue" transition
                        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
                                    );
                            }
                        }
                    }
    
                    // delete former "blue" transition
    
                    pTransitionY->Release();
                }
    
                pTransitionX->Release();
            }
    
            pStoryboard->Release();
        }
    
        return hr;
    }
    

    Now you can click the mouse on the client area, and the dot will chase it like a puppy.

    The basic idea behind the Windows Animation Library is that for each property you want to animate, you associate an animation variable, and when you want to perform the animation, you create a transition for each variable describing how you want the animation to proceed, put all the transitions into a storyboard, and then schedule the storyboard.

    Of course, you can build optimizations on top of the basic idea. For example, you might not create the animation variable until the first time you need to animate the property. Another optimization is invalidating only the parts of the window that need repainting, rather than invalidating the entire client area. You can do this by registering a change handler on your variables: When the change handler notifies you that a value changed, invalidate the old position and the new position. This will erase the old location and draw at the new location.

    Next time, I'll build a program that animates a hundred objects, just for fun.

  • The Old New Thing

    Closing holes in the update notification pattern

    • 19 Comments

    Suppose you have a function that is registered to be called the next time something gets updated, and suppose that the notification is a one-shot notification and needs to be re-armed each time you want to wait for the next notification. (For example, the Reg­Notify­Change­Key­Value function behaves this way.) Consider the following code fragment:

    void onUpdateThing()
    {
     // get the updated properties of the thing
     getThingProperties();
    
     // ask to be called back the next time it updates
     registerUpdateCallback(onUpdateThing);
    }
    
    mainProgram()
    {
     // get the thing's initial properties
     // and register for updates
     onUpdateThing();
    }
    

    There is a race condition here if the thing updates twice in rapid succession. On the first update, your onUpdateThing function is called. If the second update occurs while get­Thing­Properties is running, then your call to register­Update­Callback will be too late, and you will miss the second update.

    The solution is to register for the next update before studying the previous one.

    void onUpdateThing()
    {
     // ask to be called back the next time it updates
     registerUpdateCallback(onUpdateThing);
    
     // get the updated properties of the thing
     getThingProperties();
    }
    

    That way, if a second update comes in while you're studying the first one, your update callback will be called because you already registered it. (I'm assuming you're only interested in the last update.)

    Of course, this assumes that update requests are queued if the receiving thread is busy. If updates can be received during the execution of get­Thing­Properties, then you will end up in a bad re-entrant situation: During the processing of one update, you start processing a new update. Then when the nested update finishes, you return to the original update, which is now actually performing the second half of the second update.

    Suppose your update code wants to keep the colors of two additional objects in sync with the color of the thing:

    void getThingProperties()
    {
     Color currentThingColor = getThingColor();
     object1.setColor(currentThingColor);
     object2.setColor(currentThingColor);
    }
    

    If the set­Color method creates a re-entrancy window, you can have this problem:

    • Thing changes color to red.
    • on­Update­Thing begins.
    • Register update callback.
    • get­Thing­Properties reads current color as red.
    • get­Thing­Properties sets object 1's color to red. The set­Color method creates an opportunity for re-entrancy by some means. (For example, it may send a message to another thread, causing inbound sent messages to be processed.)
      • Thing changes color to blue.
      • on­Update­Thing begins.
      • Register update callback.
      • get­Thing­Properties reads current color as blue.
      • get­Thing­Properties sets object 1's color to blue.
      • get­Thing­Properties sets object 2's color to blue.
      • get­Thing­Properties returns.
      • on­Update­Thing returns.
    • get­Thing­Properties sets object 2's color to red. (Oops.)
    • get­Thing­Properties returns.
    • on­Update­Thing returns.

    One solution is to use a sequence number (also known as a change counter) that gets incremented each time the thing changes. If there is only one thread which updates the thing, you can try to update it atomically. For example, if the information is in the registry, you can put all the information into a single registry value or use registry transactions.

    If you can associate a change counter with the data, then you can use the following algorithm:

    // start with a known invalid value
    // (If you have multiple listeners, then this naturally
    // needs to be instance data rather than global.)
    LONG lLastChange = 0;
    
    void onUpdateThing()
    {
     bool finished = false;
     do {
      // record the most recent change we've processed
      lLastChange = getThingChangeCount();
    
      getThingProperties();
    
      // ask to be called back the next time it updates
      registerUpdateCallback(onUpdateThing);
    
      // did it change while we were busy?
      LONG lNewChange = getThingChangeCount();
    
      finished = lLastChange == lNewChange;
      if (!finished) {
       // cancel the update callback because we don't
       // want to be re-entered
       unregisterUpdateCallback(onUpdateThing);
      }
     } while (!finished);
    }
    

    Another solution would be to detect the re-entrancy and just remember that there is more work to be done after the previous update finishes.

    // 0 = not busy
    // 1 = busy
    // 2 = busy, and a change occurred while we were busy
    // (If you have multiple listeners, then this naturally
    // needs to be instance data rather than global.)
    int iBusy = 0;
    
    void onUpdateThing()
    {
     // ask to be called back the next time it updates
     registerUpdateCallback(onUpdateThing);
    
     if (iBusy) {
       iBusy = 2;
     } else {
      iBusy = 1;
      do {
       getThingProperties();
      } while (--iBusy);
     }
    }
    

    Note that all of the above examples assume that the on­Update­Thing function has thread affinity.

  • The Old New Thing

    How do I hide Public Libraries on all computers in my organization?

    • 18 Comments

    A customer wanted to know how to hide the libraries named Public (Documents), Public Pictures, and Public Videos on all computers in their organization.

    It turns out that this is already documented in TechNet under the topic Administrative How-to Guides (I found this page by issuing a Web search for ⟨library-ms⟩.)

    The customer is specifically interested in the section titled How to Customize and Deploy Libraries.

    I'll let you read that Web page for the details. I'm just posting this here so that the next customer won't burn an expensive support call on something that's already documented on TechNet. (Though if you're the type of customer who can't find the answer via a Web search, then there's a good chance you won't find this page either...)

  • The Old New Thing

    Redistributing computers among offices for heating purposes

    • 25 Comments
    Some time ago, I joked about the people who rearrange computers in their house during the winter in order to use them as space heaters.

    Turns out this happens a lot at Microsoft. One of my friends said that one of his coworkers used a small heater in her office to keep warm. On the other hand, his office always ran warm because of all the computers in it. They hit upon a simple solution to both problems: "Now she's using a 12 core/24 thread space heater that's a lot quieter than her old one."

    At one point in time, I had a large number of computers in my office, including an Itanium prototype. (You knew it was a prototype because it contained Engineering Styrofoam.) The thing generated a lot of heat. My friend across the hall, on the other hand, had a cold office. Solution: With some help from colleagues, we moved the Itanium across the hall. Two problems solved.

  • The Old New Thing

    Derive the age of the planet Jupiter from the properties of liquid hydrogen and the planet's surface temperature

    • 8 Comments

    I dreamed that my homework assignment was to derive the age of the planet Jupiter. The professor hinted that physical properties of liquid hydrogen and the current Jupiter surface temperature would be useful.

    My classmate Ted, on the other hand, had to extend a C++ base class to record a GUID name.

    The two of us were taking a class together where we watched a reality show about two ditzy celebrities, and the professor said, "The next episode is so stupid, I don't even want anybody to watch it. But they do learn about Sirens of Greek mythology, so can you guys write up a quick one-pager to get everybody up to speed?"

    That's how I ended up dreaming that Ted and I had to write a one-page overview of Greek Sirens.

Page 2 of 3 (30 items) 123