Animated Dialog Menu with Client Shadows





3.00/5 (12 votes)
Oct 24, 2003
6 min read

77027

1223
This article shows how to create an animated menu, on any CWnd
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 yourCDummy
source file, so you can create instances.
- Create a menu bar dialog, like
CDummy
. This one is inherited fromCDialog
, but it could be anyCWnd
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 ofCDummy
and initialize it (inOnInitDialog()
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 bylistMenu[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 returnsERROR_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.