Share via


C++ Q&A

Full Screen Display and Implementing Drag to Move Dialogs

Paul DiLascia

Code download available at: CQA0212.exe (75 KB)
Browse the Code Online

Q I'm writing a C++/MFC program to display fractal images. I'm using the standard AppWizard configuration, and I'm trying to toggle the view between a regular framed view and an unframed full-screen view (similar to selecting View | Full Screen in Visual Studio®). Are there any framework functions that directly support that task? If not, what approach would you suggest?

Q I'm writing a C++/MFC program to display fractal images. I'm using the standard AppWizard configuration, and I'm trying to toggle the view between a regular framed view and an unframed full-screen view (similar to selecting View | Full Screen in Visual Studio®). Are there any framework functions that directly support that task? If not, what approach would you suggest?

Amir Hindi

A There's nothing in MFC that does full-screen display explicitly; however, it's not hard to implement. The basic idea is to adjust the size and position of your main window so it's bigger than the screen by the exact amount to make the active view exactly fill the display. This will always require an origin (top-left corner) with negative x,y coordinates. I wrote a little program, FSApp, to show the details. Figure 1 shows the important code sections. FSApp is a simple SDI app with a View | Full Screen command that toggles between normal and full-screen mode. FSApp uses a class CFullScreenHandler (see Figure 2) to do the work. CFullScreenHandler encapsulates the full-screen feature so you can add it to your own app with minimal integration. There are two important methods: Maximize enters full-screen mode (not to be confused with the normal maximize button in Windows®) and Restore restores the frame to its previous size. CFullScreenHandler also provides a method InFullScreenMode to test if the app is in full-screen mode and a global object instance through which to call these methods. Here's how FSApp's main window handles the View | Full Screen command:

// handler for View | Full Screen
void CMainFrame::OnViewFullScreen()
{
  if (FullScreenHandler.InFullScreenMode())
    FullScreenHandler.Restore(this);
    else 
      FullScreenHandler.Maximize(this);
  }

A There's nothing in MFC that does full-screen display explicitly; however, it's not hard to implement. The basic idea is to adjust the size and position of your main window so it's bigger than the screen by the exact amount to make the active view exactly fill the display. This will always require an origin (top-left corner) with negative x,y coordinates. I wrote a little program, FSApp, to show the details. Figure 1 shows the important code sections. FSApp is a simple SDI app with a View | Full Screen command that toggles between normal and full-screen mode. FSApp uses a class CFullScreenHandler (see Figure 2) to do the work. CFullScreenHandler encapsulates the full-screen feature so you can add it to your own app with minimal integration. There are two important methods: Maximize enters full-screen mode (not to be confused with the normal maximize button in Windows®) and Restore restores the frame to its previous size. CFullScreenHandler also provides a method InFullScreenMode to test if the app is in full-screen mode and a global object instance through which to call these methods. Here's how FSApp's main window handles the View | Full Screen command:

// handler for View | Full Screen
void CMainFrame::OnViewFullScreen()
{
  if (FullScreenHandler.InFullScreenMode())
    FullScreenHandler.Restore(this);
    else 
      FullScreenHandler.Maximize(this);
  }

Figure 2 CFullScreenHandler

FullScreenHandler.h

////////////////////////////////////////////////////////////////
// MSDN Magazine — December 2002
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
// Handle full-screen mode: adjust frame size to make
// view's client area fill the available screen.
//
class CFullScreenHandler {
public:
    CFullScreenHandler();
    ~CFullScreenHandler();

    void Maximize(CFrameWnd* pFrame, CWnd* pView);
    void Restore(CFrameWnd* pFrame);
    BOOL InFullScreenMode() { return !m_rcRestore.IsRectEmpty(); }
    CSize GetMaxSize();

protected:
    CRect m_rcRestore;
};

// Global instance
extern CFullScreenHandler FullScreenHandler;

FullScreenHandler.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — December 2002
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#include "StdAfx.h"
#include "FullScreenHandler.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// redefine this to nop if you don't want tracing
#define FSTRACE TRACE

// Global object handles full-screen mode
CFullScreenHandler FullScreenHandler;

CFullScreenHandler::CFullScreenHandler()
{
    m_rcRestore.SetRectEmpty();
}

CFullScreenHandler::~CFullScreenHandler()
{
}

//////////////////
// Resize frame so view's client area fills the entire screen. Use
// GetSystemMetrics to get the screen size — the rest is pixel
// arithmetic.
//
void CFullScreenHandler::Maximize(CFrameWnd* pFrame, CWnd* pView)
{
    // get view rectangle
    if (pView) {
        CRect rcv;
        pView->GetWindowRect(&rcv);

        // get frame rectangle
        pFrame->GetWindowRect(m_rcRestore); // save for restore
        const CRect& rcf = m_rcRestore;     // frame rect

        FSTRACE("Frame=(%d,%d) x (%d,%d)\n",
            rcf.left, rcf.top, rcf.Width(), rcf.Height());
        FSTRACE("View =(%d,%d) x (%d,%d)\n",
            rcv.left, rcv.top, rcv.Width(), rcv.Height());

        // now compute new rect
        CRect rc(0,0, GetSystemMetrics(SM_CXSCREEN),
            GetSystemMetrics(SM_CYSCREEN));

        FSTRACE("Scrn =(%d,%d) x (%d,%d)\n",
            rc.left, rc.top, rc.Width(), rc.Height());
        rc.left  += rcf.left  - rcv.left;
        rc.top   += rcf.top   - rcv.top;
        rc.right += rcf.right - rcv.right;
        rc.bottom+= rcf.bottom- rcv.bottom;

        FSTRACE("New  =(%d,%d) x (%d,%d)\n",
            rc.left, rc.top, rc.Width(), rc.Height());

        // move frame!
        pFrame->SetWindowPos(NULL, rc.left, rc.top,
            rc.Width(), rc.Height(), SWP_NOZORDER);
    }
}

void CFullScreenHandler::Restore(CFrameWnd* pFrame)
{
    const CRect& rc = m_rcRestore;
    pFrame->SetWindowPos(NULL, rc.left, rc.top,
        rc.Width(), rc.Height(), SWP_NOZORDER);
    m_rcRestore.SetRectEmpty();
}

CSize CFullScreenHandler::GetMaxSize()
{
    CRect rc(0,0,
        GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN));
    rc.InflateRect(10,50);
    return rc.Size();
}

Figure 1 FSApp

MainFrm.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — December 2002
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#include "StdAfx.h"
#include "FSApp.h"
#include "MainFrm.h"
#include "FullScreenHandler.h"
#include "TraceWin.h"

•••

//////////////////
// Important to handle this in order to set size
// of window larger than whole screen.
//
void CMainFrame::OnGetMinMaxInfo(MINMAXINFO* lpmmi)
{
   CSize sz = FullScreenHandler.GetMaxSize();
   lpmmi->ptMaxSize = CPoint(sz);
   lpmmi->ptMaxTrackSize = CPoint(sz);
}

//////////////////
// View full screen mode. Calls CFullScreenHandler to do the work.
//
void CMainFrame::OnViewFullScreen()
{
   if (FullScreenHandler.InFullScreenMode())
      FullScreenHandler.Restore(this);
   else 
      FullScreenHandler.Maximize(this, GetActiveView());
}

//////////////////
// Put checkmark next to command if in full-screen mode.
//
void CMainFrame::OnUpdateViewFullScreen(CCmdUI* pCmdUI)
{
   pCmdUI->SetCheck(FullScreenHandler.InFullScreenMode());
}

//////////////////
// Non-client hit-test handler to move window by its client area. The view
// returns HTTRANSPARENT and the frame returns HTCAPTION if the mouse
// is in the client area. Together, these handlers let user move the
// window by dragging the client area.
//
UINT CMainFrame::OnNcHitTest(CPoint pt)
{
   CRect rc;
   GetClientRect(&rc);
   ClientToScreen(&rc);
   return rc.PtInRect(pt) ? HTCAPTION : CFrameWnd::OnNcHitTest(pt);
}

Figure 1 FSApp

View.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — December 2002
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#include "StdAfx.h"
#include "FSApp.h"
#include "Doc.h"
#include "View.h"
#include "FullScreenHandler.h"

•••

void CMyView::OnDraw(CDC* pDC)
{
   CRect rc;
   GetClientRect(&rc);
   pDC->DrawText(FullScreenHandler.InFullScreenMode() ?
      _T("Type Ctrl-U to restore.") : _T(""), &rc, 0);
}

//////////////////
// Non-client hit-test handler to move window by its client area. The view
// returns HTTRANSPARENT and the frame returns HTCAPTION if the mouse
// is in the client area. Together, these handlers let user move the
// window by dragging the client area.
//
UINT CMyView::OnNcHitTest(CPoint pt)
{
   return HTTRANSPARENT;
}

If you put your app in full-screen mode, the menu and toolbars are no longer visible, let alone usable, so it's imperative to tell the user how to restore the view. FSApp's main view displays a message:

void CMyView::OnDraw(CDC* pDC)
{
  CRect rc;
  GetClientRect(&rc);
  pDC->DrawText
    (FullScreenHandler.InFullScreenMode() ?
    _T("Type Ctrl-U to restore.") : _T(""), 
       &rc, 0);
}

Figure 3 shows how it looks on the screen. You probably wouldn't do this in your own app (you wouldn't want to draw text over your fractals), but it's important to display a message or button or some other obvious way out of full-screen mode. Otherwise, your user might press Alt-F4 or Ctrl-Alt-Del, or throw her mouse at the monitor in disgust. Don't forget a user might fall into full-screen mode accidentally by typing the wrong key or command, as yours truly can personally testify. There's nothing more lame than an app that takes over the screen without an obvious way to go back. On the flip side, you gain big GUI points for writing an app that's self-explanatory.

Figure 3 Text Over a Fractal

Figure 3** Text Over a Fractal **

So how do CFullScreenHandler::Maximize and Restore work? The code is rather dull actually, just a lot of pixel math. Maximize will first save the current frame position in a data member m_rcRestore, then calculate the desired larger-than-screen size. Figure 4 shows the calculation. In case you're wondering where to get the screen size, it comes from GetSystemMetrics. The Restore function is even simpler. It moves the frame to the position saved in m_rcRestore, then does m_rcRestore.SetRectEmpty to signify the frame is no longer in full-screen mode. What could be simpler?

Figure 4 Calculate Desired Screen Size

Figure 4** Calculate Desired Screen Size **

Of course, there is one little catch. (In Windows, there's always a catch.) If you implement everything I've shown thus far, you'll discover a minor fly in your ointment: when you press the full-screen button, the frame does in fact get bigger, but the bottom edge comes out several pixels shy of a full display. Sigh.

So what went wrong? Debugging reveals the culprit: WM_GETMINMAXINFO. Any time Windows is about to size a window—whether because the user drags the border or your code calls a sizing function—it first sends WM_GETMINMAXINFO. This message says, "Hey you! If you have some minimum or maximum size you want me to enforce, please supply the details in the enclosed MINMAXINFO struct. Otherwise I'll do it myself." Most apps ignore WM_GETMINMAXINFO (that is, they don't explicitly handle it), so the default handler DefWindowProc does its thing. By default, Windows doesn't let you make a window bigger than the screen. Hence the stunted window. To make the full-screen feature work as advertised, you need one minor correction:

// handle WM_GETMINMAXINFO
void CMainFrame::OnGetMinMaxInfo
   (MINMAXINFO* lpmmi)
{
  CSize sz = 
     FullScreenHandler.GetMaxSize();
  lpmmi->ptMaxSize = CPoint(sz);
  lpmmi->ptMaxTrackSize = CPoint(sz);
}

CFullScreenHandler.GetMaxSize returns a maximum size slightly larger than the entire screen.

CSize CFullScreenHandler::GetMaxSize()
{
  CRect rc(0,0,
    GetSystemMetrics(SM_CXSCREEN),
    GetSystemMetrics(SM_CYSCREEN));
  rc.InflateRect(10,50);
  return rc.Size();
}

GetMaxSize returns a value that's 2×10=20 pixels larger horizontally and 2×50=100 pixels larger vertically than the screen itself. The exact numbers are arbitrary; any values will do, as long as they're large enough to allow the final desired size. If you're the compulsive sort, you can calculate the exact size.

QI want to make a dialog-based application using a style with no frame or caption bar for the main dialog. How can I move this no-frame dialog with the mouse? How can I put it in move mode with the dotted rectangle around it?

QI want to make a dialog-based application using a style with no frame or caption bar for the main dialog. How can I move this no-frame dialog with the mouse? How can I put it in move mode with the dotted rectangle around it?

Radut Codrut

AI can think of two ways to do it. Would you rather be a geek or a guru? The geek way is to handle mouse events with the usual move/drag pattern. When your window gets WM_LBUTTONDOWN (OnLButtonDown), you put the app in move mode by setting a flag and calling SetCapture to grab the mouse. Now all mouse messages go to your window. While in move mode, whenever you get WM_MOUSEMOVE, you move the frame window accordingly. Finally, when the user lets go of the mouse button, your WM_LBUTTONUP handler clears the flag and calls ReleaseCapture to return mouse input to Windows. Ho hum, handling mouse messages is sooooo pedestrian. You have to calculate where to move the window. You have to figure out how to draw the funky dotted rectangle—but only if the user unchecked "Show window contents while dragging" in the Display Properties (see Figure 5). And how do you know what the display setting for that is, anyway? (Answer: call SystemParametersInfo(SPI_GETDRAGFULLWINDOWS).) It's all so tedious. Too bad there isn't some way to let Windows do all this. After all, Windows already knows how to move a window. There ought be some way to say, "Pardon me, but would you please just make the background act like the caption?"

AI can think of two ways to do it. Would you rather be a geek or a guru? The geek way is to handle mouse events with the usual move/drag pattern. When your window gets WM_LBUTTONDOWN (OnLButtonDown), you put the app in move mode by setting a flag and calling SetCapture to grab the mouse. Now all mouse messages go to your window. While in move mode, whenever you get WM_MOUSEMOVE, you move the frame window accordingly. Finally, when the user lets go of the mouse button, your WM_LBUTTONUP handler clears the flag and calls ReleaseCapture to return mouse input to Windows. Ho hum, handling mouse messages is sooooo pedestrian. You have to calculate where to move the window. You have to figure out how to draw the funky dotted rectangle—but only if the user unchecked "Show window contents while dragging" in the Display Properties (see Figure 5). And how do you know what the display setting for that is, anyway? (Answer: call SystemParametersInfo(SPI_GETDRAGFULLWINDOWS).) It's all so tedious. Too bad there isn't some way to let Windows do all this. After all, Windows already knows how to move a window. There ought be some way to say, "Pardon me, but would you please just make the background act like the caption?"

Figure 5 Display Properties Dialog

Figure 5** Display Properties Dialog **

Astute readers have no doubt guessed already where this is leading. In fact, getting Windows to move your dialog by dragging its background is practically trivial. Once you know the secret voodoo, that is. Consider what happens when Jane User drags a window by its caption. Windows first finds the window under the mouse, then sends that window a WM_NCHITTEST message to find out which "non-client" area (border, size box, menu, caption, and so on) contains the cursor. The default window procedure figures out the answer and returns a special code. If the mouse lies within the caption, the magic value is HTCAPTION. Figure 6 shows other possible return values. If WM_NCHITTEST returns HTCAPTION, Windows does its move-window thing: enters drag mode, draws a dotted rectangle (if not the contents), and so on. So all you have to do to give your application the move-by-dragging feature—the guru method—is pretend the client area is actually the caption.

UINT CMyDialog::OnNcHitTest(CPoint pt)
{
  CRect rc;
  GetClientRect(&rc);
  ClientToScreen(&rc);
  return rc.PtInRect(pt) ? HTCAPTION : 
    CDialog::OnNcHitTest(pt);
}

Figure 6 Possible Return Values from WM_NCHITTEST

Value Location of Hot Spot
HTBORDER In the border of a window that does not have a sizing border
HTBOTTOM In the lower-horizontal border of a resizable window (the user can click the mouse to resize the window vertically)
HTBOTTOMLEFT In the lower-left corner of a border of a resizable window (the user can click the mouse to resize the window diagonally)
HTBOTTOMRIGHT In the lower-right corner of a border of a resizable window (the user can click the mouse to resize the window diagonally)
HTCAPTION In a title bar
HTCLIENT In a client area
HTCLOSE In a Close button
HTERROR On the screen background or on a dividing line between windows (same as HTNOWHERE, except that the DefWindowProc function produces a system beep to indicate an error)
HTGROWBOX In a size box (same as HTSIZE)
HTHELP In a Help button
HTHSCROLL In a horizontal scroll bar
HTLEFT In the left border of a resizable window (the user can click the mouse to resize the window horizontally)
HTMENU In a menu
HTMAXBUTTON In a Maximize button
HTMINBUTTON In a Minimize button
HTNOWHERE On the screen background or on a dividing line between windows
HTREDUCE In a Minimize button
HTRIGHT In the right border of a resizable window (the user can click the mouse to resize the window horizontally)
HTSIZE In a size box (same as HTGROWBOX)
HTSYSMENU In a window menu or in a Close button in a child window
HTTOP In the upper-horizontal border of a window
HTTOPLEFT In the upper-left corner of a window border
HTTOPRIGHT In the upper-right corner of a window border
HTTRANSPARENT In a window currently covered by another window in the same thread (the message will be sent to underlying windows in the same thread until one of them returns a code that is not HTTRANSPARENT)
HTVSCROLL In the vertical scroll bar
HTZOOM In a Maximize button

That is, if the mouse lies within the client area, return HTCAPTION. For a simple dialog, this is all the code you need to implement move-by-dragging-the-background. Really! And since Windows uses the z-order to find which window lies under the mouse, all the controls continue to work as normal. If the user clicks a control, Windows sends mouse events to the control, not the dialog. Unless of course the control is a static bitmap or text, which is transparent. In short, everything works just the way you'd hope: users can move the dialog by dragging its background or static elements, but clicking an edit control, button, combobox, or any other non-static control does whatever it would normally do. And you thought programming was always difficult! Figure 7 shows a real-live dialog application named MoveDlg that implements the move-by-dragging feature.

Figure 7 MoveDlg.cpp

////////////////////////////////////////////////////////////////
// MSDN Magazine — December 2002
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#include "stdafx.h"
#include "resource.h"
#include "StatLink.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////
// Typcial dialog.
// Override OnNcHitTest to allow dragging by background.
//
class CMyDialog : public CDialog {
public:
   CMyDialog(CWnd* pParent = NULL); // standard constructor
protected:
   HICON m_hIcon;
   CStaticLink m_wndLink1;
   CStaticLink m_wndLink2;
   CStaticLink m_wndLink3;
   virtual BOOL OnInitDialog();
   afx_msg UINT OnNcHitTest(CPoint pt);
   DECLARE_MESSAGE_MAP()
};

//////////////////
// Vanilla MFC app class
//
class CMyApp : public CWinApp {
public:
   CMyApp();
   virtual BOOL InitInstance();
   DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
   ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

CMyApp::CMyApp()
{
}

CMyApp theApp;

//////////////////
// InitInstance runs the dialog
//
BOOL CMyApp::InitInstance()
{
   CMyDialog dlg;
   m_pMainWnd = &dlg;
   dlg.DoModal();
   return FALSE;
}

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
   ON_WM_NCHITTEST()
END_MESSAGE_MAP()

CMyDialog::CMyDialog(CWnd* pParent) : CDialog(IDD_MYDIALOG, pParent)
{
}

BOOL CMyDialog::OnInitDialog()
{
   CDialog::OnInitDialog();

   // Set URLs for web links
   m_wndLink1.SubclassDlgItem(IDC_PDURL, this,
      _T("https://www.dilascia.com"));
   m_wndLink2.SubclassDlgItem(IDC_MSDNURL, this,
      _T("https://msdn.microsoft.com/msdnmag"));
   m_wndLink3.SubclassDlgItem(IDC_MSDNURL2, this,
      _T("https://msdn.microsoft.com/msdnmag"));

   // Set the icon for this dialog.  The framework does this automatically
   // when the application's main window is not a dialog
   //
   m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
   SetIcon(m_hIcon, TRUE);       // Set big icon
   SetIcon(m_hIcon, FALSE);      // Set small icon
   
   return TRUE;  // return TRUE  unless you set the focus to a control
}

//////////////////
// Non-client hit-test handler to move window by its client area.
// If the user clicks anywhere on the client area, pretend it's the
// caption. Windows does the rest!
//
UINT CMyDialog::OnNcHitTest(CPoint pt)
{
   CRect rc;
   GetClientRect(&rc);
   ClientToScreen(&rc);
   return rc.PtInRect(pt) ? HTCAPTION : CDialog::OnNcHitTest(pt);
}

MoveDlg implements the drag feature for a "pure" (CDialog-derived) dialog. What if your app uses CFormView or some other non-CDialog view? The same trick applies, with slight modification. Since Windows sends WM_NCHITTEST to the first (topmost) non-transparent window under the mouse, in a frame/view app, the view—not the frame—gets first crack at WM_NCHITTEST. No problem; all you have to do is make your view transparent to mouse clicks by returning HTTRANSPARENT.

// in view, not frame
UINT CMyView::OnNcHitTest(CPoint pt)
{
  return HTTRANSPARENT;
}

Now Windows will ignore your view and continue the search for a window that can receive WM_NCHITTEST. Assuming all is well, this will be the parent frame—which you can supply with the same HTCAPTION trick as in CMyDialog (shown in Figure 7). You can even do some pixel math on the mouse coordinates to make only certain parts of your view transparent.

UINT CMyView::OnNcHitTest(CPoint pt)
{
  return PointLiesWithinDraggableRegion(pt) ?
    HTTRANSPARENT : CView::OnNcHitTest(pt);
}

Now the drag-to-move feature works only in those parts of the window for which PointLiesWithinDraggableRegion returns nonzero. Just for fun, I added the drag-move feature to the (frame/view-based) FSApp program from the previous question. Feel free to download it from the link at the top of this article. Happy programming!

Send your questions and comments for Paul to cppqa@microsoft.com.

Paul DiLasciais a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or https://www.dilascia.com.