Accessing Monitor Information with C#, Part 1: Getting Monitor Handles

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

The series will include the following:

  • Part 1: Getting Monitor Handles
  • Part 2: Getting a Monitor associated with a Window Handle

We will be discussing a number of components associated with Native Methods in these posts. If you are concerned about some references not being explicitly established in the demonstrated code, don’t worry: I have posted the source on CodePlex for your usage.

 
The Native Methods
I have stated this in earlier posts, but it bears repeating: “It has been accepted in the .Net community that you should use Native Methods only when the .Net Framework does not offer a managed solution. Manged is always better because it has been configured to be properly managed in memory, and to be recycled by the garbage collector. I tend to use Native Methods most often when I need to interact with an external process and there are no valid hooks in the Framework.

 
For this exercise, we will be using the following Native Methods and structures:

RECT
The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle.


/// <summary>
/// Rectangle
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

 
MONITORINFO
The MONITORINFO structure contains information about a display monitor. It is composed of the size of the structure, the RECT representing the area the application exists in the total viewable area (all monitors that make up the desktop space), the RECT representing the working area taken up by the application, and flags associated with special attributes assigned.


/// <summary>
/// Monitor information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
    public uint size;
    public RECT monitor;
    public RECT work;
    public uint flags;
}

 
EnumDisplayMonitors
This method enumerates through the display monitors with the assistance of a delegate to process each monitor.


/// <summary>
/// Monitor Enum Delegate
/// </summary>
/// <param name="hMonitor">A handle to the display monitor.</param>
/// <param name="hdcMonitor">A handle to a device context.</param>
/// <param name="lprcMonitor">A pointer to a RECT structure.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the enumeration function.</param>
/// <returns></returns>
public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor,
    ref RECT lprcMonitor, IntPtr dwData);

/// <summary>
/// Enumerates through the display monitors.
/// </summary>
/// <param name="hdc">A handle to a display device context that defines the visible region of interest.</param>
/// <param name="lprcClip">A pointer to a RECT structure that specifies a clipping rectangle.</param>
/// <param name="lpfnEnum">A pointer to a MonitorEnumProc application-defined callback function.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the MonitorEnumProc function.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
	MonitorEnumDelegate lpfnEnum, IntPtr dwData);

 
GetMonitorInfo
Retrieves the monitor information.

	
/// <summary>
/// Gets the monitor information.
/// </summary>
/// <param name="hmon">A handle to the display monitor of interest.</param>
/// <param name="mi">A pointer to a MONITORINFO instance created by this method.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool GetMonitorInfo(IntPtr hmon, ref MONITORINFO mi);

 
Next, we need to define MonitorInfoWithHandle, which will store the MONITORINFO and monitor handle for each monitor instance:


/// <summary>
/// Monitor information with handle.
/// </summary>
public class MonitorInfoWithHandle : IMonitorInfoWithHandle
{
    /// <summary>
    /// Gets the monitor handle.
    /// </summary>
    /// <value>
    /// The monitor handle.
    /// </value>
    public IntPtr MonitorHandle { get; private set; }

    /// <summary>
    /// Gets the monitor information.
    /// </summary>
    /// <value>
    /// The monitor information.
    /// </value>
    public MONITORINFO MonitorInfo { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="MonitorInfoWithHandle"/> class.
    /// </summary>
    /// <param name="monitorHandle">The monitor handle.</param>
    /// <param name="monitorInfo">The monitor information.</param>
    public MonitorInfoWithHandle(IntPtr monitorHandle, MONITORINFO monitorInfo)
    {
        MonitorHandle = monitorHandle;
        MonitorInfo = monitorInfo;
    }
}

 
Now that we have our Native Methods and structures in place, let’s build our explicit MonitorEnumProc which will be used by our enumeration through the monitors:


/// <summary>
/// Monitor Enum Delegate
/// </summary>
/// <param name="hMonitor">A handle to the display monitor.</param>
/// <param name="hdcMonitor">A handle to a device context.</param>
/// <param name="lprcMonitor">A pointer to a RECT structure.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the enumeration function.</param>
/// <returns></returns>
public static bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData)
{
    var mi = new MONITORINFO();
    mi.size = (uint)Marshal.SizeOf(mi);
    NativeMonitorHelper.GetMonitorInfo(hMonitor, ref mi);

    // Add to monitor info
    _monitorInfos.Add(new MonitorInfoWithHandle(hMonitor, mi));
    return true;
}

Here is what this is doing:

  1. A new MONITORINFO structure is instantiated for the purpose of containing the data discovered by GetMonitorInfo.
  2. The size of the MONITORINFO is determined as a pre-requisite for the instance to be properly populated. Note: We see this a lot with structures used with Native Methods.
  3. GetMonitorInfo is called with a monitor handle provided by EnumDisplayMonitors, as well as our newly created MONITORINFO instance.
  4. A new MonitorInfoWithHandle instance is created with the provided monitor handle and our newly populated MONITORINFO instance, which is then added to a persisted list.

 
Lastly, we need to put together our EnumDisplayMonitors method which will enumerate through all the display monitors using our MonitorEnum delegate:


/// <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();
}

What this does is a lot simpler since the majority of the work is done by MonitorEnum:

  1. The monitor info list is created.
  2. EnumDisplayMonitors is called with our MonitorEnum delegate to retrieve the data for each monitor.
  3. The monitor info array is returned to the caller.

 
And that’s all there is to it. There are a lot of components but it’s actually pretty straight forward.

Next time in Part 2, we will look at how we can discover what monitor a specific process is displayed when it has a valid Main Window Handle.

Until then…

 
Comments

No comments yet.

Leave a Reply