65.9K
CodeProject is changing. Read more.
Home

EnsureRectangleOnDisplay

Feb 24, 2006

2 min read

viewsIcon

36869

downloadIcon

554

Ensure a window rectangle is fully on one screen.

Popup half on window

Popup fully on window

Introduction

I had a problem with one of my software projects yesterday. On one of my dialogs, I had a small button which popped up a small Options dialog. This would have been a popup menu, but after looking at some of the articles on Jensen Harris' blog, I wanted a richer menu. So I went with a small popup dialog.

But as the main dialog is often parked near the edge of the screen, the popup dialog would fall off. I could adjust the popup rectangle to be in the main screen, but I wanted it to be multi-monitor aware.

The Message Board

Given a blank brain, I went and asked wiser people than me on the CP Visual C++ discussion board. James R Twine and Peterchen gave me 90% of the answers. Peterchen's answer pointed to Nish's article which I'd seen before, and was niggling in my brain.

I'd have left it there, but Ryan Binns pointed out a nitpicky problem with the above solutions - the popup dialog could be spilt across more than one screen. Being a nit-picker myself (what programmer isn't?), I had to roll my own function which wouldn't have that vulnerability.

The Code

Thanks to Occam, I found out I'd reinvented the wheel. The hard part of my snippet has already been done by Microsoft in <multimon.h>. The code has been dramatically reduced. But as I invented a decent wheel, I've kept it below. Feel free to use either implementation of the function. Hans Dietrich also pointed out the redundant fFlags parameter, which I've removed. Here's the new code, without further ado...

// Only use this definition once - it creates the stubs
#define COMPILE_MULTIMON_STUBS
#include <multimon.h>

// Take coords, and shift them so all corners
// (if possible) appear on the nearest screen.
BOOL EnsureRectangleOnDisplay (RECT *prc)
{
    HMONITOR hMonitor;
    MONITORINFO mi;
    memset (&mi, 0, sizeof (mi));
    mi.cbSize = sizeof(mi);
    
    if (!prc)
        return FALSE;
   
    hMonitor = MonitorFromRect (prc, 
               MONITOR_DEFAULTTONEAREST);
    if (hMonitor)
        GetMonitorInfo (hMonitor, &mi);

    // Now we have a clipping rectangle,
    // bring our input rectangle into sight.
    if (prc->right > mi.rcWork.right)
    {
        prc->left -= prc->right - mi.rcWork.right;
        prc->right = mi.rcWork.right;
    }
    if (prc->bottom > mi.rcWork.bottom)
    {
        prc->top -= prc->bottom - mi.rcWork.bottom;
        prc->bottom = mi.rcWork.bottom;
    }
    if (prc->left < mi.rcWork.left)
    {
        prc->right += mi.rcWork.left - prc->left;
        prc->left = mi.rcWork.left;
    }
    if (prc->top < mi.rcWork.top)
    {
        prc->bottom += mi.rcWork.top - prc->top;
        prc->top = mi.rcWork.top;
    }
   
    return TRUE;
}

The Old Code!

I had problems with linking on my Visual Studio 6 machine, so I dynamically loaded user32.dll and the two functions GetMonitorInfo and MonitorFromRect. If you can statically link them, feel free to cut my code down to size. The LoadLib class was lightly adapted from its original home in Owner Drawn Menu with Icons, Titles and Shading by Bruno Podetti.

EnsureRectangleOnDisplay function

typedef struct tagMONITORINFO
{
    DWORD   cbSize;
    RECT    rcMonitor;
    RECT    rcWork;
    DWORD   dwFlags;
} MONITORINFO, *LPMONITORINFO;
         
#define MONITOR_DEFAULTTONULL       0x00000000
#define MONITOR_DEFAULTTOPRIMARY    0x00000001
#define MONITOR_DEFAULTTONEAREST    0x00000002
         
typedef BOOL (WINAPI* GetMonitorInfo)(HMONITOR 
                  hMonitor, LPMONITORINFO lpmi);
typedef HMONITOR (WINAPI* MonitorFromRect)
             (LPCRECT lprc, DWORD dwFlags);
         
BOOL EnsureRectangleOnDisplay (RECT *prc, UINT fFlags)
{
    HMONITOR hMonitor;
    MONITORINFO mi;
    mi.cbSize = sizeof(mi);
    
    if (!prc)
        return FALSE;
   
    // Load the library once
    static CImcLoadLib user32 (_T("USER32.DLL"));
   
    // Fail to single monitor stuff
    SystemParametersInfo (SPI_GETWORKAREA, 0, &mi.rcWork, FALSE); 
   
    // It would be odd for the program to work
    // with user32.dll missing...
    if (user32.m_hModule) {
        MonitorFromRect mfr = (MonitorFromRect) 
                 user32.GetProcAddress ("MonitorFromRect");
#ifdef UNICODE
        // why there's two versions, I have *no* idea.
        GetMonitorInfo gmi = (GetMonitorInfo) user32.GetProcAddress 
                             ("GetMonitorInfoW");
#else
        // Maybe they'll extend the struct...
        GetMonitorInfo gmi = (GetMonitorInfo) 
                              user32.GetProcAddress ("GetMonitorInfoA");
#endif
    
    // were both of the functions valid?
    if (mfr && gmi)
        {
       // get the nearest monitor to the passed rect.
       hMonitor = mfr (prc, MONITOR_DEFAULTTONEAREST);
       if (hMonitor)
           gmi (hMonitor, &mi);
      }
    }
   
    // Now we have a clipping rectangle,
    // bring our input rectangle into sight.
    if (prc->right > mi.rcWork.right)
    {
        prc->left -= prc->right - mi.rcWork.right;
        prc->right = mi.rcWork.right;
     }
    if (prc->bottom > mi.rcWork.bottom)
    {
        prc->top -= prc->bottom - mi.rcWork.bottom;
        prc->bottom = mi.rcWork.bottom;
    }
   
    if (prc->left < mi.rcWork.left)
    {
        prc->right += mi.rcWork.left - prc->left;
        prc->left = mi.rcWork.left;
    }
    if (prc->top < mi.rcWork.top)
    {
        prc->bottom += mi.rcWork.top - prc->top;
        prc->top = mi.rcWork.top;
    }
   
 return TRUE;
}

The borrowed from elsewhere bit!

////////////////////////////////////////////////////////
// Originally by Bruno Podetti, lifted from
// Owner Drawn Menu with Icons, Titles and Shading 
// http://www.codeproject.com/menu/newmenuxpstyle.asp
// Adapted by me.

class CImcLoadLib
{
public:
 HMODULE m_hModule;
   
 CImcLoadLib (LPCTSTR pModuleName)
 {
  m_hModule = LoadLibrary(pModuleName);
 }
   
 ~CImcLoadLib ()
 {
  if(m_hModule)
  {
   FreeLibrary(m_hModule);
   m_hModule = NULL;
  }
 }
  
 FARPROC GetProcAddress(LPCSTR lpProcName)
 {
  return ::GetProcAddress (m_hModule, lpProcName);
 }
};

Using the code...

BOOL CHistogramPeakClipSelector::OnInitDialog ()
{
    // Make us look like a popup menu, but fancy.
    CRect rc;
    GetWindowRect (&rc);
    rc.OffsetRect (-rc.TopLeft ());
    rc.OffsetRect (m_ptCorner);

    if (EnsureRectangleOnDisplay (rc, 0))
        SetWindowPos (NULL, rc.left, rc.top, 0,0, SWP_NOSIZE | SWP_NOZORDER);
    else
        SendMessage (DM_REPOSITION);
   
 ....

m_ptCorner is where I want the top left corner of the popup to be. But if that would make it fall off the screen, adjust the rectangle appropriately. If for some reason, my glorious helper function failed, fall through to using Peterchen / Nish's idea.

History

  • 2006 March 6th: Improved and reduced, thanks to Occam and Hans Dietrich.
  • 2006 February 24th: I just wrote it - of course, every line is a bug!