This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


September 1999

Microsoft Systems Journal Homepage

C++ Q&A

Code for this article: Sept99CQ&A.exe (63KB)

Paul DiLascia is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992) and a freelance consultant and writer-at-large. He can be reached at askpd@pobox.com or http://pobox.com/~askpd.

Q I would like to create the image animation window that I see in Microsoft® Internet Explorer 4.0. I'm specifically referring to the spinning globe in the upper-right corner. How do I do it?
Tim Schreiner
and many others
A It's quite easy if you're using Visual C++® 6.0 and rebars. All you need is a doohickey called CAnimateCtrl. The basic idea is to create one of these animation controls and add it to your rebar. I wrote a little program called TestTBAnim that shows how. TestTBAnim doesn't do much, but it has Play and Stop buttons that show how to start and stop the animation. The MFC sample app, MFCIE, starts the animation whenever a document is loading, and stops when loading is complete—just like Internet Explorer. Figure 1 shows TestTBAnim; Figure 2 is the best way I can think of to show you my exploding star animation—that is, until MSJ becomes available on electronic paper.
Figure 1 TestTBAnim
      Figure 1 TestTBAnim

Figure 2 Exploding Star Animation
      Figure 2 Exploding Star Animation

      CAnimateCtrl is the MFC control that wraps the Windows® animation control SysAnimate32. It's one of the simplest controls in the entire Windows arsenal. Figure 3 shows the full declaration from afxcmn.h. There are functions to start and stop playing or seek to a particular video frame. SysAnimate32 offers a quick alternative to the much more complex MCI control when all you want to do is run a video clip. CAnimateCtrl is so simple it's hard to mess up. (Though I discovered a way.)
      To add an animation to your toolbar, the first thing you have to do is instantiate the control in your main frame.

 class CMainFrame : public CFrameWnd {
 protected:   
     CAnimationCtrl m_wndAnim; 
 }; 
Next, create the window and add it to your app's rebar. The place to do it is in your frame's OnCreate handler, where you create all the other control bars.

 // In CMainFrame::OnCreate 
 m_wndAnim.Create(WS_CHILD|WS_VISIBLE,  
     CRect(0,0,CXANIMATION,0),
     this,
     IDC_MYANIMATION);
.
.
.
 m_wndReBar.AddBar(&m_wndAnim, NULL,
     NULL, RBBS_FIXEDSIZE | RBBS_FIXEDBMP)); 
Make sure you use RBBS_FIXEDSIZE because you don't want a grip handle that lets users size the window. Once you've created the control and hooked it into the rebar, the only thing left to do is play the animation.

 m_wndAnim.Open("myanim.avi");
 m_wndAnim.Play(0, -1, -1); // loop forever 
      But wait a minute—you don't want to install an AVI file on your customer's machine! That's the programming equivalent of letting your dog poop in the neighbor's yard. To help you avoid such indiscretions, the animation control lets you load the AVI file as a program resource.

 m_wndAnim.Open(IDR_MYANIMATION); 
Now the control looks for a resource of type AVI with IDR_ MYANIMATION as the ID. You provide this resource by adding a line to your RC file:

 IDR_MYANIMATION AVI DISCARDABLE "res\\star.avi" 
      I told you it was easy. In fact, the biggest problem you're likely to have is creating your animation, since SysAnimate32 only understands AVI, not the more common GIF format. If your animation exists in GIF, you'll have to convert it to AVI. Fortunately, there are plenty of third-party tools to do the job.
      Once you have your animation running prettily, you're going to say, "Gee Paul, that looks nice. Now how do I make it open my Web page when I click my mouse on the spinning eyeball?" In order to preempt a flood of email, I'll show you right now.
      To add the hyperlink feature, I invented a new class, CTBAnim. This CAnimateCtrl-derived class uses yet another class I wrote for the December 1997 issue of MSJ, CHyperlink. I used CHyperlink to implement Web links in dialogs (see Figure 4).
Figure 4 Using CHyperlink
      Figure 4 Using CHyperlink

CHyperlink derives from CString and adds a new function, Navigate, that launches the contents of the string using ShellExecute. ShellExecute is smart enough to recognize any protocol in the known universe, like mailto:, http:, or zippo:, so you can write

 CHyperlink mylink = "http://foo.com";
                  mylink.Navigate(); 
and this will launch your browser to foo.com. The CTBAnim control derives from CAnimateCtrl and adds a CHyperlink and an OnLButtonDown handler that navigates the link when the user clicks.

 void CTBAnim::OnLButtonDown(...)
 {
    if (m_link.IsEmpty())
        m_link.LoadString(GetDlgCtrlID());
    m_link.Navigate(); 
 } 
      If the link string is empty, CTBAnim looks for a string resource with the same ID as the control. What could be easier? But when I first ran this code, I was dismayed to discover that no matter how hard I pressed the mouse button, my link wouldn't do anything. So I added a breakpoint in OnLButtonDown and discovered to my further dismay that even if I threw the mouse against the wall in such a manner that the left button was shattered to bits, control still never reached my breakpoint.
      A little spelunking with Spy++ revealed the problem. SysAnimate32 implements a WM_NCHITTEST handler that returns HTTRANSPARENT. What's that, you ask. Whenever the user clicks her mouse on a window, Windows sends it a WM_NCHITTEST message. The window is expected to return one of a set of special values like HTCLIENT to indicate the point is in the window's client area, or HTCAPTION to indicate the point is in the caption bar, and so on. If the window returns HTTRANSPARENT, Windows doesn't send a WM_LBUTTONDOWN at all, but instead attempts to pass the mouse event to the first nontransparent window under the transparent one.
      Well, naturally this doesn't do much to float your boat in the present situation. Fortunately, it's trivial to fix.

 UINT CTBAnim::OnNcHitTest(CPoint point) 
 {
    return HTCLIENT; 
 } 
Once you add the handler, the animation control becomes nontransparent and when the user clicks, Windows will send WM_LBUTTONDOWN as usual.
      If you're not using a rebar for your toolbar, you can still use CTBAnim, but you have to write a little extra code. The problem is that normal toolbars don't have a mechanism for containing child windows the way rebars do. Not to worry, you can still put an animation control inside your toolbar—you just have to handle WM_SIZE. Since I know how lazy you are, I wrote another class, CAnimToolBar. This CToolBar-derived class has a CTBAnim member, plus OnCreate and OnSize handlers. OnCreate creates the animation control and OnSize keeps it in the proper place whenever the toolbar's size changes.

 void CAnimToolBar::OnSize(UINT nType, int cx, int cy) 
 {
     CToolBar::OnSize(nType, cx, cy); 
     CRect rc(cx-m_nWidthAnim, 0, cx, cy); 
     m_wndAnim.MoveWindow(&rc); 
 } 
      You tell your CAnimToolBar what m_nWidthAnim is when you create it. The animation control, m_wndAnim, is a public member of CAnimToolBar, so you can fiddle with it. Sorry, CAnimToolBar doesn't work with floating toolbars. (Personally, I think a floating or vertically docked animation looks really grody, so I'm not going to encourage you by writing the code to do it.) Figure 5 shows the final code for CTBAnim; the rest is included in the code for this article.

Q How do I get the associated icon for a given file from my program? For example, how do I get the icon for the EXE file MSDEV in d:\vc98\msdev.exe?
      

Richard Epstein

       I want to create a toolbar in which each button has a standard OLE server icon on it (for Microsoft Word, the user should see the usual Word icon). From the registry I can retrieve all of the OLE servers and their icon handles using ExtractIcon, but I need a CBitmap to create the toolbar buttons. How can I get a bitmap from the icon?
      
Grigoriy Elbert
A Well, it looks like Grigoriy has answered Richard's question, but let me go into a little more detail and answer them both. To get the icon associated with an executable or DLL, call ExtractIconEx. The newer Ex version is better than the old ExtractIcon because it lets you extract any number of icons, and it lets you get both large and small icons. Once you have an icon handle, you can convert it to a bitmap by following three steps: create a memory device context, select a bitmap into it, and use DrawIconEx to draw the icon into the DC/bitmap.
      To show how it all works in practice, I wrote a little app called ShowIcons that shows all the icons in an executable or DLL. Figure 6 shows it running with SHDOCVW.DLL open. ShowIcons is a vanilla MFC doc/view app.
Figure 6 Shdocvw.dll's Icons
      Figure 6 Shdocvw.dll's Icons

The doc holds two arrays—one for small and one for large icon handles—and the view does the drawing. The interesting functions are CIconsDoc::Serialize, CIconsView::OnDraw, and CIconsView::OnInitialUpdate. Let's start with CIconsDoc::Serialize.

 void CIconsDoc::Serialize(CArchive& ar) 
 {
    if (!ar.IsStoring()) {
      m_nIcons = ::ExtractIconEx(
        ar.GetFile()->GetFilePath(), // file name
        0,                           // start index
        m_pIconsLarge,               // array of HICONs       
        m_pIconsSmall,               // ..ditto       
        MAXNUMICONS);                // max num to get
      ASSERT(m_nIcons != -1);
    } 
 } 
      When the user opens a file, MFC leaps into a frenzy of activity, but control eventually arrives at your doc's Serialize function. CIconsDoc calls ExtractIconEx to load the icons into its arrays, m_pIconsLarge and m_pIconsSmall. These are fixed-length HICON arrays because I was too lazy to allocate them from the heap. Besides, I figure any program with more than 1024 icons deserves to have itself truncated. Once the icons are loaded, it's the view's job to display them.

 // In CIconsView::OnDraw
 for (i=/* each icon*/) {
    ::DrawIconEx(*pdc,   // DC       
          x,0,           // x,y       
          pIconLarge[i], // HICON
          w,h,           // cx,cy     
          0,             // frame num N/A 
          brush,         // background brush
          DI_NORMAL);    // flags } 
Here pIconLarge is the array of large HICONS; w and h are the width and height of a large icon (obtained by calling GetSystemMetrics with SM_CXICON and CM_CYICON), and brush is the background CBrush. DrawIconEx is a great improvement over its predecessor, DrawIcon, (which can't even draw small icons. But alas, MFC has no wrapper for it.
      So much for loading and drawing. Now what happens if you want to change a toolbar button to use one of the icons? You can't paint directly into the toolbar; somehow you have to change the toolbar's bitmap. But DrawIconEx draws into a device context, not a bitmap—so what do you do? This is what Grigoriy wants to know.
      To answer Grigoriy's question, I added a dopey feature to ShowIcons: a new toolbar button that has no purpose whatsoever. But whenever you open a new file, the spurious button changes to look like the first small icon in the file (see Figure 6). Since MFC calls your view's OnInitialUpdate function every time the user opens a new doc, that's the natural place to implement this feature. The first thing to do is create a memory device context.

 // (in CIconsView::OnInitialUpdate) 
 // create memory DC 
 CWindowDC dc(this);
 CDC memdc;
 memdc.CreateCompatibleDC(&dc);

      The CWindowDC isn't used for anything except to create the memory DC. Once you've done that, you can load your toolbar bitmap and select it into memdc.

 CBitmap bm;
 VERIFY(bm.LoadBitmap(IDR_MAINFRAME));
 CBitmap *poldbm = memdc.SelectObject(&bm); 
      Now you can call DrawIconEx with memdc as the device context, and you'll be drawing on the bitmap. As a general principle in Windows, the way you draw on a bitmap (HBITMAP or CBitmap object) is to select the bitmap into a memory device context and begin drawing. You can call whatever GDI functions tickle your brain, and your doodles go on the bitmap. In this case, CIconsView calls DrawIconEx to draw the small icon at (x,y) position (0,0), which corresponds to the first button in the toolbar. Then deselect, set the new bitmap and you're finished.

 memdc.SelectObject(poldbm);
 pToolBar->SetBitmap((HBITMAP)bm.Detach());
 pToolBar->Invalidate(TRUE);         // repaint 
      CToolBar::SetBitmap replaces the toolbar's HBITMAP with the one you pass and destroys the old one. The toolbar expects to hold on to the bitmap, so you must call CBitmap:: Detach. Otherwise, the CBitmap destructor will delete your bitmap when it goes out of scope. Not a pretty sight. Figure 7 shows the final code, which is included in this month's download.

Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com
From the September 1999 issue of Microsoft Systems Journal.