At the end of Part 3 in this series, I noted that the window positioning logic depends on accurately getting the bounds of the monitor where the notify icon is located. Specifically, we require the bounds of the working area (the space on the monitor excluding the taskbar and other docked items). WPF gives us the System.Windows.SystemParameters.WorkArea property, but this gives us the area of the primary display monitor’s working area, and the taskbar (and thus notify icon) might be located on a different monitor. Unfortunately, support for accessing information about anything other than the primary monitor with the SystemParameters class seems to be absent as of .NET 4.0.
We could use the System.Windows.Forms.Screen class to easily solve this problem: the System.Windows.Forms.Screen.GetWorkingArea method has an overload for finding the working area of the monitor that contains a given rectangle, which is exactly what we need to do. However, I am going to opt to use Win32, instead, simply to avoid depending on WinForms (yes, I realise that sounds a bit strange given that this project revolves around a System.Windows.Forms.NotifyIcon). In any case, there is something to be said for knowing what it is that all these .NET functions wrap around 🙂
The two functions we’ll use are MonitorFromRect (to find the handle of the monitor containing the notify icon) and GetMonitorInfo (to get the working area of that monitor).
MonitorFromRect
First, we’ll get the handle of the monitor that displays the notify icon. The syntax for MonitorFromRect is:
1 2 3 4 |
HMONITOR MonitorFromRect( __in LPCRECT lprc, __in DWORD dwFlags ); |
and the PInvoke signature in C# is (you’ll also need to define the RECT struct if you haven’t already – see Part 2 or Part 3):
1 2 |
[DllImport("user32.dll")] private static extern IntPtr MonitorFromRect([In] ref RECT lprc, int dwFlags); |
dwFlags specifies what the function should return if the rectangle doesn’t intersect with any monitor. These are the possible options:
1 2 3 |
private const int MONITOR_DEFAULTTONULL = 0; // returns null private const int MONITOR_DEFAULTTOPRIMARY = 1; // returns primary monitor handle private const int MONITOR_DEFAULTTONEAREST = 2; // returns nearest monitor handle |
I will choose to use MONITOR_DEFAULTTONEAREST for this project, but this argument shouldn’t really matter; the notify icon rectangle should always intersect with a monitor.
GetMonitorInfo
Now we will define the GetMonitorInfo class, which will tell us the working area rectangle of the monitor we find with MonitorFromRect. The syntax is:
1 2 3 4 |
BOOL GetMonitorInfo( __in HMONITOR hMonitor, __out LPMONITORINFO lpmi ); |
The PInvoke signature:
1 2 |
[DllImport("user32.dll")] private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi); |
We need to also define the MONITORINFO struct:
1 2 3 4 5 6 7 8 |
[StructLayout(LayoutKind.Sequential)] private struct MONITORINFO { public Int32 cbSize; public RECT rcMonitor; public RECT rcWork; public Int32 dwFlags; } |
Putting it together
We can now write a method that puts these two functions together to find our notify icon rectangle’s monitor’s working area bounds.
1 2 3 4 |
IntPtr monitorhandle = MonitorFromRect(ref rect, MONITOR_DEFAULTTONEAREST); MONITORINFO monitorinfo = new MONITORINFO(); monitorinfo.cbSize = Marshal.SizeOf(monitorinfo); GetMonitorInfo(monitorhandle, ref monitorinfo); |
monitorinfo.rcWork will hold a RECT with the correct working area bounds.