C# winform 检查控件是否物理可见

发布于 2024-10-13 00:30:50 字数 97 浏览 4 评论 0原文

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

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

Is it possible to determine if at least one pixel of a control can be seen (by a property or maybe using event notification).

NB : I am not looking for the Visible property that can return true even if an other window hides the control

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

我们只是彼此的过ke 2024-10-20 00:30:50

一个实用的解决方案是使用表单的 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;
}

A pragmatic solution is to use the form's GetChildAtPoint() method, passing the 4 corners of the control. If one of them returns true then the control is definitely visible. It is not 100% reliable, all 4 corners could be overlapped by another control but still leave part of interior visible. I would not worry about that, too bizarre.

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;
}
放低过去 2024-10-20 00:30:50

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

You can invalidate the control and then call GetUpdateRect (Win32 api function) to find this out. It does have the side effect of causing a repaint, though.

放肆 2024-10-20 00:30:50

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

    [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;
    }

Inspired by Hans's answer I've implemented this behavior in this way;

    [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;
    }
左岸枫 2024-10-20 00:30:50

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

以下是使用 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
}

祝您好运。

In order to facilitate a previous answer to your question.

Here is the source code that you will need to work with the GetUpdateRect function as jdv-Jan de Vaan answered.

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

When you need to check if a specified is visible just do something like the following:

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

Good luck.

要走就滚别墨迹 2024-10-20 00:30:50

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

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

If a control is visible the Paint event will be called (repeatedly).

Normally for not visible controls, this event will not be called.

五里雾 2024-10-20 00:30:50

尝试了上面的方法,但即使 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;
        }
    }
}

Tried the above but kept getting true even if the winform was covered by another app.

Ended up using the following (inside my winform class):

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;
        }
    }
}
甜宝宝 2024-10-20 00:30:50

您可以使用控件的布局事件。
当控件到达屏幕并尝试布局其子控件时会触发它。

例如,假设 TabPage 中有 GroupBox。

单击相关选项卡时,第一个选项卡页将触发布局事件,然后是 GroupBox

您可以将它与可见性属性结合使用

You may use Layout event of controls.
it is triggered when control comes to screen and tries to layout its child controls.

For example, let's say there is GroupBox inside a TabPage.

When relevant tab clicked, layout event will fire for first tabpage then for GroupBox

You may use it combined with visibility property

灯下孤影 2024-10-20 00:30:50

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

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

You can check for visibility of parent control.

    protected override void OnParentVisibleChanged(EventArgs e)
    {
        base.OnParentVisibleChanged(e);
        isVisible = true;
    }
稍尽春風 2024-10-20 00:30:50

我稍微完成了汉斯·帕桑特的回答。下面的函数测试表单的所有四个角。

        /// <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;
    }

I somewhat finished the answer by Hans Passant. The function below tests for all four corners of the form.

        /// <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;
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文