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...
#define COMPILE_MULTIMON_STUBS
#include <multimon.h>
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);
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;
static CImcLoadLib user32 (_T("USER32.DLL"));
SystemParametersInfo (SPI_GETWORKAREA, 0, &mi.rcWork, FALSE);
if (user32.m_hModule) {
MonitorFromRect mfr = (MonitorFromRect)
user32.GetProcAddress ("MonitorFromRect");
#ifdef UNICODE
GetMonitorInfo gmi = (GetMonitorInfo) user32.GetProcAddress
("GetMonitorInfoW");
#else
GetMonitorInfo gmi = (GetMonitorInfo)
user32.GetProcAddress ("GetMonitorInfoA");
#endif
if (mfr && gmi)
{
hMonitor = mfr (prc, MONITOR_DEFAULTTONEAREST);
if (hMonitor)
gmi (hMonitor, &mi);
}
}
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!
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 ()
{
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!
I have now moved to Sweden for love, and recently married a lovely Swede.
-----------------
I started programming on BBC micros (6502) when I was six and never quite stopped even while I was supposed to be studying physics and uni.
I've been working for ~13 years writing software for machine control and data analysis. I now work on financial transaction transformation software, for a Software company in Gamlastan, Stockholm.
Look at my articles to see my excellent coding skills. I'm modest too!