如何在Win32控件/窗口中查找相对于其父窗口的位置?

31

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

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

我该怎么办?

5个回答

48

解决方案是结合使用GetWindowRect()MapWindowPoints()的能力。

GetWindowRect()检索窗口相对于您在监视器上看到的整个屏幕区域的坐标。我们需要将这些绝对坐标转换为我们主窗口区域的相对坐标。 MapWindowPoints()将给定相对于一个窗口的坐标转换为相对于另一个窗口的坐标。因此,我们需要屏幕区域的“句柄”以及正在尝试查找其坐标的控件的父窗口的句柄。在Windows术语中,屏幕区域是一个“窗口”,称为“桌面”。我们可以通过常量HWND_DESKTOP(包括Windows.h)中定义的方式访问Desktop的句柄。我们可以通过调用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) 相对于其父窗口的相对坐标。


2
今天我花了相当多的时间来寻找解决这个问题的方法。在互联网上甚至在这个网站上,有很多错误和误导性的信息。Win32没有提供一个明确的函数来收集这些信息,这是一个巨大的悲剧,而且很难找到一个好的解释和示例代码来解决这个简单的问题。我在这里分享解决方案,帮助其他将来搜索它的用户。 - hkBattousai
1
@Xearinox 我知道我在这里挖掘老坟,但是... 是的。在多个显示器上,根据哪个是主显示器,该方法将返回高度负面或高度正面的值来表示相对于主显示器的坐标。使用 SetCursorPos 自己尝试一下,并查看鼠标指针落在哪里。 - RectangleEquals
如果您在一个子类化了CWnd(如CDialog)的类中使用它,您可能想在系统函数调用前加上::前缀,例如::MapWindowPoints(...) - Gunther Struyf
不幸的是,如果父窗口被最小化,这将无法工作。 - Jonathan Potter

16

这就是我在Windows或控件(子窗口)上使用的解决方案。

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

应该是 MapWindowPoints(hWnd,GetParent(hWnd),(LPPOINT)&rc,1);。 - Erdinc Ay
为什么会这样,@Erdinc?一个矩形中有两个点。 - Rob Kennedy
2
确实有一个rect,@Erdinc。而且里面有两个点。MapWindowPoints的最后一个参数需要映射的数。事实上,如果你不传递2,对于镜像(从右到左)的窗口,该函数可能无法按照你的预期工作。请参阅文档中的备注 - Rob Kennedy
谢谢!这个很好用,即使在多个显示器上也是如此。我认为这应该成为被采纳的答案。 - dbeachy1

4
我知道这个问题之前已经有答案,但更容易的方法是获取子窗口在屏幕坐标系下的矩形区域,获取它的位置 (POINT ptCWPos = {rectCW.left, rectCW.top};) 并使用 ScreenToClient() 函数,将屏幕坐标点转换为窗口客户端坐标点:
PS:我知道这看起来像更多的代码,但大部分是在操作矩形位置;在大多数情况下,实际上只需要矩形位置而不是整个矩形。
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 中的备注。 - user541686

0
这是一个基于上述答案的非常基本的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扩展到一系列控件上,对应的rectOut的存储可以考虑更适合的结构。
用法:以下代码返回转换后的矩形:

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

以下是部分编码的函数,将两个答案的元素转换为可用于对话框的内容 - 特别是用于移动/调整大小控件坐标,这也是着陆在此页面的原因。
它接受来自资源或CreateWindow中的HMENU项的整数控件ID作为参数,并接受其容器的句柄。
在调用该函数之前,应考虑ownerHwnd是否被最小化,通过侦听WM_SIZE中的SIZE_MINIMIZED来判断。
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;
}

0

更简单

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);

}

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