Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to obtain task bar Notification Area width in a C# program?

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:

Width dimension needed from Notification Area of Task Bar, and explanation of use

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.

like image 452
Lemonseed Avatar asked Sep 28 '18 11:09

Lemonseed


People also ask

What is the notification area in the taskbar?

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.

How do I expand my taskbar?

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.

How do I change my taskbar settings?

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.


1 Answers

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.

Shell_TrayWnd

The Tray Notification Area is a child window of Shell_TrayWnd. Its class name is TrayNotifyWnd
(Shell_TrayWnd → TrayNotifyWnd)

TrayNotifyWnd

A couple of other child classes:

The Task Bar, class name MSTaskSwWClass:
(Shell_TrayWnd → ReBarWindow32 → MSTaskSwWClass → MSTaskListWClass)

MSTaskSwWClass

The Tray Clock, class name TrayClockWClass:
(Shell_TrayWnd → TrayNotifyWnd → TrayClockWClass)

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();
}
like image 133
Jimi Avatar answered Oct 21 '22 04:10

Jimi