C++ Win32检测窗口是否最大化/停靠到半屏幕(Win键左/右)

C++ Win32检测窗口是否最大化/停靠到半屏幕(Win键左/右),c++,winapi,C++,Winapi,我有一个经典的Win32 API(C++)应用程序,需要检测窗口是否停靠在屏幕的左/右半部分 问题的背景是,窗口的大小仅以网格步长为单位,比如说32像素。在全屏模式下,程序检测到该状态,允许大小与全屏匹配,并填充多余的空间。在Windows 8和更高版本中,我也希望这样做,而不是当前保留边框(因为大小捕捉到32像素的倍数)。Aero捕捉功能内置于外壳中,而不是窗口管理器。因此,没有特定的窗口样式或标志指示停靠状态。Shell只是根据某些操作重新定位窗口(并在内部记录状态)。这样做的方式与用鼠标或

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

问题的背景是,窗口的大小仅以网格步长为单位,比如说32像素。在全屏模式下,程序检测到该状态,允许大小与全屏匹配,并填充多余的空间。在Windows 8和更高版本中,我也希望这样做,而不是当前保留边框(因为大小捕捉到32像素的倍数)。

Aero捕捉功能内置于外壳中,而不是窗口管理器。因此,没有特定的窗口样式或标志指示停靠状态。Shell只是根据某些操作重新定位窗口(并在内部记录状态)。这样做的方式与用鼠标或键盘手动重新定位窗口是无法区分的

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


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

除了IInspectable已经提到的内容之外,还有另一种方法来确定此信息并相应地采取行动

  • 等待消息并从存储在
    lParam
    中的指针读取其
    x
    y
    cx
    cy
  • 通过调用获取放置窗口的当前监视器的句柄
  • 创建一个变量并将其
    cbSize
    字段设置为
    sizeof(MONITORINFO)
  • 使用监视器句柄和
    MONITORINFO
    变量的地址来调用
  • 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
    ,则可以确定有效的最大化

    请注意,可能更需要缓存
    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 ...
    
      }
    }
    

    使用函数
    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%可靠。即使窗口停靠在显示器侧面,普通矩形和当前窗口矩形也很可能匹配。

    窗口在这样“停靠”时没有特殊状态。shell只是为用户提供了一个快捷方式,可以将窗口移动到那里,这与使用鼠标将窗口移动到那里没有什么不同。使用GetWindowRect和GetMonitorInfo检测它。此算法无法区分使用Aero Snap停靠的窗口和大小和位置相同但由用户手动完成的窗口。处理
    WM_size
    是区分用户调整窗口大小和系统调整窗口大小的关键。@IInspectable:您完全正确。我写这个答案的想法是,如果用户将窗口调整到精确的“停靠”尺寸,或者使用Aero Snap,因为它实际上是相同的,那么这并不重要,但是调整大小的方法实际上可能会影响可用性。关于这一点,我将添加另一个建议。
    WM_ENTERSIZEMOVE
    会在用户启动移动操作时发送到窗口,这可能会导致停靠窗口。这将导致假阴性。事实上,情况更糟
    WM_WINDOWPOSCHANGED
    在整个尺寸调整和移动操作中发送。建议的实现只产生假阴性。我没有尝试过,但我相信可靠的实现会在
    WM\u size
    中设置标志,并在
    WM\u EXITSIZEMOVE
    期间重置它。在调整停靠窗口的未停靠边的大小时,会出现角点情况。我甚至不确定,在这种情况下会发生什么。我目前还不能测试它,但是是的,我忘记了在移动/大小期间调用
    WM\u WINDOWPOSCHANGED
    ——这就是WinForms处理实时控件布局的方式。问题是,我不知道
    WM_EXITSIZEMOVE
    是在
    WM_WINDOWPOSCHANGED
    之前还是之后发送的,没有testi