By: Kenn Scribner for TechTalk
Run the demo (all demos require Visual C++ 5.0!) | |
Download the demo source |
Background: The first few lines of the article are really true...I did want to add a status bar to my applications. :) I was working in the C/SDK arena then, so I read Nancy Clut's book on programming the Windows 95 interface, which helped me get a status bar going with my old apps. But how to do it with C++/MFC? I found some obscure references, while other books mentioned it could be done and maybe gave a single line of code to do it (without all of the necessary steps to set up that code!). After bloodying my forehead a few times, I got it down. It's really quite simple, but someone once told me everything is simple once you know how. |
The Lowly Status Bar
I remember my thoughts when I first saw an application with a status baryou know, the informational control at the bottom of most applications windows. I was wondering "how in the world did they do that?" Now, however, I use them myself in my own application programs. After all, Visual C++ generates them for us by default when we build a basic framework application using AppWizard. Some people, though, may not know how to tap their power to do cool and exotic things. In this issue, well discuss some of the basics, then perform our first exotic trick, that being attaching a child window to the status bar (youll see why shortly). In a future issue, Id like to discuss transparent bitmaps and how we might use them as other status bar "exotic-touch" fodder.
But first, the basics. When you build an initial application using Visual C++s AppWizard, you have the option of incorporating and using a status bar. Assuming youve build such an application, lets see what Visual C++ provided to us:
If all you ever want to do is work with the generic status bar, then you need do nothing. Just compile and go. However, if you want to be a bit more expressive, then you need to modify the default appearance and/or handling of the status bar. Most modifications are made to your source code such that the status bar is created slightly differently. Well do that here in this issue. However, you are also free to modify the size and style of the status bar, as well as add or remove panes, while the program is running, which Im not going to show you here (perhaps in another issue if there is interest).
Enough talk, lets make a change. Open this months demo MainFrm.cpp file and look for this code:
static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, ID_INDICATOR_OK };
This code establishes an integer array of indicator values that are used by the status bar to determine how many panes to create and how large each pane should be. Visual C++s default code looks very much like this, with the exception of the ID_INDICATOR_OK indicator. I added this indicator to this array (the text for which I placed in my string table), which will result in an additional pane when the status bar is created. Similarly, if you didnt want to show the status of the <numlock> key, or any other key, simply remove the ID_INDICATOR_NUM (or corresponding) indicator. That pane will not be created. When we execute CMainFrame::OnCreate(), the following code will initialize the status bar and prepare it to be shown to the user:
if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\n"); return -1; // fail to create }
Now we have a status bar attached to our main window.
If we wanted to modify the text shown in any of the panes, we would execute this code somewhere in CMainFrame:
CString strMsg; strMsg.LoadString(ID_INDICATOR_OK); // or any other string table text // resource or string m_wndStatusBar.SetPaneText(4,strMsg,TRUE); // pane 4 is our "OK" pane
Well, setting text is nice, but pretty boring. In my example, I might check for some sort of error condition somewhere, and if I found none, Id display "OK". If I found an error, I might display "ERROR" or some other descriptive message. Ho hum. Yawn. Lets do something a bit more fun!
Youve probably noticed Visual C++ shows you the status of your project as its loading from disk by using a progress control, which has been installed onto Visual C++s status bar. Mine looks like this:
What theyve done is to create a new progress bar control and install it as a child window of the status bar. Then, they make the usual calls to the progress bar (perhaps using CProgressCtrl), which updates its visual display. Lets do something similar, only well use an animated control (which we learned about last issue). This animated control will "pop-up" when an error condition is noted. Okay, its a demo, so we wont really see any error. Itll show up when we press a toolbar button. Well use our imagination to believe there was some error or alert condition that the user must address. Dont get too hung up over the type of controlany control could be used, whether its a button, progress bar, or some other home-brew control youve built.
First, we need to decide where to place our control. For the demo, I added a new pane (ID_INDICATOR_OK), so Ill put the control there. We need to determine the window coordinates we require to create our control, so well use the CStatusBar::GetItemRect() function to provide us with the coordinates we need. When we find these coordinates, we need to remember they delineate the "bounding rectangle", which encloses the entire pane. If we want to keep our 3D effect intact, we need to reduce the size of the rectangle before we pass it to our controls creation routine. Here is the code I used in the demo:
CRect rcPane; m_wndStatusBar.GetItemRect(4,&rcPane); // rect includes borders... rcPane.DeflateRect(1,1); // so decrease size...
If youve examined the demo code, you noticed I called CMainFrame::RecalcLayout() before I asked for the panes bounding rectangle. I had to make this function call because the window has not been shown, which means the status bars pane rectangles are all invalid and useless. By calling RecalcLayout(), their sizes are calculated and are now useful to us. Its a handy trick.
Once we have a window rectangle properly initialized, we have what we need to create a new control. For our animation control, I used this code:
m_CAnimCtrl.Create(WS_CHILD|ACS_CENTER|ACS_TRANSPARENT,rcPane, &m_wndStatusBar,IDR_ALERTAVI); m_CAnimCtrl.Open(IDR_ALERTAVI);
Here, I created the control (note it is a child window) and loaded our AVI resource. Unlike a control in a dialog box, we must set the style bits for the control window by hand and call the creation routine (this is not handled for us, as it was last issue). In this case, I wanted transparent bitmaps used. I also asked it to be "centered"this is important! This causes the control to draw the AVI bitmaps in the center of the window rectangle rather than their actual size. In this manner, I keep the size of the status bar unchanged, at the cost of a smaller animated image. We also must remember to later add code to CMainFrame::OnSize() to handle the case where the user is sizing the window (well need to move this control in such a case).
For the demo, I added a menu item and toolbar button to activate the control. In my handler, I have all of the code I need to start the controls animation and show it to the user. Conversely, this routine turns the control off and hides the window. Its important to "turn off" the animation, as this control uses a separate thread to perform the animation. If you dont need the animation to be in effect, you save CPU cycles by stopping the animation, which then terminates the thread (watch the threads shut down in Visual C++s debugger window!). Here is my handler code:
void CMainFrame::OnViewAlert() { // Toggle state of alert indicator. m_bAlertActive = !m_bAlertActive; // Now, take action depending upon new state. if ( m_bAlertActive ) { // Blank the text. Note we don't repaint, as the control // will force a repaint when we show the child window in a // moment. m_wndStatusBar.SetPaneText(4,"",FALSE); // User may have resized window, so we determine where the // pane currently is and move the control window there. CRect rcPane; m_wndStatusBar.GetItemRect(4,&rcPane); // rect includes // borders... rcPane.DeflateRect(1,1); // so decrease size... m_CAnimCtrl.MoveWindow(&rcPane,FALSE); // no need to repaint // here // Start the control, then show the window. m_CAnimCtrl.Play(0,-1,-1); m_CAnimCtrl.ShowWindow(SW_SHOWNORMAL); } // if else { // Hide the window, then shut the control down. m_CAnimCtrl.ShowWindow(SW_HIDE); m_CAnimCtrl.Stop(); // Fill in the text. CString strMsg; strMsg.LoadString(ID_INDICATOR_OK); m_wndStatusBar.SetPaneText(4,strMsg,TRUE); } // else }
For this control, or any other control using additional memory, be sure to release the memory and/or resource when youre finished. In my case, I added the CAnimateCtrl::Close() operation to my CMainFrame::DestroyWindow() function. This is more than just being nice memory is a limited commodity and should be used properly. This is even more critical when you use the very limited "GDI Resource" memory, which an AVI file would use. When that gets filled, Windows could crash.
Well, thats it! Ive probably beat animation controls to death, but we did examine some neat effects we can provide our users via the status bar. As usual, you may download the demo program (see top of page). Also as usual, I used Visual C++ 5.0, so if youre using an earlier version, youll need to rebuild the project if you would like to experiment and recompile. In another page page, well take a look at "transparent bitmaps" and see how we might add other interesting effects to our status bar (or other window). Until then, happy coding!
Comments? Questions? Find a bug? Please send me a note! |