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 > January 1997
January 1997


Code for this article: C++0197.exe (20KB)
Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992).

Q I'm trying to implement printing for my application. I want to do something like what Word, Microsoft® Excel, and other programs do, where there's a File Print command that displays the print dialog and a toolbar button that prints without displaying a dialog. I noticed MFC has commands for ID_FILE_PRINT and ID_FILE_PRINT_ DIRECT, so I used ID_FILE_PRINT in my menu command and ID_FILE_PRINT_DIRECT for my toolbar button. I also made Ctrl-P an accelerator key for File Print, and I wanted to make Ctrl-Shift-P an accelerator key for direct printing, but it doesn't work. When I type Ctrl-Shift-P, I still get the print dialog, even though this key is bound to ID_FILE_PRINT_DIRECT. I tried different keys, but no matter which key I used, I couldn't get direct printing to work—I always got the dialog. It works fine from the toolbar button, however. What gives?
Martin Buren

A The problem is a bug in CView::OnFilePrint. This is MFC's standard handler for both ID_FILE_PRINT and ID_FILE_PRINT_DIRECT. In a normal app, you'd have entries in your view's message map that map both commands to OnFilePrint.


 BEGIN_MESSAGE_MAP(CMyView, CView)
    •
    •
    •
    ON_COMMAND(ID_FILE_PRINT,        OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_DIRECT, OnFilePrint)
 END_MESSAGE_MAP()
How does CView::OnFilePrint know whether the command is ID_FILE_PRINT or ID_FILE_PRINT_DIRECT? Therein lies the bug. If you look at the MFC source code for CView::OnFilePrint, you'll discover it's a rather long function, but the overall structure of it goes like this:

 void CView::OnFilePrint()
 {
    CPrintInfo printInfo; // create print info
    ASSERT(printInfo.m_pPD != NULL);
    if (GetCurrentMessage()->wParam == ID_FILE_PRINT_DIRECT) {
    •
    •
    •
       printInfo.m_bDirect = TRUE;
    }
    •
    •
    •
 }
GetCurrentMessage is an MFC function that returns the current message as an MSG structure. For a command, the message is WM_COMMAND and wParam is the command ID. So if the command ID (wParam) is ID_FILE_ PRINT_DIRECT, MFC does direct printing by setting the flag CPrintInfo::m_bDirect. CView::DoPreparePrinting will later examine this flag to determine (among other things) whether it needs to run the print dialog or not. If the wParam for WM_COMMAND is not ID_FILE_PRINT_ DIRECT, OnFilePrint skips that part of the if clause and m_bDirect remains FALSE, its default value initialized by the CPrintInfo constructor.
All this looks pretty innocent at first blush, until you realize that wParam is not exactly the command ID. The command ID is in the low-order word of wParam; the high-order word contains the notification code. Notification codes are used when the WM_COMMAND is a control notification, like EN_CHANGE from an edit control or LBN_DBLCLK from a list box. For menu commands there is no notification code. Or is there?
If you reach into the far recesses of your mind where you store all that useless Windows
® trivia (or if you just read the documentation carefully), you'll recall that Windows uses the notification code for simple commands to tell your app whether the command came from the menu or an accelerator key. If the command came from an accelerator key, as opposed to a menu command or toolbar button, then Windows sets the notification code (high word of wParam) to 1. Surprise, surprise! The idea is to provide a way to differentiate between a command invoked from the menu and one invoked from an accelerator, should you ever wish to do that.
Now you can see the bug; instead of looking at wParam, CView::OnFilePrint should only look at LOWORD(wParam). No doubt this bug snuck in when the MFC folks upgraded from Windows 3.1 to Win32
®. That's when the parameters for WM_COMMAND got shuffled slightly. WPARAM grew from a 16-bit WORD to a full 32-bit LONG. At the same time, the API designers moved the notifica- tion code from HIWORD(lParam) to HIWORD(wParam). Oh well, these things happen.
The question is, how do you fix it? There are many ways, but the quickest one I can think of is to override CView::OnFilePrint to simply clobber the notification code.

 void CMyView::OnFilePrint()
 {
    MSG& msg = AfxGetThreadState()->m_lastSentMsg;
    msg.wParam &= 0xFFFF; // zero hi word
    CView::OnFilePrint(); // pass to MFC
 }
Now, whether the command came from the menu or an accelerator key, HIWORD(wParam) will be zero before CView::OnFilePrint sees it. If for some reason you need to do something different when the command comes from an accelerator, you can always test for HIWORD(wParam)==1 before you clobber it. Note that I used AfxGetThreadState()->m_lastSentMsg instead of GetCurrentMessage. The former is slightly faster because it doesn't call ::GetMessageTime and ::GetMessagePos (like GetCurrentMessage does) to fill in MSG.time and MSG.pt—neither of which you care about here.
I'm sure the folks in Redmond will fix OnFilePrint as soon as they discover the bug (perhaps from reading this). In the meantime, if any of you have apps that you've recently ported from Windows 3.1 to Win32, you might want to test all your accelerated commands and grep your source files for use of wParam in a WM_COMMAND message. Looking at wParam instead of LOWORD(wParam) to get the command ID is a fairly common Win32 gotcha.

Q I'm trying to paint my own special title bar. In my CFrameWnd-derived class, I handle WM_NCPAINT and WM_NCACTIVATE messages to draw a custom caption. My code worked fine for MFC 3.0, but doesn't work with MFC 4.0. After some investigation, I found that when my main window is activated, it first receives WM_NCACTIVATE with wParam=1 (activate) and then WM_NCACTIVATE with wParam=0 (deactivate). You can see similar behavior with Developer Studio itself, but not with other "normal" applications. Do you know anything about this behavior that could help me? I'll be grateful for any clue.

Adam Sapek

A Well, you know, MFC is just getting so quirky these days. Indeed, under MFC 4.0, any time your window gets activated, you'll get WM_NCACTIVATE(TRUE) to activate it, immediately followed by WM_NCACTIVATE(FALSE) to deactivate it. The instant your poor title bar gets activated, it gets deactivated before you can even say "millisecond."
This is truly a case where MFC has become too bloated for its own good. It's definitely crying for one of Matt Pietrek's liposuction procedures, if only there was one for source code. The problem lies within CFrameWnd::OnActivate, which handles WM_ACTIVATE. There are zillions of activation messages in Windows; well, at least four. WM_NCACTIVATE is the one Windows sends to tell you to activate your non-client area (window frame and title bar); Windows sends WM_ACTIVATE when it's time to activate the whole window. CFrameWnd's handler for WM_ACTIVATE is truly staggering (see Figure 1). I don't really expect you to read all that code, I just want to give you a sense of how gnarly it is. I didn't even show you CFrameWnd::OnEnable, another forty lines of closely related code! All this code is there mainly to handle floating toolbars. Compare this with Figure 2, which shows an earlier version of CFrameWnd::OnActivate from Visual C++
® 1.52.
The bug—and there's really no way to call it anything else—lies in a line inside CFrameWnd::NotifyFloatingWindows where MFC sends WM_NCACTIVATE(FALSE) to the parent window, even when the parent window is the same window as the one being activated! Apparently the Friendly Redmondtonians discovered the same bug, because they corrected it with a flag. One of the first things CFrameWnd::OnActivate does is figure out the top-level window and then set


 pTopLevel->m_nFlags |= WF_STAYACTIVE;
if the top-level window is the same as the one being activated. This is MFC's way of telling the window, "Do what I say, not what I do—even if I deactivate you, you should remain active." This injunction is directed at CFrameWnd::OnNcActivate.

 BOOL CFrameWnd::OnNcActivate(BOOL bActive)
 {
    // stay active if WF_STAYACTIVE is on
    if (m_nFlags & WF_STAYACTIVE)
       bActive = TRUE;
 
    // but not if window is disabled
    if (!IsWindowEnabled())
       bActive = FALSE;
 
    return DefWindowProc(WM_NCACTIVATE,
       bActive, 0L);
 }
In effect, CFrameWnd::OnNcActivate converts the WM_NCACTIVATE(FALSE) back to a WM_NCACTIVATE(TRUE), but not if the window is disabled. Got it?
Overall, I'm a fan of MFC, but this is not one of its most stellar moments. Inventing a flag to overcome a bug is an example of how, in my humble opinion, MFC has become too complicated and lost its original goal of being a lean-and-mean C++ wrapper around the Windows API. Docking/floating toolbars are nice, but they don't belong in the core classes, where every app is forced to inherit the code, including bugs, whether the app uses it or not. In this example, MFC isn't helping—it's in the way.
I can think of at least two ways to fix the problem. If you're not using floating toolbars, you can override CFrameWnd::OnActivate to do something more along the lines of Figure 2. Even simpler is to duplicate CFrameWnd's set-a-flag kludge in your own CMainFrame::OnNcActivate handler. I chose the latter in my program ShadeCap.
Figure 3 ShadeCap sample program
Figure 3 ShadeCap sample program

Since we're on the subject of painting title bars, I thought now would be an excellent time to revisit the topic, as the details are much more tricky in Windows 95 and MFC (as Adam found out!) than in Windows 3.1. ShadeCap is a simple text editor that draws a shaded caption like the one in the Office 95 products. ShadeCap displays the ACME company name in a special font that's always white, but it uses the normal caption font for the rest of the window title (see Figure 3). While Microsoft Excel shades the caption even when the window is inactive, I chose not to do this (see Figure 4). Somehow it just doesn't seem right to be doing weird painting tricks when your window is out to lunch.
Figure 4 Inactive ShadeCap title bar
Figure 4 Inactive ShadeCap title bar

The standard paint-your-own-caption technique programmers have used ever since Windows 3.0 days is to handle three messages: WM_NCPAINT, WM_NCACTIVATE, and WM_SETTEXT. The fact that you must override three messages instead of just WM_NCPAINT shows that the original designers of this part of Windows did not fully comprehend the concept of a paint function: namely, that it should be the only place painting occurs. Well, never mind that, it's too late to change Windows.
In handling each of these messages, the basic strategy is to first let Windows do its default thing, then do your own special painting:

 void CMyMainFrame::OnNcPaint()
 {
    CFrameWnd::OnNcPaint(); // do default
    PaintCaption();         // then paint mine 
 }
Likewise with WM_SETTEXT and WM_NCACTIVATE; first let the base class handle it, then paint your custom caption.

 BOOL CMainFrame::OnNcActivate(BOOL bActive)
 {
    BOOL bRet = 
       CFrameWnd::OnNcActivate(bActive);
    PaintCaption(bActive);
    return bRet;
 }
This is where Adam ran into the MFC bug. CFrameWnd:: OnActivate sends WM_NCACTIVATE(FALSE), so as soon as Adam's frame window is activated, MFC deactivates it. For ShadeCap, I worked around MFC by essentially duplicating the code in CFrameWnd::OnNcActivate. (If you want the details, you can download ShadeCap from the link at the top of this article.)
Once you've implemented your three handlers, mimicked the kludge in CFrameWnd::OnNcActivate, and written PaintCaption to do your custom painting, everything is all fine and dandy. Except for one little thing: if the user sizes your window, he or she may be blinded by all the activity in the title bar. I mean, we're talking some serious screen flicker here. The problem never arose in Windows 3.1 because, in the old days, Windows displayed only the dotted outline of the window during sizing, not the whole dynamically sizing window. (It still does this if you don't use the Plus! pack's funky window resize features.)
Screen flicker is one of those things that lazy programmers ignore but proud programmers do their best to eliminate. It's not a serious bug—like crashing or losing data or something like that—and the display does eventually settle down. But it just isn't classy to have pixels flashing out of control all over the place when the user does something. And it certainly falls short of our high MSJ programming standards.
To get rid of flicker, you have to understand where it comes from. When you call the default handler for WM_NCPAINT, WM_NCACTIVATE, or WM_SETTEXT by calling either the base class CFrameWnd handler or Default, the message ultimately ends up at the Windows default window procedure, DefWindowProc, which handles the message and then paints the caption. The control returns to your handler and your PaintCaption function paints it again. So first Windows paints the caption, then you immediately paint over it. For a brief moment, the user can see whatever Windows painted, and that's your flicker.
The seemingly obvious solution would be to not pass the message to DefWindowProc. Unfortunately, this fails because DefWindowProc does other important things besides painting, like setting the window text or painting other nonclient areas such as the window frame.
To stomp out flicker, you have to roll up your sleeves and roll out the big guns. ShadeCap takes no prisoners in avoiding stroboscopic window titles. There are three functions to worry about: OnNcPaint, OnNcActivate, and OnSetText. For OnNcPaint, ShadeCap uses the little-known HRGN parameter to WM_NCPAINT. The wParam of WM_NCPAINT is a handle to an update region (HRGN) identifying the area that needs painting, similar to rcPaint in PAINTSTRUCT. The folks in MFC country must not want you to use the update region, because it's not even packaged as an argument for OnNcPaint, though it's clearly documented in the Win32 documentation for WM_NCPAINT. To get the update region, you have to unpack it manually.

 MSG& msg = AfxGetThreadState()->m_lastSentMsg;
 HRGN hRgn = (HRGN)msg.wParam;
In Windows, a region can be a complex shape composed of rectangles, paths, or ellipses, but for WM_NCPAINT it's always a rectangle. Normally the region is NULL (wParam=0), which means paint the whole caption. Other times, the region has the mysterious undocumented value 0x0001, which is obviously not a valid region handle. Observation suggests that Windows uses wParam=1 while the user is sizing the frame, but I can't prove it.
In ShadeCap, I use the update region in two ways. First, if my caption doesn't intersect the update region, I don't paint it. That's just being efficient. The second way I use the update region is to eliminate flicker. Before I pass the WM_NCPAINT along to CFrameWnd, I remove the entire caption area from the update region. In effect, I tell Windows, "don't paint the caption." The details are mostly a straightforward matter of manipulating rectangles and regions.
Setting the update region in OnNcPaint gets rid of one source of flicker (Windows painting the caption before you do), but there's another source that comes from ShadeCap itself. My custom paint function first draws the shaded title bar, then draws the text over it. This generates flicker because the user first sees a shaded rectangle, then the text getting slapped over it. The way to avoid this (and all such flicker problems) is to first paint onto a bitmap selected into a memory device context, then blast the entire bitmap onto the screen in one fell swoop. This approach has other benefits as well: you can keep the bitmap around so future paint operations are fast. For example, if the user drags something past your window.
ShadeCap maintains two bitmaps as an array m_bmCaption[2]—one bitmap for the active and one for the inactive captions. My PaintCaption function calls another function, GetCaptionBitmap, to get the bitmap based on the current activation state, then blasts this bitmap onto the screen with BitBlt. Most of the time, GetCaptionBitmap just returns m_bmCaption[0] (active) or m_bmCaption[1] (inactive). Only if the size of the caption has changed since the last time the bitmaps were drawn does GetCaptionBitmap draw new bitmaps.
So much for OnNcPaint. The next place I tackled flicker is in OnNcActivate. The first thing I did was mimic the WF_STAYACTIVE kludge from CFrameWnd::OnNcActivate, to correct the MFC bug Adam found. Beyond that, I started out with the officially recommended approach: pass to DefWindowProc (which in new-age programming means call the base class handler, CFrameWnd::OnNcPaint), then do my own PaintCaption thing. As expected, this results in flicker whenever the user activates or deactivates the window, as first Windows, then ShadeCap, paints the caption.
There's no update region parameter for WM_NCACTIVATE, so I began looking for some way to bypass DefWindowProc entirely for WM_NCACTIVATE. There are only two graphic window elements that can have different colors for active/inactive states or could need painting for activation/deactivation: the caption and the border. I already have a function to draw the active caption (PaintCaption), so what about the border? How can I get Windows to paint the border? Easy. I just send myself a WM_NCPAINT message:

 BOOL CMainFrame::OnNcActivate(BOOL bActive)
 {
    .
    . // Mimic MFC WF_STAYACTIVE kludge
    .
    m_bActive = bActive;       // update state
    SendMessage(WM_NCPAINT);   // paint myself
    return TRUE;               // done OK
 }
This is what DefWindowProc should have done in the first place, so all NC painting would go through one message!
Figure 5 Title bar painting madness!
Figure 5 Title bar painting madness!

Unfortunately, when I first implemented this code and tried it out, ShadeCap exhibited a new painting anomaly: whenever I clicked ShadeCap to activate it, I would briefly see a caption bar that looked like Figure 5—just the buttons active, with the rest of the title bar inactive. Going from active to inactive resulted in a similar phenomenon. It's not exactly flicker, or even a bug—who says the title bar has to be painted from right to left? It is annoying, however, especially to a perfectionist like me. Moreover, I could plainly see that Microsoft Excel doesn't exhibit any such problem, and darned if I was going to be outdone by a program from Microsoft!
This new paint problem arose from the fact that I was letting Windows paint the buttons while I painted only the part of the caption that contains text. The solution was to take over the title bar completely. In my final implementation, ShadeCap draws the whole caption area, icon, text, buttons and all (but not the window frame). Drawing the icons and buttons is not as painful as you might imagine. A new-for-Win32 API function, DrawFrameControl, lets you draw individual frame controls. The only trouble I had was getting the placement of the buttons just right. The size of a caption bar button is defined by the GetSystemMetrics codes SM_CXSIZE and SM_CYSIZE ("size" box), but Windows 95 draws the minimize, maximize/restore, and close buttons within slightly smaller rectangles, so some of the caption appears as a background border around the buttons. (The whole rectangle is part of the button; if you click on the background near the close box, for instance, the button goes down.)
I couldn't find any GetSystemMetrics or SystemParametersInfo codes or any other symbols anywhere that define the sizes of these borders, so I had to glean them from inspection. It would appear that caption bar buttons all have a two-pixel border on the top and bottom, but the left/right border depends on which button it is. The close and maximize/restore buttons have a two-pixel border on the right, zero on the left. The minimize button is the opposite: two pixels on the left and zero on the right (see Figure 6). For the icon, there's a one-pixel border top and bottom, two pixels on the left, and none on the right. I tested my button-border hypothesis (and that's all it is since I don't have the source code) in several different configurations of caption bars with different sizes, fonts, and button sizes, and it held true in all cases.
Figure 6 Caption button dimensions
Figure 6 Caption button dimensions

Once I figured out all the button borders, my OnNcActivate function worked like a charm, without even calling the default handler for this message. No flicker or weird painting behavior when activating or deactivating my main window.
So then it was on to the next function, OnSetText. Once again, I started with the official recommendation: call Default to let Windows do its thing, then paint my custom caption. Once again, I got the dreaded flicker. In this case it's really bad because DefWindowProc paints the new text, then ShadeCap paints over it, so you can see text jumping around. The problem only manifests when the title changes (whenever the program calls SetWindowText, which sends WM_SETTEXT), typically when the user opens a new file. Once again, I took Microsoft Excel as my model. Its caption doesn't flicker when you open a new file, so why should mine?
The Windows documentation recommends setting the window text to NULL so DefWindowProc won't draw any text when it gets WM_SETTEXT. To be honest, this is lame advice. In the first place, it doesn't eliminate flicker entirely because, even if there's no text, Windows still draws a blank caption bar. Now the user sees a solid rectangle instantly paved over with a shaded one with text. Second, and more important, if you set the text to NULL, then your app has no title in the Windows 95 task bar and when you use Alt-Tab to switch apps! This is true even if you implement your own WM_GETTEXT to get the title. Apparently Windows looks directly at the text stored in the WND structure; it doesn't go through WM_GETTEXT! Don't you just want to scream?
At this point I was stumped. I had to let DefWindowProc get the WM_SETTEXT message in order to set the text internally, but I didn't want DefWindowProc to do any painting. Somehow Microsoft Excel manages this feat, but how? Does Microsoft Excel use undocumented features?
I did a little snooping with Spy, and I noticed that when Microsoft Excel gets WM_SETTEXT, it immediately gets WM_STYLECHANGING and WM_STYLECHANGED before WM_SETTEXT returns. Moreover, it gets them twice. Windows sends the WM_STYLExxx messages whenever a program alters a window's style, so the WM_SETTEXT handler in Microsoft Excel must alter the window style—twice! Well, this was the big tip-off. I imagined Microsoft Excel must be turning WS_CAPTION off before sending the message to DefWindowProc, and then turning it back on again after. Running Microsoft Excel under BoundsChecker revealed that this is in fact the case, only it's WS_VISIBLE, not WS_CAPTION. So in CMainFrame::OnSetText, I do the same: turn off WS_VISIBLE before calling DefWindowProc, then turn it back on again after. This has no visible effect (the window doesn't actually disappear), but it prevents DefWindowProc from painting the text. Problem solved with yet another totally arcane kludge. Once I learned about this, I decided to pull the same trick in OnNcActivate, just in case the DefWindowProc ever does anything besides paint.
With that final nail in the flicker demon's coffin, ShadeCap is now fully flicker-free. There're just a few more details I'll note before calling it a day.
Whenever you write a program that uses system colors, fonts, and other user-customizable system stuff, don't forget to handle WM_SYSCOLORCHANGE and WM_SETTINGCHANGE! Windows broadcasts these messages to all top-level windows when the user changes colors or other system settings. Which message depends on what changed. WM_SETTINGCHANGE used to be called WM_WININICHANGE, but since .INI files are now replaced by the registry, this message got renamed to WM_SETTINGCHANGE. (Both symbols are still defined, and even have the same value, but if you want to be among the digirati, use WM_SETTINGCHANGE.) Handling the messages is easy: ShadeCap destroys its fonts and invalidates its caption bitmaps so GetCaptionBitmap will create new fonts and generate new bitmaps the next time it's called.
Figure 7  Nonlinear Shading Algorithm Turns Dark Faster
Figure 7 Nonlinear Shading Algorithm Turns Dark Faster

Another minor change I had to make for ShadeCap relates to the way MFC generates the window title. By default, MFC generates a title of the form "FileName - AppName". For ShadeCap I want the title to be "ACME EDIT filename", so I overrode CFrameWnd::OnUpdateFrameTitle, which is the MFC function that generates the title. Remember, regardless of how you paint the caption, whatever you set as your frame's title (by calling SetWindowText) will be what Windows displays for your app in the task bar, when the user tabs through apps with Alt-Tab, and in general anywhere Windows displays app names.
Finally, the fun part: actually painting the caption. Mostly it's straightforward. To get the caption rectangle any time I need it, I wrote a little class, CCaptionRect, with a constructor that initializes the rectangle to the caption rectangle in window coordinates. All I have to do to get the caption rectangle is add this line of code:

 CCaptionRect rcCap; // get caption rectangle
Figure 8 Microsoft Excel caption painting
Figure 8 Microsoft Excel caption painting

In selecting the caption text color, I used some rudimentary color theory: ShadeCap checks the luminosity of the active caption text color and, if it's too dark, uses white instead. This avoids the problem of black-on-black text (a little hard on the eyes). As for doing the shading, it wasn't enough to match Microsoft Excel—I had to outdo it. I used an arguably superior algorithm: instead of the Microsoft Excel linear fade-to-black, I used a quadratic algorithm (see Figure 7) that results in a slower transition to black and hence more of the user's caption color showing (compare Figures 3 and 8). Details are in the source code. Happy painting!

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

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

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.

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