C++ Q&A: Prevent Users from Performing Normal G...

We were unable to locate this content in de-de.

Here is the same content in en-us.

This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
MSDN Magazine
Prevent Users from Performing Normal GUI Operations
Paul DiLascia
Download the code for this article: CQA0102.exe (57KB)
Browse the code for this article at Code Center: NoSize

Q
I'm working on a Visual C++® SDI project where I have two splitter windows (form views) within my main frame. Is there a way to keep the user from moving and resizing the frame window and child windows while maintaining a title bar in each?
Ross Giumara

A
I feel so sorry for users sometimes; programmers are always trying to protect users from themselves, preventing this or that normal GUI operation. It annoys me to no end when some program prevents me from sizing the window or using cut or paste or otherwise inflicts its bizarre behavior upon me. My first instinct is to move these apps to the Recycle Bin.
      But who am I to judge? Perhaps there are circumstances when it's wise to disallow moving and sizing. Maybe you're writing a nuclear control program for the President. Whatever. The simplest way to disallow sizing is to create a window without WS_THICKFRAME. How can the user size the window if there's no frame to grab it by? But if you turn WS_THICKFRAME off, you'll still get a window with a caption, which the user can grab to move the window. To disallow moving as well as sizing, you have to turn off WS_CAPTIONâ€"but then your app has no title! Oops. You certainly want the President to know he's using the Nuclear Command Program, not the space wars game. How do you escape this perplexing dilemma? How can you have your caption and disallow moving, too? And what if you don't want a thin frame anyway? What if you want a fat frame with no sizing?
      In fact, you can have your cake and eat it, too. You can have a window with thick frames and a caption, and still disallow moving and sizing. The trick is to handle WM_NCHITTEST. Windows® sends this obscure message to find out specifically where the mouse is when it's in one of the nonclient screen areas. Nonclient areas areâ€"as even the newest newbie could guessâ€"any part of the window that's not part of the client area. This includes the menu, caption, and borders. Normally, you never have to mess with nonclient stuff, or even know it exists. But sometimesâ€"now is goodâ€"you have to roll up your sleeves and get into the gritties.
      When the user clicks the mouse in your app's caption bar, Windows sends WM_NCHITTEST. The default window procedure examines the mouse coordinates and returns one of the specialized hit-test codes listed in Figure 1. For example, if the mouse is in the caption, the default window proc returns HTCAPTION. If the mouse is in the left or right border, the default proc returns HTLEFT or HTRIGHT, respectively. These codes tell Windows to begin its moving or sizing chores.
      You can prevent moving and sizing by overriding ON_NCHITTEST. Instead of returning HTCAPTION or HTLEFT or HTRIGHT, you can return...what? At first you might try HTNOWHERE; but if you do you'll discover a problem: if another window overlaps your window and you click your window's caption bar, nothing happens. And I mean nothingâ€"Windows doesn't even activate your app. Sigh. Well, how about HTTRANSPARENT? Same thing. Both HTTRANSPARENT and HTNOWHERE leave your frame in a state of existential ambiguity.
      The correct value to return is HTBORDER, the same value the default window proc returns when the user clicks on the border of a window without a thick (sizeable) frame. Pretty clever, eh? If you return HTBORDER, Windows activates your window without initiating any move/size operations.
      Naturally, I wrote a little app called NoSize to demonstrate how it works in real life. Figure 2 shows the CMainFrame class, which is where all the action is. The key function is CMainFrame::OnNcHitTest. It maps all the "bad" hit-test codes to HTBORDER.
UINT CMainFrame::OnNcHitTest(CPoint point)
{
    // get vanilla code
    UINT hit = CFrameWnd::OnNcHitTest(point);

    // disallow these codes: map to HTBORDER
    static char DisallowCodes[] = {
        HTLEFT,HTRIGHT,HTTOP,...,
        HTSIZE,HTCAPTION };

    return strchr(DisallowCodes, hit)) ? HTBORDER: hit;
}
      The actual version in Figure 2 contains useful TRACE diagnostics that will help you see what's going on. With the magic lines shown previously, users can't move or size your window.

Figure 3 Menu
Figure 3 Menu

      Or can they? Do you really know how many ways there are to move or size a window? What about the min/max buttons in the caption bar? What about the Move and Size commands in the system menu (see Figure 3)? Don't tell me you forgot. It turns out imposing your sizing will is not so easy! Fortunately, the rest is straightforward. To turn off the min/max/restore buttons in the title bar, all you have to do is adjust the window style in PreCreateWindow.
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    // no min/max buttons in caption!
    cs.style &= ~(WS_MINIMIZEBOX|WS_MAXIMIZEBOX);
    return CFrameWnd::PreCreateWindow(cs);
}
      To remove the offending commands from the system menu, you need a few lines in CMainFrame::OnCreate:
static UINT BadCommands[] = { 
    SC_SIZE, SC_MOVE,
    SC_MINIMIZE, SC_MAXIMIZE, SC_RESTORE, 0
};
CMenu *pSysMenu = GetSystemMenu(FALSE);
for (int i=0; BadCommands[i]; i++) {
    pSysMenu->RemoveMenu(BadCommands[i], MF_BYCOMMAND);
}
Now the system menu looks like Figure 4.

Figure 4 New Menu
Figure 4 New Menu

      You might think that after all this, your app is pretty safe from any freethinking user who might attempt to move or size her window, but there's yet another loophole, yet another way users can size their windows. In Windows, there's always some twist somewhere.
      If you built your app using MFC, you probably have a status bar with size handlesâ€"you know, those grippy little lines that make a triangle in the lower right corner of your window (see Figure 5). All the user has to do is drag those little babies to change the window size.

Figure 5 Status Bar with Size Handles
Figure 5 Status Bar with Size Handles

      So how can you, with your totalitarian mindset, disallow this? Here's how:
// In CMainFrame::OnCreate
ModifyStyle(WS_THICKFRAME,0); 
// no thick frame
m_wndStatusBar.Create(...);
ModifyStyle(0,WS_THICKFRAME); 
// restore thick frame
Here you turn off the thick frame before you create the status bar, then turn it back on again afterwards. When you create your status bar, MFC looks at the window style to determine whether the status bar should get the size handles. I couldn't find any way to turn off the size handles once the status bar has been created, so the only thing left to do is fool MFC from the outset. Why not? Figure 6 shows the resultâ€"a status bar with no size handles.

Figure 6 Status Bar without Size Handles
Figure 6 Status Bar without Size Handles

      Whew! OnNcHitTest, min/max buttons, system menu, MFC status bar fakeout.� Now your window absolutely, positively can't be moved or sized. Just don't be surprised if customers call you to complain or delete your app from their computers!

Q
I have an MFC app that uses a splitter window, but I don't want to let the user move the splitter. How can I prevent the user from moving the splitter bar?
Fascist Programming Wizard

A
Heyâ€"What's with all these prevent-the-user questions? Are people getting snippy just because we've had some trouble electing a President? Well, the truth is I just knew after that last question and seeing Ross's app has splitter windows, that someone would ask me how to prevent sizing them, so I made up this question to save the stress on my Inbox.

Figure 7 Adjustable Splitter Bar
Figure 7 Adjustable Splitter Bar

      Fortunately, preventing the user from moving a splitter is easy. There are two things you must do. First, you have to prevent sizing with the mouse; second, you have to replace the sizing cursor (see Figure 7) with the normal arrow (see Figure 8). The whole thing can be done by overriding two functions:
// override: don't allow resize
void CMySplitterWnd::OnLButtonDown(UINT, CPoint)
{
    return; // no passez-vous GO, no collectez-vous $200
}

// override: don't allow setting cursor
void CMySplitterWnd::OnMouseMove(UINT, CPoint)
{
    return; // ditto
}
      This is the kind of code I really like: code that does nothing. Normally, when you click the mouse on the splitter bar, the splitter goes into its drag shtickâ€"as evidenced by the following lines from MFC:
// (in WinSplit.cpp)
void CSplitterWnd::OnLButtonDown(UINT, CPoint pt)
{
    if (m_bTracking)
        return;
    StartTracking(HitTest(pt));
}
      To prevent tracking, all you have to do is override OnLButtonDown to do nothing. Similarly, when the user moves the mouse over the splitter bar, CSplitterWnd::OnMouseMove sets the cursor to the one that looks like a capacitor in a circuit diagram (see Figure 7); to prevent this, just short-circuit the capacitor (forgive me, I couldn't resist) by overriding and returning.

Figure 8 No Sizing Allowed
Figure 8 No Sizing Allowed

      There's a third function that lets users adjust the splitter bar: CSplitterWnd::DoKeyboardSplit. This function is provided to implement the Window | Split command, which lets users adjust the splitter using the keyboard. There shouldn't be any need to override DoKeyboardSplit, however, since you're the one writing the app. If you don't want users to move the splitter, don't put a Window | Split command in your menu!
Paul DiLascia is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992) and a freelance consultant and writer-at-large. He can be reached at askpd@pobox.com or http://www.dilascia.com.

From the February 2001 issue of MSDN Magazine

Page view tracker