I am developing a feature within a C# Winforms application which has a requirement for determining the width of the Windows "Notification Area" of the Task Bar (specifically when docked in the default location at the bottom of the screen).
To give better context, the program displays small, transient, borderless windows (tool-tip like), and we would like to ensure that the Notification Area "system tray" icons and clock are never covered by one of these windows.
Below is a screen shot (from Windows 10) of the width I would like to obtain, and how it is intended to be used:
Thus far I have explored the .Net framework, as well as researched Win32 API calls, however I could not identify any potential solutions (nor ascertain whether or not this is indeed possible).
Any suggestions would be greatly appreciated. Ideally a solution would be compatible as far back as Windows 7, however this is not an absolute requirement.
The notification area is located at the right end of the taskbar. It contains icons you might find yourself selecting or pressing pretty often: battery, Wi-Fi, volume, Clock and Calendar, and action center. It provides status and notifications about things like incoming email, updates, and network connectivity.
If you need additional space for more icons, or a little bit more desktop space, you can resize the unlocked taskbar. From the border of the taskbar, click and drag it up or down, depending on its current location.
Change your taskbar settingsPress and hold or right-click any empty space on the taskbar, and then select Taskbar settings. In the Taskbar settings, scroll to see the options for customizing, choosing icons, and much more.
The position and size of the Shell Tray Window (or TaskBar) and its child windows can be retrieved using GetWindowRect(), passing the Handle
of the related window.
The windows Handle
is returned by FindWindowEx() (FindWindowExW
), using the window Class Name to identify it. (As a note, this function performs a case-insensitive search).
There are some important details to consider when performing this kind of measures, as Hans Passant noted in his comment.
This operation is not something the Framework or the System need to support. The class names might change in the future. It's not a managed task.
Most important, the application must be DPI Aware. If not, the application is subject to virtualization.
This implies that when a non-DPI-Aware API function is used, its measures/results can be virtualized, too.GetWindowRect()
, for example, is not a DPI-Aware function.
From MSDN:
Mixed-Mode DPI Scaling and DPI-aware APIs
Some notes from a SO question:
Getting a DPI aware correct RECT from GetWindowRect
About DPI Awareness, I've written some notes here.
Also, this (classic) answer from Hans Passant:
How to configure an app to run correctly on a machine with a high DPI setting (e.g. 150%)?
From Raymond Chen's blog:
How can I update my WinForms app to behave better at high DPI, or at normal DPI on very large screens?
From MSDN:
High DPI Desktop Application Development on Windows
The Shell Tray Window has class name Shell_TrayWnd
. Its position and relative size can be defined by the user. This is the class Windows extent in its default position.
The Tray Notification Area is a child window of Shell_TrayWnd
. Its class name is TrayNotifyWnd
(Shell_TrayWnd → TrayNotifyWnd
)
A couple of other child classes:
The Task Bar, class name MSTaskSwWClass
:
(Shell_TrayWnd → ReBarWindow32 → MSTaskSwWClass → MSTaskListWClass
)
The Tray Clock, class name TrayClockWClass
:
(Shell_TrayWnd → TrayNotifyWnd → TrayClockWClass
)
These class names applies to these system components in both Windows 7 and Windows 10
Some notes about the Naming Convention here:
Raymond Chen on Why do some people call the taskbar the "tray"?
Shell_TrayWnd
parent is Desktop
, thus we're passing IntPtr.Zero
as parent handle.
GetDesktopWindow() can be used instead.
TrayNotifyWnd
is a child window of Shell_TrayWnd
. We're using its Handle to speed up the search.
using System.Drawing;
using System.Runtime.InteropServices;
//Shell Tray rectangle
IntPtr hWnd = FindWindowByClassName(IntPtr.Zero, "Shell_TrayWnd");
Rectangle shellTrayArea = GetWindowRectangle(hWnd);
//Notification area rectangle
hWnd = FindWindowByClassName(hWnd, "TrayNotifyWnd");
Rectangle trayNotifyArea = GetWindowRectangle(hWnd);
Windows API Declarations:
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
[SuppressUnmanagedCodeSecurity, SecurityCritical]
internal static class SafeNativeMethods
{
[DllImport("User32.dll", SetLastError = true)]
internal static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
}
//Helper methods
[SecuritySafeCritical]
public static IntPtr FindWindowByClassName(IntPtr hwndParent, string className)
{
return SafeNativeMethods.FindWindowEx(hwndParent, IntPtr.Zero, className, null);
}
[SecuritySafeCritical]
public static Rectangle GetWindowRectangle(IntPtr windowHandle)
{
RECT rect;
new UIPermission(UIPermissionWindow.AllWindows).Demand();
SafeNativeMethods.GetWindowRect(windowHandle, out rect);
return rect.ToRectangle();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With