如何确定窗口是否超出屏幕范围?

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

21

Windows很容易确定用户在主显示器上的工作区大小(即屏幕不被任务栏遮挡的区域)。调用SystemParametersInfo函数并指定第一个参数(uiAction)为SPI_GETWORKAREA标志。其中pvParam参数应指向接收虚拟屏幕坐标下工作区坐标的RECT结构。

一旦您获得了描述工作区的坐标,只需将其与您的应用程序窗口的当前位置进行比较,以确定它是否落在这些范围内。


支持多个监视器需要考虑到更多因素。文档建议调用GetMonitorInfo函数获取除主监视器外其他监视器的工作区,该函数会填充名为MONITORINFOEX的结构体,其中包含成员rcWork,该成员定义该监视器的工作区,同样以RECT结构体表示虚拟屏幕坐标。

要做到这一点,您需要枚举用户连接到系统的所有监视器,并使用GetMonitorInfo检索每个监视器的工作区。

在互联网上有一些此类示例:

  • 如果你正在使用MFC,这是一个看起来非常不错的多显示器支持的示例
  • 即使您没有使用MFC,该文章也提到了以下链接,在解释Windows下如何支持多显示器方面,这是一篇真正有价值的文章,尽管它可能有点过时。无论喜欢与否,在后续的Windows版本中,这些内容都很少改变。

  • 最后,您提到想要检测分辨率更改。这比您想象的要简单得多。如果您已经进行了任何Windows编程,那么操作系统与您的应用程序之间的主要通信方式是通过向您的WindowProc函数发送消息。
    在这种情况下,您将需要监视WM_DISPLAYCHANGE消息,该消息会在显示器分辨率更改时发送给所有窗口。 wParam 包含新的图像深度(每像素位数);lParam 的低位字包含屏幕的水平分辨率,高位字则指定垂直分辨率。


    谢谢,这是一个很好的开始 - 我简直不敢相信我以前从未接触过SystemParametersInfo。但我仍然困扰于标题栏是否可见的问题,以及分辨率改变时会发生什么。 - hatcat
    @hatcat: 没有必要自责;Windows API 很大,没有人知道所有的东西。我不确定你如何仍然卡在标题栏可见性上。检查一下包含标题栏的窗口区域是否位于使用上述任一函数获取的屏幕工作区内。而且,我直接忘记了检测分辨率更改;我会更新我的答案。 - Cody Gray
    谢谢!信不信由你,我从1992年开始编写Windows程序。我认为最难的事情是知道哪些功能已经被取代,以及何时以及是否应该坚持自己所知道的东西。标题栏部分更多是为了检索该区域,以便我可以测试它。我只打算检查顶部十个像素和侧面十个像素,那应该足够了。 - hatcat
    @hatcat:啊,我不会硬编码一个10像素的值;这太容易出问题了。特别是当你可以使用GetSystemMetrics函数来确定实际大小时。要获取窗口标题栏的高度(以像素为单位),请在nIndex参数中指定SM_CYCAPTION。要获取窗口边框的宽度(以像素为单位),请指定SM_CXBORDER,要获取窗口边框的高度(以像素为单位),请指定SM_CYBORDER - Cody Gray
    哎呀!是的,我现在记得了。天啊,我的大脑正在萎缩。再次感谢。 - hatcat

    7
    您可以使用 MonitorFromRect 或 MonitorFromPoint 来检查窗口左上角或右下角点是否不包含在任何显示器(屏幕外)中。
    POINT p;
    p.x = x;
    p.y = y;
    HMONITOR hMon = MonitorFromPoint(p, MONITOR_DEFAULTTONULL);
    if (hMon == NULL) {
        // point is off screen
    }
    

    请添加一些描述 - Mathews Sunny
    我使用这个工作解决方案来检查窗口是否超出屏幕。它比原始答案更简单。 - Evgeny
    2
    不错,这比枚举所有监视器要简单。为了彻底回答原始问题,您应该使用使用表示标题栏的RECT的MonitorFromRect。 - Adrian McCarthy

    3

    可见性检查非常简单。

    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。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接