Programming for Multiple Monitors in Windows 98
New Multiple-Monitor Win32 API functions
Continued from Installing Multiple Monitors
In order to give applications access to multiple-monitor information, Microsoft has added several new Win32 API functions and updated several existing functions. In addition to new functions, there is also a new type of handle, the HMONITOR, which represents a physical display device. In order to get and set the settings of a particular monitor, you first need to obtain an HMONITOR, which will always point to the same monitor.
The first of the new functions we will discuss is MonitorFromPoint, which has the following definition:
HMONITOR MonitorFromPoint(POINT pt,
DWORD dwFlags);
The returned HMONITOR is the monitor that contains the POINT passed to the function. If no monitor contains the specified point, then the return value of MonitorFromPoint is determined by dwFlags, for which there are three possible values: MONITOR_DEFAULTTONULL returns NULL if the point is not contained by any monitor, MONITOR_DEFAULTTOPRIMARY returns the primary display device, and MONITOR_DEFAULTTONEAREST returns the monitor closest to the point.
HMONITOR MonitorFromRect(LPRECT lprc,
DWORD dwFlags);
HMONITOR MonitorFromWindow(HWND hWnd,
DWORD dwFlags);
MonitorFromRect and MonitorFromWindow return the handle of the monitor that contains the largest portion of the specified rectangle or window. If no monitor contains the specified rectangle or window, the return value depends upon the value of the dwFlags field, which has the same possible values for these two functions as it does for MonitorFromPoint.
Once you have obtained an HMONITOR, you can use the function GetMonitorInfo to retrieve a MONITORINFO structure filled with data relevant to the specified monitor, as follows:
BOOL GetMonitorInfo( HMONITOR hmonitor, LPMONITORINFO lpmi);
typedef struct tagMONITORINFO
{
DWORD cbSize;
RECT rcMonitor;
RECT rcWork;
DWORD dwFlags;
} MONITORINFO, *LPMONITORINFO;
typedef struct tagMONITORINFOEXA
{
MONITORINFO;
TCHAR szDevice[CCHDEVICENAME];
} MONITORINFOEX, *LPMONITORINFOEX;
There are actually two versions of this structure, MONITORINFO and MONITORINFOEX. The cbSize member contains the size of the structure, and it is used by Windows to determine whether a structure is a MONITORINFO or MONITORINFOEX. After a successful call to GetMonitor, the rcMonitor member contains the rectangle of the monitor within the virtual desktop. The rcWork member contains the work area of the monitor within the virtual desktop. (A monitor's work area is the portion not covered by the taskbar.) The dwFlags member has only one possible value, MONITORINFOF_PRIMARY, which is set if the monitor is the primary display. The MONITORINFOEX structure has one additional parameter, szDevice, which contains the unique name of the device. The device name can be passed to other functions, such as ChangeDisplaySettingsEx, allowing the settings for each monitor to be changed independently.
Before you pass GetMonitorInfo a pointer to a MONITORINFO or MONITORINFOEX structure, you must set the cbSize member to the size of the structure so that Windows can determine which structure is being used. The following code sample shows how:
MONITORINFOEX mix;
mix.cbSize = sizeof(mix);
GetMonitorInfo(hMonitor,
(LPMONITORINFO)&mix);
Some changes were made to GetSystemMetrics to allow the use of multiple monitors. Passing SM_CXSCREEN and SM_CYSCREEN as parameters returns the x and y extents, respectively, of only the primary monitor. If you wish to get the x and y extents of the virtual desktop, use the new constants SM_CXVIRTUALSCREEN and SM_CYVIRTUALSCREEN; GetSystemMetrics will return the left and top coordinates of the virtual desktop, respectively. You can no longer assume the top-left corner of the desktop is (0,0), and the extents will do you no good if you don't know the origins. Passing the new constant SM_SAMEDISPLAYFORMAT to GetSystemMetrics will return TRUE if all of the monitors on the system have exactly the same color settings, FALSE otherwise. Use the constant SM_CMONITORS when you want to know how many monitors make up the virtual desktop. Note that this value includes only installed monitors that have been added to the desktop by the user.
The SystemParametersInfo function has also been changed slightly. The changes affect only the uiAction values SPI_GETWORKAREA and SPI_SETWORKAREA. Using the former always returns the work area of the primary monitor. If you want to get the work area of another monitor, you need to use GetMonitorInfo. Using SPI_SETWORKAREA sets the work area of the monitor that contains the RECT structure passed into the pvParam value.
EnumDisplayMonitors is a new function that allows an application to optimize its drawing code when a window is being partially displayed on multiple monitors that have different color-bit depths.
BOOL WINAPI EnumDisplayMonitors(
HDC hdc,
LPCRECT lprcClip,
MONITORENUMPROC lpfnEnum,
LPARAM dwData);
The MONITORENUMPROC callback is called for each monitor that intersects the visible region of hdc and the lprcClip parameter. If the lprcClip parameter is NULL, then no additional clipping is performed. The dwData parameter is for user-defined data and is passed through to the dwData parameter in the callback function. MONITORENUMPROC is a user-defined callback function that must have the following signature:
BOOL CALLBACK MonitorEnumProc(
HMONITOR hmonitor,
HDC hdcMonitor,
LPRC lprcMonitor,
DWORD dwData);
If the hdc passed to EnumDisplayMonitors was NULL, then hdcMonitor is NULL. Otherwise, hdcMonitor contains a valid device context whose color attributes match the display monitor defined by hmonitor. If hdcMonitor is NULL, then lprcMonitor is in virtual-desktop coordinates. It is important to note that no application needs to use EnumDisplayMonitors to handle monitors of differing bit depths, since Windows automatically dithers high-color images on lower-color devices. You should use this function only if you want to do custom dithering to ensure the best possible display. In order to help you understand exactly how EnumDisplayMonitors is used, I will present a couple of code snippets illustrating common usage scenarios.
One use for EnumDisplayMonitors is to make sure that images are drawn optimally on lower-color devices. The best way to do this is to change how your window's WndProc handles the WM_PAINT message.
case WM_PAINT:
HDC hdc;
hdc = BeginPaint(hWnd,
&paintStruct);
EnumDisplayMonitors(hdc, NULL, monitorEnumPaintProc, 0);
EndPaint(&paintStruct);
Inside your callback, you query the passed device context to determine its capabilities and display area and do your drawing accordingly.
You can also use EnumDisplayMonitors to query every monitor on the desktop. If the device context and RECT passed into EnumDisplayMonitors are both NULL, no clipping is performed and your callback function is called once for each monitor in the virtual desktop. The code in Figure 3 caches the work area of every monitor for later use. (Note that if your application actually did this, you would need to update the cached information when the WM_DISPLAYCHANGE message was received, since the values might change.)
Another way to query display devices is the EnumDisplayDevices function, which can query all devices available on the system, whether or not they're part of the virtual desktop.
BOOL WINAPI EnumDisplayDevices(
PVOID Unused,
DWORD iDevNum,
PDISPLAY_DEVICE lpDisplayDevice,
DWORD dwFlags);
Unused is reserved for future use and must be set to NULL. There are no valid values for dwFlags, so set it to 0. iDevNum is the index of the device you wish to query and is zero-based. If there is not a device available in the iDevNum position, EnumDisplayDevices returns FALSE. If there is a device in the position indicated by iDevNum, EnumDisplayDevices returns TRUE and fills in lpDisplayDevice. So to examine all devices on the system, you'd call EnumDisplayDevices for each possible index, starting with 0, until it returned FALSE. The lpDisplayDDevice parameter is a pointer to a DISPLAY_DEVICE structure, which is defined as follows:
typedef struct _DISPLAY_DEVICE {
DWORD cb;
BYTE DeviceName[32];
BYTE DeviceString[128];
DWORD StateFlags;
} DISPLAY_DEVICE, *PDISPLAY_DEVICE,
*LPDISPLAY_DEVICE;
The cb member contains the size of the structure in bytes. DeviceName is the same unique name returned by GetMonitorInfo in the szDevice member of the MONITORINFOEX structure. DeviceString is a user-friendly description of the enumerated device. Unfortunately, I was not able to find documentation on the possible values of StateFlag, but they seem self-explanatory. Here are their definitions as listed in the Win32 SDK:
#define DISPLAY_DEVICE
_ATTACHED_TO_DESKTOP
0x00000001
#define DISPLAY_DEVICE
_MULTI_DRIVER
0x00000002
#define DISPLAY_DEVICE
_PRIMARY_DEVICE
0x00000004
#define DISPLAY_DEVICE
_MIRRORING_DRIVER
0x00000008
#define DISPLAY_DEVICE
_VGA_COMPATIBLE
0x00000010
Since EnumDisplayDevices allows you to query devices that aren't part of the desktop, your application can use monitors in an exclusive manner, without having to share the screen with the desktop. To do this, find a device that doesn't have the DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set and call the Win32 API function CreateDC, passing the name returned in the DeviceName member of lpDisplayDevice. If you create a device context in this manner, you should delete it when you're done with it by calling DeleteDC.
Next: Other Issues
Published as Power Programming in the 4/7/98 issue of PC Magazine.