【问题标题】:How do I determine if a window is off-screen?如何确定窗口是否在屏幕外?
【发布时间】:2011-06-08 13:54:32
【问题描述】:

在 Windows XP 及更高版本中,给定窗口句柄 (HWND),我如何判断窗口位置和大小是否使窗口无法恢复地离开屏幕?例如,如果标题栏对光标可用,则可以将窗口拖回屏幕上。我需要发现窗口实际上是否可见或至少对用户可用。我想我还需要知道如何检测和响应分辨率变化以及如何处理多个显示器。这似乎是一个相当大的问题。我正在使用 C++ 和常规 SDK,因此请将您的答案限制在该平台上,而不是调用 C# 或类似的。

【问题讨论】:

    标签: c++ windows winapi css-position multiple-monitors


    【解决方案1】:

    Windows 可以相对简单地确定用户在主监视器上的工作区域(即未被任务栏遮挡的屏幕区域)的大小。调用 SystemParametersInfo 函数并为第一个参数 (uiAction) 指定 SPI_GETWORKAREA 标志。 pvParam 参数应该指向一个RECT structure,它将以虚拟屏幕坐标接收工作区域的坐标。

    获得描述工作区域的坐标后,只需将这些坐标与应用程序窗口的当前位置进行比较,即可确定它是否在这些范围内。


    支持多台显示器的愿望使事情变得稍微复杂一些。 SystemParametersInfo 的文档建议您需要调用 GetMonitorInfo function 来获取除主监视器之外的监视器的工作区域。它填充了一个名为 MONITORINFOEX 的结构,其中包含定义该监视器的工作区域的成员 rcWork,再次以虚拟屏幕坐标表示为 RECT 结构。

    要正确执行此操作,您需要枚举用户已连接到系统的所有监视器,并使用 GetMonitorInfo 检索每个监视器的工作区域。

    在 Internet 上可以找到一些这样的示例:

    • MSDN 有一些Positioning Objects on a Multiple Display Setup 的示例代码。
    • 如果您使用的是 MFC,下面是 an excellent example 的多显示器支持。
    • 即使您不使用 MFC,那篇文章也提到了 the following link,就解释了多显示器支持如何在 Windows 中工作而言,它看起来是一个真正的宝石,即使它有点老派。不管你喜不喜欢,这在 Windows 的更高版本中几乎没有改变。


    最后,您提到想要检测分辨率变化。这比你想象的要简单得多。如您所知,如果您进行过任何 Windows 编程,操作系统与您的应用程序通信的主要方式是向您的 WindowProc function 发送消息。
    在这种情况下,您需要注意WM_DISPLAYCHANGE message,它会在显示分辨率发生变化时发送到所有窗口。 wParam 包含新的图像深度(以每像素位数为单位); lParam的低位字指定水平分辨率,lParam的高位字指定屏幕的垂直分辨率。

    【讨论】:

    • 谢谢,这是一个很好的开始 - 我不敢相信我以前没有遇到过 SystemParametersInfo。我仍然遇到标题栏是否可见的问题,以及如果分辨率发生变化会发生什么。
    • @hatcat:没有理由自责; Windows API 很大,没有人知道一切。我不确定您如何仍然停留在标题栏的可见性上。检查包含标题栏的窗口区域是否位于使用上述任一功能获得的屏幕工作区域内。我直接忘记了检测分辨率变化;我会更新我的答案。
    • 谢谢!自 1992 年以来,我一直在编写 Windows,信不信由你。我认为最难的事情是知道什么功能被取代,什么时候被取代,以及你是否应该坚持你所知道的。标题栏部分更多地与检索该区域有关,因此我可以对其进行测试。我只是检查前十个像素和侧十个像素,应该足够了。
    • @hatcat:啊,我不会硬编码 10 像素的值;这太容易坏了。尤其是当您可以使用GetSystemMetrics function 确定实际 大小时。要获取窗口标题栏的高度(以像素为单位),请为 nIndex 参数指定 SM_CYCAPTION。要以像素为单位获取窗口边框的宽度,请指定SM_CXBORDER,以像素为单位的窗口边框的高度,请指定SM_CYBORDER
    • 哦!是的,我现在记得。天哪,灰质正在缩小。再次感谢。
    【解决方案2】:

    可见性检查非常简单。

    RECT rtDesktop, rtView;
    
    GetWindowRect( GetDesktopWindow(), &rtDesktop );
    GetWindowRect( m_hWnd, &rtView );
    
    HRGN rgn = CreateRectRgn( rtDesktop.left, rtDesktop.top, rtDesktop.right, rtDesktop.bottom );
    
    BOOL viewIsVisible = RectInRegion( rgn, &rtView );
    
    DeleteObject(rgn);
    

    你不必使用 RectInRegion,我用于缩短代码。

    如果处理WM_SETTINGCHANGE消息,显示、分辨率变化监控也很容易。

    http://msdn.microsoft.com/en-us/library/ms725497(v=vs.85).aspx

    更新

    正如@Cody Gray 所说,我认为 WM_DISPLAYCHANGE 比 WM_SETTINGCHANGE 更合适。但是 MFC 9.0 库使用了 WM_SETTINGCHANGE。

    【讨论】:

      【解决方案3】:

      您可以使用 MonitorFromRect 或 MonitorFromPoint 来检查窗口的左上角或右下角是否不包含在任何显示监视器中(离屏)。

      POINT p;
      p.x = x;
      p.y = y;
      HMONITOR hMon = MonitorFromPoint(p, MONITOR_DEFAULTTONULL);
      if (hMon == NULL) {
          // point is off screen
      }
      

      【讨论】:

      • 添加一些描述
      • 我用它作为工作解决方案来检查窗口是否在屏幕外。它比原始答案更简单。
      • 很好,这比枚举所有监视器更简单。要彻底回答原始问题,您应该使用 MonitorFromRect 使用代表标题栏的 RECT。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-16
      • 2010-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-21
      相关资源
      最近更新 更多