All posts in Unmanaged Code

This is the second of a 2-part series on Accessing Monitor Information with C#.

The series will include the following:

Like I said in Part 1 this uses Native Methods so don’t act surprised if I drop a few in this post.

Note: if easily startled by unmanaged code, please seek the help of a medical professional. Otherwise, read on.

So, to continue, we are going to start by using our GetMonitors method to grab the current set of Monitors associated with our computer:


private static IList<MonitorInfoWithHandle> _monitorInfos;

/// <summary>
/// Gets the monitors.
/// </summary>
/// <returns></returns>
public static MonitorInfoWithHandle[] GetMonitors()
{
    // New List
    _monitorInfos = new List<MonitorInfoWithHandle>();

    // Enumerate monitors
    NativeMonitorHelper.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero);

    // Return list
    return _monitorInfos.ToArray();
}

Now, we won’t be using this method directly in this post, but you should observe the results to understand which handles are associated with each monitor on your machine. For this purposes of this exercise, we will be looking from the process-side and determining which monitor handle it relates to.

So, to continue we look to the native method EnumWindows:

 
EnumWindows
Enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function. EnumWindows continues until the last top-level window is enumerated or the callback function returns false.

 
EnumWindowsProc
An application-defined callback function used with the EnumWindows or EnumDesktopWindows function. It receives top-level window handles. The WNDENUMPROC type defines a pointer to this callback function. EnumWindowsProc is a placeholder for the application-defined function name.


/// <summary>
/// EnumWindows Processor (delegate)
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
public delegate bool EnumWindowsProc(IntPtr windowHandle, IntPtr lParam);

/// <summary>
/// Enums the windows.
/// </summary>
/// <param name="enumWindowsProcessorDelegate">The enum windows processor delegate.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc enumWindowsProcessorDelegate, IntPtr lParam);

The key to using this method is to provide it a delegate which will act on each instance discovered and help us extract the results.

To get the monitor related to a window handle we need to first get the RECT the window occupies with GetWindowRect.

 
GetWindowRect
Retrieves the dimensions of the bounding rectangle of the specified window. The dimensions are given in screen coordinates that are relative to the upper-left corner of the screen.


/// <summary>
/// Gets the rectangle representing the frame of a window.
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="rectangle">The rectangle.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr windowHandle, ref RECT rectangle);

 
Next we need to derive the monitor from the RECT found in our last operation and we will do that using MonitorFromRect:

MonitorFromRect
The MonitorFromRect function retrieves a handle to the display monitor that has the largest area of intersection with a specified rectangle.


/// <summary>
/// Monitors from rect.
/// </summary>
/// <param name="rectPointer">The RECT pointer.</param>
/// <param name="flags">The flags.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromRect([In] ref RECT rectPointer, uint flags);

 
So, now let’s put that all together with an example:


#region Members

private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
private IList<WindowAndMonitorHandle> _windowAndMonitorHandles;

#endregion

#region Methods

/// <summary>
/// Retrieves a list of all main window handles and their associated process id's.
/// </summary>
/// <returns></returns>
public static WindowAndMonitorHandle[] GetWindowAndMonitorHandles()
{
	// new list
	_windowAndMonitorHandles = new List<WindowAndMonitorHandle>();

	// Enumerate windows
	WindowHelper.EnumWindows(EnumTheWindows, IntPtr.Zero);

	// Return list
	return _windowAndMonitorHandles.ToArray();
}

/// <summary>
/// Enumerates through each window.
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="lParam">The l parameter.</param>
/// <returns></returns>
private static bool EnumTheWindows(IntPtr windowHandle, IntPtr lParam)
{
	// Get window area
	var rect = new RECT();
	MonitorHelper.GetWindowRect(windowHandle, ref rect);

	// Get current monitor
	var monitorHandle = MonitorHelper.MonitorFromRect(ref rect, MONITOR_DEFAULTTONEAREST);

	// Add to enumerated windows
	_windowAndMonitorHandles.Add(new WindowAndMonitorHandle(windowHandle, monitorHandle));
	return true;
}

#endregion

#region Native Methods

/// <summary>
/// EnumWindows Processor (delegate)
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="lParam">The lparameter.</param>
/// <returns></returns>
public delegate bool EnumWindowsProc(IntPtr windowHandle, IntPtr lParam);

/// <summary>
/// Enums the windows.
/// </summary>
/// <param name="enumWindowsProcessorDelegate">The enum windows processor delegate.</param>
/// <param name="lParam">The lparameter.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc enumWindowsProcessorDelegate, IntPtr lParam);

/// <summary>
/// Gets the rectangle representing the frame of a window.
/// </summary>
/// <param name="windowHandle">The window handle.</param>
/// <param name="rectangle">The rectangle.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr windowHandle, ref RECT rectangle);

/// <summary>
/// Monitors from rect.
/// </summary>
/// <param name="rectPointer">The RECT pointer.</param>
/// <param name="flags">The flags.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromRect([In] ref RECT rectPointer, uint flags);

#endregion

/// <summary>
/// A simple class to group our handles.
/// </summary>
/// <returns></returns>
public class WindowAndMonitorHandle
{
	public IntPtr WindowHandle { get; }
	public IntPtr MonitorHandle { get; }
	
	public WindowAndMonitorHandle(IntPtr windowHandle, IntPtr monitorHandle)
	{
		WindowHandle = windowHandle;
		MonitorHandle = monitorHandle;
	}
}

 
Does it seem like a lot of work? Yes, definitely. With native methods, PAIN = GAIN. Don’t concern yourself with how much code it takes, but focus on what you are actually gaining. If you compare the monitor handle for each WindowAndMonitorHandle with what you returned from GetMonitors you should immediately be able to relate a window handle to a monitor.

So, until next time…