Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Animated Dialog Menu with Client Shadows

0.00/5 (No votes)
5 Nov 2003 1  
This article shows how to create an animated menu, on any CWnd

Sample Image - AnimatedDialogMenu.gif

Introduction

Have you seen a menu with no menu items in it, or just a single item popping up? For example the standard Help menu created in VC has only the "About" item. Some applications start with very basic menu panels. Why not show the dialogs behind the single menu item instead of the menu item itself. There I come with this new idea!

How it works

Making animated dialog menus is truly simple on Windows 98, NT5 and later, the solution to the problem on older systems is far more complex. This is the first version of my code, it surely has lots of "bugs". I hope it is useful to someone.

The key is the AnimateWindow function deeply hidden in the Windows API. A good idea when using "preliminary" windows API functions is to add these two lines before any headers get included in the application code:

   #undef     WINVER
   #define     WINVER 1000000


Heh, not really a good idea, but the only choice for Visual C++ 6 with some service packs and Windows XP Pro SP1 (and yeah, it works!). You should first check if the call to AnimateWindow works without it. WINVER is what VC defines in order to identify the version of windows you are building for. It is predefined and the API declarations refer to it when some compatibility info is required. While compiling you will be informed what to do if you want a stable build.

I created a dialog based application which itself contains a child dialog that serves as a menu bar (namely CDummy). The menu bar dialog handles user requests by popping up the dialogs. It also does draw the effects and most of the cleanup/repaint operations. Simple buttons control the popup operations. A little trick I used is that the IDs of the buttons are consecutive 1026, 1027, 1028, so I can address them by an offset value if necessary. It is not implemented in the demo project.

The code that draws the menus is very straightforward (and slow), the best explanation is just to read along the lines:

void CDummy::ShowDialogMenu(int number, int initial_control)
{
    HideAllDialogMenus();
    if(!statMenu[number])
    {
        CRect rc;
        GetDlgItem(initial_control)->GetClientRect(&rc);
        GetDlgItem(initial_control)->ClientToScreen(&rc);
        this->ScreenToClient(&rc);

        listMenu[number]->SetWindowPos(0,rc.left,rc.bottom,
                    0,0,SWP_NOSIZE|SWP_HIDEWINDOW);
        AnimateWindow(listMenu[number]->GetSafeHwnd(),300,
            AW_ACTIVATE|AW_SLIDE|AW_VER_POSITIVE|AW_HOR_POSITIVE);
        CWindowDC dc(0);
        CDC tdc;
        statMenu[number]=1;
        listMenu[number]->GetClientRect(&rc);
        listMenu[number]->ClientToScreen(&rc);
        dc.MoveTo(rc.left,rc.top);
        dc.LineTo(rc.right,rc.top);
        dc.LineTo(rc.right,rc.bottom);
        dc.LineTo(rc.left,rc.bottom);
        dc.LineTo(rc.left,rc.top);

        tdc.CreateCompatibleDC(&dc);
        CBitmap map1;
        map1.CreateCompatibleBitmap(&dc,1600,1200);
        tdc.SelectObject(&map1);
        tdc.BitBlt(rc.left,rc.top,rc.right+S_SHADE,
                    rc.bottom+S_SHADE,&dc,rc.left,rc.top,SRCCOPY);

        for(int w=rc.right;w<=rc.right+S_SHADE;w++)
        {
            float f=((float)w-rc.right)/S_SHADE;
            for(int e=rc.top+S_SHADE;e< rc.bottom;e++)
            {
                tdc.SetPixel(w,e,RGB(
                    GetRValue(tdc.GetPixel(w,e))/(2-f),
                    GetGValue(tdc.GetPixel(w,e))/(2-f),
                    GetBValue(tdc.GetPixel(w,e))/(2-f)));
            }
        }
        for(int e=rc.bottom;e<=rc.bottom+S_SHADE;e++)
        {
            float f=((float)e-rc.bottom)/S_SHADE;
            for(w=rc.left+S_SHADE;w<=rc.right+1;w++)
            {
                tdc.SetPixel(w,e,RGB(
                GetRValue(tdc.GetPixel(w,e))/(2-f),
                GetGValue(tdc.GetPixel(w,e))/(2-f),
                GetBValue(tdc.GetPixel(w,e))/(2-f)));
            }
        }
        for(e=rc.bottom;e<=rc.bottom+S_SHADE-2;e++)
        {
            float f=((float)e-rc.bottom)/S_SHADE;
            for(w=rc.right+2;w<=rc.right+S_SHADE-2;w++)
            {
                float g=((float)w-rc.right+2)/(S_SHADE);
                tdc.SetPixel(w,e,RGB(
                    GetRValue(tdc.GetPixel(w,e))/(2-(f+g)/2),
                    GetGValue(tdc.GetPixel(w,e))/(2-(f+g)/2),
                    GetBValue(tdc.GetPixel(w,e))/(2-(f+g)/2)));
            }
        }

        dc.BitBlt(rc.left,rc.top,rc.right+S_SHADE,rc.bottom+S_SHADE,
                    &tdc,rc.left,rc.top,SRCCOPY);
    }
}

The shadow is completely owner-drawn, it paints directly on the desktop window. This causes some problems with the cleanup paint job but it is fine for shadows. I will soon update to popup windows and then it will make some sense. However, if you are concerned about efficiency or paint safety you can simply do a CClientDC paint. The hide menu routine is simply a repaint.

Something important to say is that you can change the animation type by modifying the flags in the AnimateWindow. For example change AW_ACTIVATE | AW_SLIDE | AW_VER_POSITIVE | AW_HOR_POSITIVE to AW_ACTIVATE | AW_SLIDE | AW_HOR_POSITIVE or AW_ACTIVATE | AW_SLIDE | AW_VER_POSITIVE or something else as defined in MSDN under AnimateWindow.

Using the code in your apps

I didn't try to make a class to implement the functionality because I wanted to keep the way user controls the dialogs custom. I am open to suggestions here. Anyways I managed to separate the following steps, that should make using the code in your applications easier:

  • Create all dialogs you want to appear as menus. It is recommended to use CDialog inherited dialogs. In properties select child, check control, visible and set foreground. If you don�t specify any of these styles the menu may not paint correctly. They all have to do with the z-order and the parent management, not sure how exactly. You know where to dig for details, do you not. Hey, don't forget to include the proper headers in your CDummy source file, so you can create instances.
  • Create a menu bar dialog, like CDummy. This one is inherited from CDialog, but it could be any CWnd based thing. For the SDK fans - you can use direct handles with slight modifications to eliminate the MFC device contexts. In your main window class declare an instance of CDummy and initialize it (in OnInitDialog() for example) as follows:
        dummy=new CDummy(this);
        dummy->Create(IDD_DIALOG2,this);
        dummy->SetWindowPos(0,0,0,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
  • Declare arrays to store the pointers to your dialogs and the current status (in the demo project: listMenu and statMenu). Create instances of your menu dialogs and put initialization code in the WM_INITDIALOG handler, like that:
    BOOL CDummy::OnInitDialog() 
    {
        CDialog::OnInitDialog();
        
        memset(listMenu,0,sizeof(listMenu));
        menu1.Create(IDD_DIALOG1,this->GetParent());
        menu1.SetWindowPos(0,-1000,-1000,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
        menu2.Create(IDD_DIALOG3,this->GetParent());
        menu2.SetWindowPos(0,-1000,-1000,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
        menu3.Create(IDD_DIALOG4,this->GetParent());
        menu3.SetWindowPos(0,-1000,-1000,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
        menu4.Create(IDD_DIALOG5,this->GetParent());
        menu4.SetWindowPos(0,-1000,-1000,0,0,SWP_NOSIZE|SWP_SHOWWINDOW);
    
        listMenu[0]=&menu1;
        listMenu[1]=&menu2;
        listMenu[2]=&menu3;
        listMenu[3]=&menu4;
    
        return TRUE;  // return TRUE unless you set the focus to a control
    
                      // EXCEPTION: OCX Property Pages should return FALSE
    
    }
  • The events causing the menus to show up or hide must occur in CDummy. Put some buttons or whatever and process the events. In the demo project I used simple buttons. This is how I handle one of my buttons� BN_CLICKED messages:
     void CDummy::OnButton1() 
    {
        ShowDialogMenu(0,IDC_BUTTON1);
    }
    ShowDialogMenu(int arg1,int arg2) shows the dialog on the screen. arg1 tells which dialog should be shown, for example 0 means the dialog referenced by listMenu[0] will be show. arg2 is the ID of the control (static, button, edit box, check box, whatever) next to which the dialog will be drawn. The function gets the client rectangle of the control and draws the dialog just below the rectangle.
  • Handle WM_KILLFOCUS in the main window, one of the reasons all menu dialogs must be child:
    void CDmenuDlg::OnKillFocus(CWnd* pNewWnd) 
    {
        CDialog::OnKillFocus(pNewWnd);
        
        dummy->HideAllDialogMenus();
    
    }
    
  • Handle WM_ACTIVATE in the main window, this kind of fixes the "repaint after lost focus", but not always (see below):
    void CDmenuDlg::OnActivate(UINT nState, CWnd* pWndOther, 
        BOOL bMinimized) 
    {
        CDialog::OnActivate(nState, pWndOther, bMinimized);
        dummy->HideAllDialogMenus();
        Invalidate();
    }
    

Known issues

There are few bugs in Visual C++ or in my code.

  • Sometimes AnimateWindow doesn�t do anything and returns ERROR_SUCCESS. The demo project application will never do that, but your applications may not be so lucky. I didn�t manage to find out what causes the problem, but it applies to all preliminary functions.
  • Clicking on a control in the main window doesn�t make the menu to disappear and paints the control over it. To fix this you may use message reflection or manual message forwarding to the main window, but you have to do that for all controls on the main window. I am working on another solution.
  • If a menu is active and the application loses focus, incorrect repaint will occur. The last one is a mystery to me!
  • On Windows 98 and ME the parallel tasks do not work together very well, I mean they go slow because of the weak OS threading. Do not overload the app with many active threads at a time.

I was hired by the pentagon to do this project. They needed some software to attach to the control interface. I have not included the drivers you see, but I may have cut/left some other code by an accident. Keep you eyes open!

Heh, kidding, but keep your eyes open, I may have forgotten to mention about some little trick you need to make it work. This is a good place to add �If this code works it�s written by Vladimir Ralev else I don�t know who wrote it!�, like someone writing for the MSDN.

Thanks for all comments and fixes.

Credits

Written by Vladimir Ralev <v_ralev@yahoo.com>, 20 October 2003

Special thanks to PJ Naughter for his great work on CTreeFileCtrl & CSortedArray v1.06. I used (a little cut) CTreeFileCtrl to illustrate how easily one can select a file without having to popup a modal dialog.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here