【问题标题】:How do I find position of a Win32 control/window relative to its parent window?如何找到 Win32 控件/窗口相对于其父窗口的位置?
【发布时间】:2013-08-04 18:59:40
【问题描述】:

给定一个 Win32 窗口的句柄,我需要找到它相对于其父窗口的位置。

我知道几个函数(例如;GetWindowRect()GetClientRect()),但它们都没有明确返回所需的坐标。

我该怎么做?

【问题讨论】:

    标签: winapi position window win32gui


    【解决方案1】:

    解决方案是使用GetWindowRect()MapWindowPoints() 的组合力量。

    GetWindowRect() 检索窗口相对于您在监视器上看到的整个屏幕区域的坐标。我们需要将这些绝对坐标转换为我们主窗口区域的相对坐标。 MapWindowPoints() 将给定的相对于一个窗口的坐标转换为相对于另一个窗口的坐标。所以我们需要一个屏幕区域的“句柄”和我们试图找到坐标的控件的父窗口的句柄。屏幕是 Windows 术语中的“窗口”,称为“桌面”。我们可以通过WinUser.h中定义的常量HWND_DESKTOP访问Desktop的句柄(包括Windows.h就够了)。我们可以通过调用Win32函数GetParent()来获取我们父窗口的句柄。现在我们有了调用MapWindowPoints()函数所需的所有参数。

    RECT YourClass::GetLocalCoordinates(HWND hWnd) const
    {
        RECT Rect;
        GetWindowRect(hWnd, &Rect);
        MapWindowPoints(HWND_DESKTOP, GetParent(hWnd), (LPPOINT) &Rect, 2);
        return Rect;
    }
    

    MapWindowPoints() 定义为:

    int MapWindowPoints(
      _In_     HWND hWndFrom,
      _In_     HWND hWndTo,
      _Inout_  LPPOINT lpPoints,
      _In_     UINT cPoints
    );
    

    MapWindowPoints() 将坐标从hWndFrom 相对转换为hWndTo。在我们的例子中,我们进行从桌面 (HWND_DESKTOP) 到父窗口 (GetParent(hWnd)) 的转换。因此,生成的 RECT 结构保存了我们的子窗口 (hWnd) 相对于其父窗口的相对坐标。

    【讨论】:

    • 今天我花了相当多的时间来解决这个问题。互联网上甚至本网站上都有很多关于此事的错误和误导性信息。 Win32 没有给出一个明确的函数来收集这些信息是一个很大的悲剧,而且很难找到一个很好的解释和示例代码来解决这个简单的问题。我在这里分享解决方案,以帮助其他将在未来搜索它的用户。
    • @Xearinox 我意识到我在这里复活了死者,但是......是的。在多个监视器上,根据哪个是主显示器,该方法将返回高度负值或高度正值以表示相对于主显示器的坐标。使用SetCursorPos 自己尝试一下,看看鼠标光标落在哪里。
    • 如果你在一个类中使用它,它是CWnd 的子类(比如CDialog),你可能需要在系统函数调用前加上:: 前缀,比如::MapWindowPoints(...)
    • 不幸的是,如果父窗口被最小化,这将不起作用。
    【解决方案2】:

    这是我用于窗口或控件(子窗口)的解决方案

    RECT rc;
    GetClientRect(hWnd,&rc);
    MapWindowPoints(hWnd,GetParent(hWnd),(LPPOINT)&rc,2);
    

    【讨论】:

    • 应该是 MapWindowPoints(hWnd,GetParent(hWnd),(LPPOINT)&rc,1);
    • 为什么会这样,@Erdinc?一个矩形中有 两个 点。
    • 确实有一个矩形,@Erdinc。其中有两点。 MapWindowPoints 的最后一个参数是要映射的 points 的数量。事实上,如果你不通过 2,那么在镜像(从右到左)窗口的情况下,该函数可能不会达到你期望的效果。请参阅remarks in the documentation
    • 谢谢!这很好用,即使在多台显示器上也是如此。 IMO 这应该是公认的答案。
    【解决方案3】:

    这是一个基于上述答案的非常基本的 typedef 结构:

    typedef struct tagRectCl {
        static RECT rectOut;
        RECT RectCl(int ctrlID, HWND &hwndCtrl, HWND &ownerHwnd)
        {
        RECT rectIn;
        GetWindowRect(hwndCtrl, &rectIn); //get window rect of control relative to screen
        MapWindowPoints(NULL, ownerHwnd, (LPPOINT)&rectIn, 2);
        rectOut = rectIn;
        return rectOut;
        }
        RECT RectCl(int ctrlID)
        {
           // for rectOut already populated
           return rectOut;
        }
        };
    }RectCl;
    
    RECT RectCl::rectOut = {};
    

    这个想法是将 ctrlID 扩展到一系列控件,其中相应的rectOuts 的存储可以考虑用于更适应的结构。
    用法:以下返回转换后的rect:

    RECT rect1 = RectCl().RectCl(IDC_CTRL1, hWndCtrl, hWndOwner);
    

    以下是一个部分编码的函数,它将两个答案的元素转化为可用于对话框的东西 - 特别是对于移动/调整控制坐标,这是登陆此页面的最初原因。
    它接受来自资源的完整控件 ID 或来自 CreateWindowHMENU 项及其容器的句柄作为参数。
    还应该考虑在调用函数之前是否通过在WM_SIZE 中侦听SIZE_MINIMIZED 来最小化ownerHwnd

    BOOL ProcCtrl(int ctrlID, HWND ownerHwnd)
    {
        RECT rcClient = {0};        
        HWND hwndCtrl = GetDlgItem(ownerHwnd, ctrlID);
        if (hwndCtrl)
        {
        GetWindowRect(hwndCtrl, &rcClient); //get window rect of control relative to screen
        MapWindowPoints(NULL, ownerHwnd, (LPPOINT)&rcClient,2);
    
        /* Set extra scaling parameters here to suit in either of the following functions
        if (!MoveWindow(hwndCtrl, rcClient.left, rcClient.top, 
        rcClient.right-rcClient.left, rcClient.bottom-rcClient.top, TRUE))
        {
            //Error;
            return FALSE;
        }
        //if (!SetWindowPos(hwndCtrl, NULL, (int)rcClient.left, (int)(rcClient.top),
        //(int)(rcClient.right - rcClient.left), (int)(rcClient.bottom - rcClient.top), SWP_NOZORDER))
        {
            //Error;
            //return FALSE;
        }
        }
        else
        {
            //hwndCtrl Error;
            return FALSE;
        }
        return TRUE;
    }
    

    【讨论】:

      【解决方案4】:

      我知道之前已经回答过了,但是在屏幕坐标中获取子窗口的矩形、获取它的位置 (POINT ptCWPos = {rectCW.left, rectCW.top};) 并使用 ScreenToClient() 函数要容易得多,这会将屏幕坐标点转换为窗口的客户端坐标点:

      PS:我知道这看起来有很多代码,但大部分都在摆弄 rect 位置;在大多数情况下,实际上需要的是 rect 位置而不是整个 rect。


      HWND hwndCW, hwndPW; // the child window hwnd
                           // and the parent window hwnd
      RECT rectCW;
      GetWindowRect(hwndCW, &rectCW); // child window rect in screen coordinates
      POINT ptCWPos = { rectCW.left, rectCW.top };
      ScreenToClient(hwndPW, &ptCWPos); // transforming the child window pos
                                        // from screen space to parent window space
      LONG iWidth, iHeight;
      iWidth = rectCW.right - rectCW.left;
      iHeight = rectCW.bottom - rectCW.top;
      
      rectCW.left = ptCWPos.x;
      rectCW.top = ptCWPos.y;
      rectCW.right = rectCW.left + iWidth;
      rectCW.bottom = rectCW.right + iHeight; // child window rect in parent window space
      
      

      【讨论】:

      • -1 因为这在镜像窗口上是错误的;见MapWindowPoints上的备注。
      【解决方案5】:

      更简单

      BOOL CAuxFormView::OnInitDialog()
      {
          // run the default
          CDialog::OnInitDialog();
          // define a rectangular
          CRect rc1;
          // get area of the control to the screen coordinates
          m_Viewer_Ctrl.GetWindowRect(rc1);
          // transform the screen coordinates relevant to the Dialog coordinates
          ScreenToClient(rc1);
          // move and refresh the control to the new area
          m_Viewer_Ctrl.MoveWindow(rc1);
      
      }
      

      【讨论】:

        猜你喜欢
        • 2015-01-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-10
        • 1970-01-01
        • 2017-09-06
        • 1970-01-01
        • 2012-06-24
        相关资源
        最近更新 更多