【问题标题】:C# winform check if control is physicaly visibleC# winform 检查控件是否物理可见
【发布时间】:2011-06-12 11:41:16
【问题描述】:

是否可以确定是否可以看到控件的至少一个像素(通过属性或可能使用事件通知)。

注意:我不是在寻找即使其他窗口隐藏控件也可以返回 true 的 Visible 属性

【问题讨论】:

  • 不要以为内置了这样的东西,但您始终可以遍历所有顶级控件并检查它们的顶部、左侧、宽度和高度。

标签: c# winforms controls


【解决方案1】:

一个实用的解决方案是使用窗体的 GetChildAtPoint() 方法,传递控件的 4 个角。如果其中一个返回 true,则该控件肯定是可见的。它不是 100% 可靠的,所有 4 个角都可能被另一个控件重叠,但仍然使部分内部可见。我不会担心那个,太离奇了。

public bool ChildReallyVisible(Control child) {
    var pos = this.PointToClient(child.PointToScreen(Point.Empty));

    //Test the top left
    if (this.GetChildAtPoint(pos) == child) return true;

    //Test the top right
    if (this.GetChildAtPoint(new Point(pos.X + child.Width - 1, pos.Y)) == child) return true;

    //Test the bottom left
    if (this.GetChildAtPoint(new Point(pos.X, pos.Y + child.Height -1)) == child) return true;

    //Test the bottom right
    if (this.GetChildAtPoint(new Point(pos.X + child.Width - 1, pos.Y + child.Height -1)) == child) return true;

    return false;
}

【讨论】:

  • 我建议将“this”替换为“child.parent”,这将使代码更易于移植和维护。此外,您可以将函数设为静态,我相信这非常有用。哦,顺便说一句,您的代码运行良好。谢谢。
  • 一点也不,那会破坏代码。 this 必须是它的工作形式。
  • 很有趣,我已经使用了孩子的父控件,即使孩子的父控件不是表单,它仍然可以工作。但是我目前正在使用 jdv-Jan de Vaan 建议的方式,我认为它更可靠。
  • 确实是务实的解决方案。只是有一个案例,其中一些控件根据用户操作隐藏了其他控件,并且必须弄清楚哪些控件实际上是可见的。此解决方案避免手动复制 Windows 窗体完成的工作。为了实现这一点,我只保留一个此类控件的列表,附加一个运行 Hans 代码的 VisibleChanged 事件,使用此重载跳过不可见的控件:Control.GetChildAtPoint, méthode (Point, GetChildAtPointSkip) (System.Windows.Forms)
  • 非常好的解决方案先生!我正在开发一个 winforms 应用程序,该应用程序将在没有键盘的触摸屏上运行,并且我以重叠模式显示不同的表单。我只希望实际位于顶部(因此可见)的表单进行事件处理。您的 Childreallyvisible 方法正是这样做的!
【解决方案2】:

您可以使控件无效,然后调用GetUpdateRect(Win32 api函数)来查找。不过,它确实有导致重绘的副作用。

【讨论】:

  • 试过了,当被其他控件/窗口重叠时返回true(可见),仅当控件/窗口最小化时返回false。你确定这种方法有效吗?
  • 这是一个棘手的案例。 Windows 仍将绘制控件,以确保您可以 alpha 混合控件。如果您使父控件不可见,或者将控件滚动到其父可见矩形之外,它将起作用,这些都是重要的用例。
  • 感谢您的回复,但是由于与控件重叠的窗口透明度为零并且还完全覆盖了控件,因此不应该发生 Alpha 混合,不明白为什么 GetUpdateRect() 仍然返回 true。
  • 默认情况下,操作系统不会针对这种情况进行优化。但是如果你有兴趣,你可以检查一下父控件上的 ClipSiblings 窗口样式是否有帮助。
【解决方案3】:

受汉斯回答的启发,我以这种方式实现了这种行为;

    [DllImport("user32.dll")]
    static extern IntPtr WindowFromPoint(POINT Point);

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int X;
        public int Y;

        public POINT(int x, int y)
        {
            this.X = x;
            this.Y = y;
        }

        public static implicit operator System.Drawing.Point(POINT p)
        {
            return new System.Drawing.Point(p.X, p.Y);
        }

        public static implicit operator POINT(System.Drawing.Point p)
        {
            return new POINT(p.X, p.Y);
        }
    }

    public static bool IsControlVisibleToUser(this Control control)
    {
        var pos = control.PointToScreen(control.Location);
        var pointsToCheck = new POINT[]
                                {
                                    pos,
                                    new Point(pos.X + control.Width - 1, pos.Y),
                                    new Point(pos.X, pos.Y + control.Height - 1),
                                    new Point(pos.X + control.Width - 1, pos.Y + control.Height - 1),
                                    new Point(pos.X + control.Width/2, pos.Y + control.Height/2)
                                };

        foreach (var p in pointsToCheck)
        {
            var hwnd = WindowFromPoint(p);
            var other = Control.FromChildHandle(hwnd);
            if (other == null)
                continue;

            if (control == other || control.Contains(other))
                return true;
        }

        return false;
    }

【讨论】:

  • 工作完美!
【解决方案4】:

为了方便之前answer回答你的问题。

这里是使用GetUpdateRect 函数所需的源代码,jdv-Jan de Vaan 已回答。

[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
internal struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
    public int Width { get { return this.Right - this.Left; } }
    public int Height { get { return this.Bottom - this.Top; } }
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
internal static extern bool GetUpdateRect(IntPtr hWnd, ref RECT rect, bool bErase);
public static bool IsControlVisibleToUser(Control control)
{
    control.Invalidate();
    Rectangle bounds = control.Bounds;
    RECT rect = new RECT { Left = bounds.Left, Right = bounds.Right, Top = bounds.Top, Bottom = bounds.Bottom };
    return GetUpdateRect(control.Handle, ref rect, false);
}

当您需要检查指定是否可见时,只需执行以下操作:

if (IsControlVisibleToUser(controlName) == true)
{
    // The Specified Control is visible.
    // ... do something 
}
else
{
    // Control is not visible.
    // ... do something else
}

祝你好运。

【讨论】:

    【解决方案5】:

    如果控件可见,则将调用 Paint 事件(重复)。

    通常对于不可见的控件,该事件不会被调用。

    【讨论】:

    • 我会说简单而聪明的解决方案。
    • 好的..但是我应该对疼痛事件进行什么测试? (DateTime.now-lastPaintDt
    • @Toto:取决于你的需要。触发可见性的开始很容易,触发结束并不容易。
    【解决方案6】:

    尝试了上述方法,但即使 winform 被另一个应用程序覆盖,也仍然是正确的。

    最终使用了以下内容(在我的 winform 类中):

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace yourNameSpace
    {
        public class Myform : Form
        {
    
            private void someFuncInvokedByTimerOnMainThread()
            {
                bool isVisible = isControlVisible(this);
                // do something.
            }
    
            [DllImport("user32.dll")]
            static extern IntPtr WindowFromPoint(System.Drawing.Point p);
    
    
            ///<summary><para>------------------------------------------------------------------------------------</para>
            ///
            ///<para>           Returns true if the control is visible on screen, false otherwise.                </para>
            ///
            ///<para>------------------------------------------------------------------------------------</para></summary>
            private bool isControlVisible(Control control)
            {
                bool result = false;
                if (control != null)
                {
                    var pos = control.PointToScreen(System.Drawing.Point.Empty);
                    var handle = WindowFromPoint(new System.Drawing.Point(pos.X + 10, pos.Y + 10)); // +10 to disregard padding   
                    result = (control.Handle == handle); // should be equal if control is visible
                }
                return result;
            }
        }
    }
    

    【讨论】:

      【解决方案7】:

      您可以使用控件的布局事件。 当控件进入屏幕并尝试对其子控件进行布局时触发它。

      例如,假设 TabPage 中有 GroupBox。
      单击相关选项卡时,将为第一个选项卡页触发布局事件,然后为 GroupBox
      您可以将其与可见性属性结合使用

      【讨论】:

        【解决方案8】:

        您可以检查父控件的可见性。

            protected override void OnParentVisibleChanged(EventArgs e)
            {
                base.OnParentVisibleChanged(e);
                isVisible = true;
            }
        

        【讨论】:

          【解决方案9】:

          我有点完成了 Hans Passant 的答案。下面的函数测试表单的所有四个角。

                  /// <summary>
              /// determines if a form is on top and really visible.
              /// a problem you ran into is that form.invalidate returns true, even if another form is on top of it. 
              /// this function avoids that situation
              /// code and discussion:
              /// https://stackoverflow.com/questions/4747935/c-sharp-winform-check-if-control-is-physicaly-visible
              /// </summary>
              /// <param name="child"></param>
              /// <returns></returns>
              public bool ChildReallyVisible(Control child)
              {
                  bool result = false;
          
                  var pos = this.PointToClient(child.PointToScreen(Point.Empty));
          
                  result = this.GetChildAtPoint(pos) == child;
                  //this 'if's cause the condition only to be checked if the result is true, otherwise it will stay false to the end
                  if(result)
                  {
                      result = (this.GetChildAtPoint(new Point(pos.X + child.Width - 1, pos.Y)) == child);
                  }
                  if(result)
                  {
                      result = (this.GetChildAtPoint(new Point(pos.X, pos.Y + child.Height - 1)) == child);
                  }
                  if(result)
                  {
                      result = (this.GetChildAtPoint(new Point(pos.X + child.Width - 1, pos.Y + child.Height - 1)) == child) ;
                  }
                  return result;
              }
          

          【讨论】:

          • 这与韩的回答相同,但更糟。唯一的区别是您不使用 return 而是使用 if 语句和结果变量。
          猜你喜欢
          • 1970-01-01
          • 2011-03-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多