如何在 C# 程序中获取任务栏通知区域宽度?

Lem*_*eed 6 .net c# winapi winforms

我正在 C# Winforms 应用程序中开发一个功能,它需要确定任务栏的 Windows“通知区域”的宽度(特别是当停靠在屏幕底部的默认位置时)。

为了提供更好的上下文,程序显示小的、瞬态的、无边界的窗口(类似于工具提示),我们希望确保通知区域“系统托盘”图标和时钟永远不会被这些窗口之一覆盖。

下面是我想要获得的宽度的屏幕截图(来自 Windows 10),以及它的用途:

任务栏通知区域需要的宽度尺寸,以及使用说明

到目前为止,我已经探索了 .Net 框架,并研究了 Win32 API 调用,但是我无法确定任何潜在的解决方案(也无法确定这是否确实可行)。

任何建议将不胜感激。理想情况下,解决方案最早可与 Windows 7 兼容,但这并不是绝对要求。

Jim*_*imi 7

外壳托盘窗口(或任务栏)及其子窗口的位置和大小可以使用GetWindowRect()检索,传递Handle相关窗口的 。
窗口HandleFindWindowEx() ( FindWindowExW)返回,使用窗口类名来标识它。(注意,此函数执行不区分大小写的搜索)。

正如 Hans Passant 在评论中指出的那样,在执行此类措施时需要考虑一些重要的细节。

操作不是框架或系统需要支持的。类名将来可能会更改。这不是一项托管任务。

最重要的是,应用程序必须是 DPI Aware。如果不是,则应用程序受虚拟化的约束。
这意味着当使用非 DPI-Aware API 功能时,其测量/结果也可以虚拟化。
GetWindowRect()例如,不是 DPI-Aware 功能。

来自 MSDN:
混合模式 DPI 缩放和 DPI 感知 API

SO 问题中的一些注释:
从 GetWindowRect 获取 DPI 感知正确的 RECT

关于 DPI Awareness,我在这里写了一些笔记。
另外,来自 Hans Passant 的这个(经典)回答:
如何配置应用程序以在具有高 DPI 设置(例如 150%)的机器上正确运行?

来自 Raymond Chen 的博客:
如何更新我的 WinForms 应用程序,使其在高 DPI 或非常大屏幕上的正常 DPI 下表现更好?

来自 MSDN:
Windows 上的高 DPI 桌面应用程序开发


Shell Tray Window 具有类名Shell_TrayWnd。它的位置和相对大小可由用户定义。这是处于默认位置的类 Windows 范围。

Shell_TrayWnd

托盘通知区域是 的子窗口Shell_TrayWnd。它的类名是TrayNotifyWnd
( Shell_TrayWnd ? TrayNotifyWnd)

托盘通知窗口

其他几个子类:

任务栏,类名MSTaskSwWClass:
( Shell_TrayWnd ? ReBarWindow32 ? MSTaskSwWClass ? MSTaskSwWClass)

MSTaskSwWClass

托盘时钟,类名TrayClockWClass:
( Shell_TrayWnd ? TrayNotifyWnd ? TrayClockWClass)

托盘时钟W类

这些类名适用于 Windows 7 和 Windows 10 中的这些系统组件

关于命名约定的一些说明:
Raymond Chen on为什么有些人称任务栏为“托盘”?


Shell_TrayWndparent is Desktop,因此我们IntPtr.Zero作为父句柄传递。可以改用
GetDesktopWindow()

TrayNotifyWnd是 的子窗口Shell_TrayWnd。我们正在使用它的句柄来加速搜索。

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);
Run Code Online (Sandbox Code Playgroud)

Windows API 声明:

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();
}
Run Code Online (Sandbox Code Playgroud)