Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
305 views
in Technique[技术] by (71.8m points)

.net - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...