Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > October 1999
October 1999

Microsoft Systems Journal Homepage

C++ Q&A

Code for this article: Oct99CQA.exe (309KB)
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 How can I implement a start-up screen that displays a bitmap in MFC?
Turner Munbrey

A The Visual C++® Component Gallery has a component called Splash screen (see Figure 1). Just open the Components window, select Splash screen, and press Insert. Visual C++ adds a new module (splash.h and splash.cpp) to your project, and some lines of code to your app and CMainFrame classes. You even get a blank bitmap like the one in Figure 2.
Figure 1 Adding a Splash Screen
Figure 1 Adding a Splash Screen

The vanilla splash is nice, but it's not very satisfying. Aside from the glum bitmap, it adds code to your app in several places. There's a CWinApp::PreTranslateMessage override to let the splash process messages, some lines to your InitInstance function to parse command line args, and a line in your CMainFrame to show the splash when your app starts up. A line here, a line there, three lines somewhere else—why all this spaghetti code when it's totally unnecessary? But that's the problem with code generators—they generate code.
In just a moment, I'll show you how to write a splash screen you can install with one line of code. But first let me mention two more significant problems with the prepackaged splash screen.
Problem number one: the splash appears for a fixed number of seconds that you specify—two, say—and then disappears. But often the reason you want a splash screen is to give your users something to look at while your app takes forever to start up. While the splash is running, your app is scrambling to get itself in gear. But if your app is computing—loading a file or initializing some humongoid list structure with ten bazillion items, say—it can't process messages through its main message pump. The component gallery generates a splash window that runs in the same process and thread as the main app, which means it doesn't get messages while your app is chomping at the CPU. If the user clicks to dismiss the splash, nothing happens. Nor will the splash get any timer messages.
Figure 2 Scintillating Splash
Figure 2 Scintillating Splash

Problem number two: the splash screen you get from Redmond uses CBitmap and BitBlt to display its bitmap—which is to say it doesn't do palette realization. That's fine for the melancholic black-and-gray splash in Figure 2, but if your splash has lots of exotic cheery colors, it'll look pathetic in 256 color mode. Of course, nowadays even your grandmother has a 32MB video card, but I'm told some people still use 256 color mode to get more pixels. They must be deranged—but who listens to me?
Needless to say, I wrote my own CSplash class to fix all these inadequacies. What else am I here for? To use CSplash, all you have to do (aside from adding the files to your project and #including the header) is add the promised one-liner to your app's InitInstance function:
 // 2000 msec = 2 sec 
 new CSplash(IDB_SPLASH, 2000); 
Technically that's two lines, but comments don't count. This creates a new splash screen with a bitmap resource loaded from IDB_SPLASH, and displays it for two seconds. Unlike the component manager splash, my splash remains visible until your app creates a main window. In other words, the time-to-display argument is really a minimum. The splash appears for at least two seconds, but longer if your application is slow to rise.
That's it; that's all there is. Just create the splash and forget about it. You don't even have to delete it since CSplash manages its own death. This is the way life should be. No code generators, just import the thing and call it.
If for some reason you want to kill your splash prematurely (perhaps you're feeling aggressive today), there's a way to do it:
 CSplash *pSplash =
     new CSplash(IDB_SPLASH, 2000, 0, &pSplash);
 .
 .
 .
 if (pSplash)
     pSplash->Kill(); 
If you plan to kill the splash, you must provide the constructor with a "back" pointer to your splash pointer, and you must check the splash pointer for NULL before killing it. If you don't, the splash will retaliate by killing your app! Why this subterfuge? Because the splash may already have died by the time you get around to killing it, and you can't kill it twice. If the splash destroys itself—perhaps the user dismissed it—it politely sets your pointer to NULL.
Using CSplash is a stroll in the park, so now let's go inside the code to see how I pulled all those rabbits out my hat. Actually, it's pretty straightforward. First, CSplash is derived from CWinThread, which is to say, a splash is a thread. When you create a new CSplash, the constructor saves its arguments and calls CreateThread (which CSplash inherits from CWinThread) to create a new user interface thread. Control returns immediately to your app, which can hoard the CPU all it likes while—poof!—the splash disappears through a wormhole.
And—poof!—the splash pops out the other side, over here in the gamma quadrant, which is to say, in a new thread. As the thread starts up, MFC goes through its usual initialization conniptions before eventually calling the thread's InitInstance function. CSplash::InitInstance parses the main app's command line to check for -Embedding (embedded OLE object), -Automation (automation server), or -nologo. If any of these are present, InitInstance returns FALSE to terminate the thread; otherwise, it creates the splash window.
 BOOL CSplash::InitInstance()
 {
 •
 •
 • 
 // check for -nologo, etc.
     m_pMainWnd = OnCreateSplashWnd(
         m_nIDRes,   // saved arg: resrc ID   
         // ..  timeout
         m_duration,
         // ..and flags 
         m_flags);   
     return m_pMainWnd != 
         NULL; 
   } 
Note that m_pMainWnd is the main window for the splash thread, not your main app. To create the window, InitInstance passes the buck to another function, OnCreateSplashWnd, which does the dirty work.
 CWnd* CSplash::OnCreateSplashWnd(UINT nIDRes,
                                  UINT duration, 
                                  WORD flags) 
 {
    CSplashWnd *pSplashWnd = new CSplashWnd;
    if (pSplashWnd)
        pSplashWnd->Create(nIDRes, duration, flags);
    return pSplashWnd; 
 } 
CSplashWnd is a new CWnd-derived window class that implements the splash screen. Why put window creation in a separate function? To let you override it, of course. OnCreateSplashWnd is virtual, so if you want to derive a specialized CSplash that creates a specialized CSplashWnd (with animations, perhaps), all you have to do is override OnCreateSplashWnd.
I forgot to mention the flags. When you create a CSplash, you give it flags. Every API has to have some flags. Currently, there are only three: CSplash::KillOnClick tells the splash to do the dismiss-if-the-user-presses-a-key thing; IgnoreCmdLine tells the splash to splash, even if the command line says -nologo (I only added this for my test program); and CSplash::NoWaitForMainWnd presents the wait-for-main-window feature.
Aside from all that, window creation is totally normal. CSplash::OnCreateSplashWnd calls CSplashWnd::Create, which loads the bitmap and calls CreateEx. To handle DIBs correctly, I (re)used the CDib class from my MFC Goodies articles in the January and March 1997 issues of MSJ. Using CDib, loading the bitmap is as simple as this:
 // in CSplashWnd::Create 
 m_dib.Load(nIDRes); 
Drawing it is equally mindless:
 // in CSplashWnd::OnPaint
 m_dib.Draw(dc); 
This is where reusable classes really pay off. Version 1 of CSplash used an ordinary MFC CBitmap; once I got that working, all I had to do was dust off my CDib and plug it in. I left the old code behind in case for some perverse reason you don't want to use CDib. All you have to do is #define NODIB—but why you'd turn down a bunch of free code that makes your bitmaps paint correctly is a mystery to me. (Perhaps your bitmap has a color-challenged palette.) If you don't understand all the fuss made about CDib and palette realization, check out the aforementioned articles.
So far, CSplashWnd is pretty straightforward. I've saved the tricky part for last. Terminating a user interface thread—that is, a thread that displays a window—is always a little delicate. In this case, it can happen in one of three ways: the timer pops, the user clicks or types at the splash, or the app calls CSplash::Kill. In the first case (timer pop), CSplashWnd checks to see whether the app has a main window. If not, it sets another timer to wait another tenth of a second.
 void CSplashWnd::OnTimer(UINT nIDEvent)
 {
    if ((m_Flags & NoWaitForMainWnd) // 
         AfxGetApp()->m_pMainWnd)
       // have main window: OK to die
       SendMessage(WM_CLOSE);
    else
       // no main window: keep splashing
       SetTimer(1,100,NULL);
 }
This is the feature I mentioned earlier; the splash doesn't disappear until the main window comes alive. In the second termination case (user clicks or types at the splash), CSplashWnd::PreTranslateMessage handles the event.
 BOOL CSplashWnd::PreTranslateMessage(MSG* pMsg)
 {
    if (m_flags & CSplash::KillOnClick) {
       UINT msg = pMsg->message;
       if (msg == /* keyboard or mouse */) {
          PostMessage(WM_CLOSE); // die
          return TRUE;           // eat msg
       }
    }
    return CWnd::PreTranslateMessage(pMsg);
 }
Since CSplash runs in its own thread with its own message pump, there's no need to hook the app's PreTranslateMessage as in the component gallery version. PreTranslateMessage is the most convenient place to nip any keystrokes in the bud, before the message pump can translate them. But it's important to post WM_CLOSE (instead of sending it) so Windows® can finish processing the keystroke before the window goes bye-bye.
The last way the thread can die is if the main process calls CSplash::Kill. In this case, CSplash posts WM_CLOSE:
 void CSplash::Kill()
 {
    if (m_pMainWnd)
       // post so thread doesn't block
       m_pMainWnd->PostMessage(WM_CLOSE);
 }
So in all three cases—timer pop, user input, and kill—the result is the same: the splash window gets WM_CLOSE. The Windows default message proc (DefWindowProc) handles WM_CLOSE by calling DestroyWindow to usher your splash window to its eternal resting heap. After the last message has come and gone, MFC calls CSplashWnd::PostNcDestroy to let the object perform any last rites.
 // I be dead
 void CSplashWnd::PostNcDestroy()
 {
    CWinApp* pApp = AfxGetApp();
    CWnd* pMainWnd = pApp->m_pMainWnd;
    if (pMainWnd && IsWindow(pMainWnd->m_hWnd))
       ::SetForegroundWindow(pMainWnd->m_hWnd);
    delete this;
 }
CSplashWnd puts the main window in the foreground and deletes itself. This might be a good time to call ::PostQuitMessage to terminate the thread—but there's no need since CWnd::OnNcDestroy does that already if the window is the same as the running thread's m_pMainWnd. When CWinThread::PumpMessage sees WM_QUIT, it stops pumping and terminates the thread—but not before calling CWinThread::Delete, which does a "delete this." Bye-bye CSplash object. Just before the CSplash merges back into the heap like Odo melding with his Founders, it's destructor notifies the world of its impending croakment by NULLing the caller's back pointer.
 CSplash::~CSplash()
 {
     if (m_ppBackPtr) 
       *m_ppBackPtr = NULL; 
 }

And so the story ends. Figure 3 shows the final code. By implementing CSplash the way I've shown, all the code is self-contained. There's no need to hook into the message loop or any other bizarreness. Just create the splash when your app starts up and forget about it. Whenever you add new functionality to your app, you should always strive for the narrowest interface possible—that is, the one with the fewest calls and interactions. Your code is much more likely to work that way.
With CSplash, the only real work you have to do is the fun part: namely, create a bitmap that looks splashy. Figure 4 shows my test splash. But don't get too carried away! Visual Studio spends so many CPU cycles displaying its splash screen that it's noticeably faster starting up if you add -nologo to the command string in your registry.
Figure 4 Splashy Splash
Figure 4 Splashy Splash
Q How can I parse the command line in an MFC app? My program has several command line options (such as -l, -x, -help). I see there's m_lpCmdLine that points to the command line, but do I have to parse it myself? It seems like there should be some way to make CCommandLineInfo do it, but I can't figure out how.
Amy Brechstler
A You're on the right track. CCommandLineInfo is the class MFC uses to parse command-line options; the generic MFC app starts out with the following lines in InitInstance:
 CCommandLineInfo cmdInfo; 
 ParseCommandLine(cmdInfo); 
ParseCommandLine is a CWinApp member function that calls CCommandLineInfo to do the work. CCommandLineInfo has a virtual function, ParseParam, that parses a single parameter.
 void ParseParam(const TCHAR*
      pszParam,    // text param, sans / or -
      BOOL bFlag,  // TRUE if / or -
      BOOL bLast); // TRUE if last 
CWinApp::ParseCommandLine calls this function repeatedly for each command-line token. Unfortunately, ParseParam is hardcoded to recognize only certain common switches like -Embedding, -Automation, -pt (PrintTo), and so on (see Figure 5). For example, if ParseParam sees -p, it sets m_nShellCommand = CCommandLineInfo::FilePrintTo, and if it sees -Embedding or -Automation, MFC sets m_bRunEmbedded or m_bRunAutomated = TRUE. There can be other effects as well. In the last two cases, MFC sets m_bShowSplash = FALSE and calls AfxOleSetUserCtrl(FALSE) to kill the UI.
CCommandLineInfo is all very fine and dandy, but there's no easy provision to add your own options, other than to derive a new class and override ParseParam. That's a lot of typing for such a simple feature. What you really want is to call some function that will get the value of any options the user may have typed.
 CCommandLineInfo cmdInfo; 
 ParseCommandLine(cmdInfo); 
 if (cmdinfo.GetOption("mumble")) {
     // handle mumble option 
 } 
Naturally, I wrote a new class, CCommandLineInfoEx, that does this (see Figure 6). It has two overloaded GetOption functions: one to get a boolean option as in the previous case, and another to get a string value:
 if (cmdinfo.GetOption("baz", m_sBaz)) { 
     // handle it 
 } 
In this case, m_sBaz is a CString that's filled with whatever the user typed after -baz. For example, if the user typed "MyApp -baz mumble" then m_sBaz will be "mumble." CCommandLineInfoEx has far fewer lines of code than the MFC version with all its hardwired if/then/else statements, and it's totally generic. Why didn't the Redmondtonians give us this? It could be their heads got stuck in assembler mode that day; it happens to the best of us.
How does CCommandLineInfoEx work? Simple. It uses a string hash (CMapStringToString) to store all the options as it parses them. For a string option, it sets the value of the option to the typed string; for a nonstring option, it stores the value "TRUE". The CSplash class I created for the previous question uses CCommandLineInfoEx to parse the -nologo switch.

Q I'm writing an application with MFC that needs to add buttons dynamically to the dialog box. How can I implement the message handlers, given a situation like this?

Shanker

A The standard message map entry for a menu or button command handler looks like this:
 ON_COMMAND(ID_MYCOMMAND, OnMyCommand) 
But if you create the button/menu item on the fly, you may not know the command ID at compile time. It may be stored in a data member somewhere like CSqueegie::m_nMeMyMineID. What to do? Simple: use ON_COMMAND_EX_ RANGE with an infinite range.
 ON_COMMAND_EX_RANGE(0,
     0xFFFF, OnAnyCommand) 
Now MFC routes any command in the range 0 to 0xFFFF—which is all commands—to your handler. Your handler can check for the specific IDs at runtime.
 BOOL CSqueegie::OnAnyCommand(UINT nID) 
 {
    if (nID == m_nMeMyMineID) {
    // do the command
    return TRUE; // handled
    }
    return FALSE;
 }
With ON_COMMAND, your handler function has no arguments and returns void, but with ON_COMMAND_ EX_RANGE, your handler function gets the ID of the command and must return TRUE if you handle the command, FALSE if not. If you return FALSE, MFC will continue routing the command so other objects can get a crack at it.

Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com

From the October 1999 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1999 Microsoft Corporation. All rights reserved.
Terms of Use      Privacy Policy.

© 2015 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft