【问题标题】:How to obtain task bar Notification Area width in a C# program?如何在 C# 程序中获取任务栏通知区域的宽度?
【发布时间】:2019-03-04 09:06:14
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 为什么需要确定宽度?这感觉像是一个 XY 问题 - meta.stackexchange.com/questions/66377/what-is-the-xy-problem
  • @mjwills 我们的解决方案使用小的、瞬态的、无边框的窗口(想想工具提示之类的),在我们的特定用例中,我希望始终确保通知区域/系统托盘永远不会被其中一个覆盖这些窗口。
  • @mjwills 我已经更新了问题和图表,以清楚地说明问题和方法。
  • 只有一种正式的方法可以做这样的事情,.NET 中的 NotifyIcon.ShowBalloonTip()。做任何其他事情都有非常麻烦的风险,乘以用户的烦恼。您可以通过使用 64 位版本的 Spy++ 找出如何破解它,帮助您找到该窗口的类名。他们已经有一段时间没有改变了,未来是模糊的。不要担心用户喜欢的任务栏位置,并确保您的应用是 dpiAware。
  • @Jimi Methinks 这应该是一个答案,而不是评论。以评论的形式阅读有点困难。

标签: c# .net winforms winapi


【解决方案1】:

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

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

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

最重要的是,应用程序必须支持 DPI。如果没有,应用程序将受到虚拟化的影响。
这意味着当使用非 DPI-Aware API 函数时,其度量/结果也可以虚拟化。
例如,GetWindowRect() 不是 DPI-Aware 函数。

来自 MSDN:
Mixed-Mode DPI Scaling and DPI-aware APIs

来自 SO 问题的一些注释:
Getting a DPI aware correct RECT from GetWindowRect

关于 DPI 意识,我写了一些笔记here
此外,Hans Passant 的这个(经典)回答:
How to configure an app to run correctly on a machine with a high DPI setting (e.g. 150%)?

来自 Raymond Chen 的博客:
How can I update my WinForms app to behave better at high DPI, or at normal DPI on very large screens?

来自 MSDN:
High DPI Desktop Application Development on Windows


Shell 托盘窗口的类名称为 Shell_TrayWnd。它的位置和相对大小可以由用户定义。这是默认位置的类 Windows 范围。

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

其他几个子类:

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

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

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

这里有一些关于命名约定的注意事项:
Raymond Chen Why do some people call the taskbar the "tray"?


Shell_TrayWnd 父级是 Desktop,因此我们将 IntPtr.Zero 作为父句柄传递。
可以使用GetDesktopWindow() 代替。

TrayNotifyWndShell_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);

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

【讨论】:

  • 感谢您提供了一个令人难以置信但简单的解决方案。刚刚测试了您的代码,它正是我正在寻找的!
  • 这对我帮助很大。不过我有一个问题,我在哪里可以找到这些类名?我实际上需要在通知区域上找到音量图标矩形(我相信它是TrayNotifyWndShell_TrayWnd 的孩子)。我在哪里可以找到音量窗口类名称,或者你碰巧知道吗?提前致谢。
  • @DC_AC 该工具栏(用户升级通知区域)是SysPager 的子级:Shell_TrayWnd -> TrayNotifyWnd -> SysPager -> ToolbarWindow32。还有一个名为 User Promoted Notification Area 的类,其类名为 ToolbarWindow32,但它不是 SysPager 的子代,它是 TrayNotifyWnd 的直接子代。您可以使用 Spy++(.exe 名称 spyxx_amd64.exe)找到这些类名。它与 Visual Studio 一起安装。
  • @DC_AC 您应该在 C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools 中找到它(在 Visual Studio 2017 安装中)。
猜你喜欢
  • 2015-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-27
  • 1970-01-01
相关资源
最近更新 更多