C# 获取窗体控件的位置

72

有没有办法在表单中检索控件的位置,即使该控件可能在其他控件(如面板)内部?

控件的Left和Top属性仅给出它在父控件内的位置,但如果我的控件位于五个嵌套的面板中,我需要它在表单上的位置怎么办?

举个例子:

按钮btnA位于面板pnlB内的坐标为(10,10)。
面板pnlB位于窗体frmC内的坐标为(15,15)。

我想要btnA在frmC上的位置,即(25,25)。

我能获取这个位置吗?

9个回答

99

我通常使用PointToScreenPointToClient相结合:

Point locationOnForm = control.FindForm().PointToClient(
    control.Parent.PointToScreen(control.Location));

9
这是“真正的绝对位置”https://dev59.com/72445IYBdhLWcg3wLXOh。 - PUG
4
这与control.PointToScreen(Point.Empty);有什么不同? - strongriley
1
@strongriley 我不知道(从未尝试过,现在也没有开发环境),但我在文档中没有看到提到这种行为,如果有的话,也不能保证它将在未来的框架版本中正常工作。 - Fredrik Mörk
5
@strongriley control.PointToScreen(Point.Empty) 返回相对于屏幕的位置,而答案返回相对于顶级窗体的位置。 - nawfal
嘿,你帮了我很多:)它将设计好的弹出窗口居中显示在程序显示器的中心,而不是所有屏幕的中心! - kokbira
评论中似乎有些混淆。为了帮助未来的读者,我想说:control.Parent.PointToScreen(control.Location)control.PointToScreen(Point.Empty) 是一样的;它们都返回控件相对于屏幕的位置(我更喜欢第二个因为更短)。然后,我们把这个值传递给 Form.PointToClient() 以获取相对于窗体的位置。 - 41686d6564 stands w. Palestine

11

我通常这样做...每次都有效...

var loc = ctrl.PointToScreen(Point.Empty);

11

您可以使用控件的 PointToScreen 方法来获取相对于屏幕的绝对位置。

您可以使用窗体的 PointToScreen 方法,并结合基本数学知识,获取控件的位置。


那样做不会完全正确,除非您考虑到标题栏的高度和表单边框的宽度。 - MusiGenesis
这如何帮助获取控件的绝对位置?你必须传递一个Point对象到PointToScreen(),但在这种情况下这没有意义。 - Tim
@Tim,在这种情况下,你必须传递System.Drawing.Point.Empty:var absolutePosition = control.PointToScreen(System.Drawing.Point.Empty); - Umar T.

7
您可以沿着父级向上走,注意它们在其父级中的位置,直到到达表单。
编辑:类似以下内容(未经测试):
public Point GetPositionInForm(Control ctrl)
{
   Point p = ctrl.Location;
   Control parent = ctrl.Parent;
   while (! (parent is Form))
   {
      p.Offset(parent.Location.X, parent.Location.Y);
      parent = parent.Parent;
   }
   return p;
}

是的,我考虑过那种方法,但似乎不太实用,所以我希望有另一种方法。如果没有其他建议,那就只能这样做了。 - Erlend D.
这是使用简单的递归函数完成的方法。 - MusiGenesis

5

Supergeek,你的非递归函数没有产生正确的结果,但我的函数做到了。我相信你的函数进行了多余的加法运算。

private Point LocationOnClient(Control c)
{
   Point retval = new Point(0, 0);
   for (; c.Parent != null; c = c.Parent)
   { retval.Offset(c.Location); }
   return retval;
}

3

奇怪的是,PointToClient和PointToScreen对于我的情况并不理想。特别是因为我正在处理与任何表单都没有关联的离屏控件。我发现sahin的解决方案最有帮助,但去掉了递归并消除了表单终止。下面的解决方案适用于我所有可见或不可见的控件,无论是否包含在表单中或容器中。谢谢Sahim。

private static Point FindLocation(Control ctrl)
{
    Point p;
    for (p = ctrl.Location; ctrl.Parent != null; ctrl = ctrl.Parent)
        p.Offset(ctrl.Parent.Location);
    return p;
}

2
private Point FindLocation(Control ctrl)
{
    if (ctrl.Parent is Form)
        return ctrl.Location;
    else
    {
        Point p = FindLocation(ctrl.Parent);
        p.X += ctrl.Location.X;
        p.Y += ctrl.Location.Y;
        return p;
    }
}

1
在我的测试中,Hans Kesting和Fredrik Mörk的解决方案给出了相同的答案。但是:
我发现在使用Raj More和Hans Kesting的方法时有一个有趣的差异,并且想分享一下。感谢两位的帮助,我简直不敢相信这样的方法没有内置到框架中。
请注意,Raj没有编写代码,因此我的实现可能与他的意思不同。
我发现的差异是,Raj More的方法在X和Y方向上通常会比Hans Kesting的方法多两个像素。我还没有确定为什么会出现这种情况。我相当确定这与Windows窗体的内容(即窗体最外层边框内部)周围似乎有一个两个像素的边框有关。在我的测试中,并没有详尽地涉及所有情况,我只在嵌套的控件上遇到过这种情况。然而,并非所有嵌套的控件都会出现这种情况。例如,我在一个GroupBox中有一个TextBox,它显示出差异,但是在同一个GroupBox中的一个Button却没有。我无法解释为什么会这样。
请注意,当答案等效时,它们会将点(0,0)视为我上面提到的内容边框的内部。因此,我认为Hans Kesting和Fredrik Mörk的解决方案是正确的,但我不认为我会信任Raj More所实现的解决方案。
我也想知道Raj More会写什么代码,因为他提出了一个想法,但没有提供代码。在阅读这篇帖子之前,我并没有完全理解PointToScreen()方法:http://social.msdn.microsoft.com/Forums/en-US/netfxcompact/thread/aa91d4d8-e106-48d1-8e8a-59579e14f495 这是我的测试方法。请注意,评论中提到的“方法1”与Hans Kesting的略有不同。
private Point GetLocationRelativeToForm(Control c)
{
  // Method 1: walk up the control tree
  Point controlLocationRelativeToForm1 = new Point();
  Control currentControl = c;
  while (currentControl.Parent != null)
  {
    controlLocationRelativeToForm1.Offset(currentControl.Left, currentControl.Top);
    currentControl = currentControl.Parent;
  }

  // Method 2: determine absolute position on screen of control and form, and calculate difference
  Point controlScreenPoint = c.PointToScreen(Point.Empty);
  Point formScreenPoint = PointToScreen(Point.Empty);
  Point controlLocationRelativeToForm2 = controlScreenPoint - new Size(formScreenPoint);

  // Method 3: combine PointToScreen() and PointToClient()
  Point locationOnForm = c.FindForm().PointToClient(c.Parent.PointToScreen(c.Location));

  // Theoretically they should be the same
  Debug.Assert(controlLocationRelativeToForm1 == controlLocationRelativeToForm2);
  Debug.Assert(locationOnForm == controlLocationRelativeToForm1);
  Debug.Assert(locationOnForm == controlLocationRelativeToForm2);

  return controlLocationRelativeToForm1;
}

0

这就是我所做的,非常完美。

            private static int _x=0, _y=0;
        private static Point _point;
        public static Point LocationInForm(Control c)
        {
            if (c.Parent == null) 
            {
                _x += c.Location.X;
                _y += c.Location.Y;
                _point = new Point(_x, _y);
                _x = 0; _y = 0;
                return _point;
            }
            else if ((c.Parent is System.Windows.Forms.Form))
            {
                _point = new Point(_x, _y);
                _x = 0; _y = 0;
                return _point;
            }
            else 
            {
                _x += c.Location.X;
                _y += c.Location.Y;
                LocationInForm(c.Parent);
            }
            return new Point(1,1);
        }

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