【问题标题】:Win32 detect if window is maximized/docked to half screen (Win-key + Left/Right)Win32 检测窗口是否最大化/停靠到半屏(Win-key + Left/Right)
【发布时间】:2018-03-29 20:51:50
【问题描述】:

我有一个经典的 Win32-API (C++) 应用程序,需要检测窗口是否停靠在屏幕的左/右半部分。

问题的背景是窗口的大小仅以网格为单位,比如说 32 像素。在全屏中,程序会检测到该状态,允许大小与全屏匹配并填充多余的空间。在 Windows 8 及更高版本中,我想做同样的事情,而不是目前留下边框(因为尺寸会捕捉到 32 像素的倍数)。

【问题讨论】:

  • 一个窗口在像这样“停靠”时没有特殊状态。 shell 只是为用户提供了一个将窗口移动到那里的快捷方式,这与用鼠标将窗口移动到那里没有什么不同。使用 GetWindowRect 和 GetMonitorInfo 检测它。

标签: c++ winapi


【解决方案1】:

通过函数GetWindowPlacement(),您可以使用WINDOWPLACEMENT 的成员rcNormalPosition 检索正常的窗口矩形。然后将普通矩形与实际的窗口矩形进行比较。如果它们不匹配,则窗口很可能处于停靠状态。

例子:

bool IsDockedToMonitor(HWND hWnd)
{
    WINDOWPLACEMENT placement = {sizeof(WINDOWPLACEMENT)};
    GetWindowPlacement(hWnd, &placement);
    RECT rc;
    GetWindowRect(hWnd, &rc);

    return placement.showCmd == SW_SHOWNORMAL
        && (rc.left != placement.rcNormalPosition.left ||
            rc.top != placement.rcNormalPosition.top ||
            rc.right != placement.rcNormalPosition.right ||
            rc.bottom != placement.rcNormalPosition.bottom);
}

请注意,此解决方案并非 100% 可靠。即使窗口停靠在监视器的一侧,普通矩形和当前窗口矩形也有可能匹配。

【讨论】:

  • 这可能不可靠,因为 GetWindowPlacement 使用工作区坐标,而 GetWindowRect 使用屏幕坐标(请参阅WINDOWPLACEMENT)。例如,我相信当有一个栏停靠在屏幕的顶部或左侧时,坐标将不匹配。但是,我已经有好几年没有测试过了。
【解决方案2】:

Aero Snap 功能内置在 Shell 中,而不是窗口管理器中。因此,没有指示停靠状态的特定窗口样式或标志。 Shell 只是简单地重新定位窗口以响应某些操作(并在内部记录状态)。这样做的方式与使用鼠标或键盘手动重新定位窗口没有区别。

您无法可靠地确定窗口是停靠在屏幕的左侧还是右侧。 Shell 没有发送特定的消息,窗口的大小和相对于工作区域的位置也不是足够的属性。

你想要完成的事情是不可能的。您将必须实施一个解决方案,该解决方案不需要不可用的信息。一种这样的实现是始终使用填充窗口大小,这不允许使用整个客户区。另一种解决方案是实施相反的方法:允许将窗口大小调整为任意大小,除非您知道用户正在手动调整窗口大小。您可以通过处理WM_SIZING 消息来确定后者。

【讨论】:

    【解决方案3】:

    除了 IInspectable 已经提到的之外,还有另一种方法可以确定此信息并采取相应措施。

    1. 等待WM_WINDOWPOSCHANGED 消息并从存储在lParam 中的WINDOWPOS 指针读取其xycxcy 值。
    2. 通过调用MonitorFromWindow,获取放置窗口的当前监视器的句柄。
    3. 创建一个MONITORINFO 变量并将其cbSize 字段设置为sizeof(MONITORINFO)
    4. 使用监视器句柄和MONITORINFO 变量的地址来调用GetMonitorInfo
    5. MONITORINFO 变量中读取rcWork 值。
      • rcWork.top == WINDOWPOS.y && rcWork.bottom == (WINDOWPOS.y + WINDOWPOS.cx) && rcWork.left == WINDOWPOS.x - 窗口“停靠”在左侧
      • rcWork.top == WINDOWPOS.y && rcWork.bottom == (WINDOWPOS.y + WINDOWPOS.cx) && rcwork.right == (WINDOWPOS.x + WINDOWPOS.cx) - 窗口“停靠”在右侧
      • rcWork.top == WINDOWPOS.y && rcWork.left == WINDOWPOS.x && rcWork.right == (WINDOWPOS.x + WINDOWPOS.cx) - 窗口“停靠”到顶部
      • rcWork.top == (WINDOWPOS.y + WINDOWPOS.cy) && rcWork.left == WINDOWPOS.x && rcWork.right == (WINDOWPOS.x + WINDOWPOS.cx) - 窗口“停靠”到底部

    你说你已经有了判断窗口是否全屏的逻辑(你的意思是全屏还是最大化?),但是如果left == x && top == y && right == x + cx && bottom == y + cy可以确定有效最大化。

    Here is an MSDN example of something similar.

    请注意,缓存MONITORINFO 值可能更理想,因此您无需在每次重新定位窗口时都调用它。


    如果您只希望在用户不手动调整窗口大小时应用此功能,这里是一个人为的示例,可能的方法是:

    LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
    {
      static bool userSizing = false;
    
      switch (msg)
      {
      // could also catch WM_ENTERSIZEMOVE here, but this will trigger on 
      // moves as well as sizes
      case WM_SIZING:
        userSizing = true;
        break;
    
      case WM_EXITSIZEMOVE:
        userSizing = false;
        break;
    
      case WM_WINDOWPOSCHANGED:
        if (userSizing)
        {
          break;
        }
    
        // do logic to check to see if the window is sized in a "docked"
        // manner here
        break;
    
      // handle other window messages ...
    
      }
    }
    

    【讨论】:

    • 此算法无法区分使用 Aero Snap 停靠的窗口和大小和位置恰好相同但由用户手动完成的窗口。处理WM_SIZING 对于区分用户调整窗口大小和系统调整窗口大小至关重要。
    • @IInspectable:你是绝对正确的。我写这个答案的想法是,如果用户将窗口大小调整为精确的“对接”尺寸,或者是否使用 Aero Snap,因为它实际上是相同的,但调整大小的方法实际上对于可用性目的可能很重要。我将对此添加另一个建议。
    • WM_ENTERSIZEMOVE 被发送到窗口,当用户也开始移动操作时,这实际上可能导致停靠窗口。这会导致假阴性。
    • 其实情况更糟。 WM_WINDOWPOSCHANGED 在整个尺寸调整和移动操作中发送。提议的实现仅产生假阴性。我还没有尝试过,但我相信一个可靠的实现会在WM_SIZING 中设置标志并在WM_EXITSIZEMOVE 期间重置它。在调整停靠窗口的非停靠边缘大小时,会出现极端情况。我什至不确定,在这种情况下会发生什么。
    • 我目前无法对其进行测试,但是是的,我忘记了 WM_WINDOWPOSCHANGED 被称为 during 移动/大小 - 这就是 WinForms 处理实时控件布局的方式。问题是我不知道WM_EXITSIZEMOVE 是在WM_WINDOWPOSCHANGED 之前还是之后发送而没有测试它——我不能再做几个小时了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-24
    相关资源
    最近更新 更多