34

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

Toto
  • 7,491
  • 18
  • 50
  • 72

9 Answers9

19

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;
}
Arvo Bowen
  • 4,524
  • 6
  • 51
  • 109
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    I would suggest replacing "this" with "child.parent", it would make the code more portable and easier to maintain. Besides, then you could make the function static, which is quite useful I believe. Oh by the way, your code works pretty well. Thanks. – Fábio Antunes May 31 '12 at 01:23
  • 2
    Not at all, that would ruin the code. *this* must be the form for it to work. – Hans Passant May 31 '12 at 01:29
  • Well interesting enough, I've used with the parent control of the child, and even when the parent of the child isn't the form it still worked. However I'm currently using the way jdv-Jan de Vaan suggested, which I believe to be more reliable. – Fábio Antunes May 31 '12 at 02:07
  • Pragmatic solution indeed. Just had a case where some controls were hiding others depending on user action, and had to figure out which ones were actually visible. This solution avoids to manually duplicate the job done by Windows Forms. To implement that I just keep a list of such controls, attach a VisibleChanged event that run Hans' code on all, skipping invisible controls using this overload: [Control.GetChildAtPoint, méthode (Point, GetChildAtPointSkip) (System.Windows.Forms)](http://msdn.microsoft.com/fr-fr/library/ms158404(v=vs.110).aspx). – Stéphane Gourichon Dec 12 '14 at 21:51
  • very nice solution sir! I am working on a winforms application that will run on a touching screen that has NO keybord and i display different forms in overlapping mode. I only want the form that is actually on top (hence visible) to have events processing. your Childreallyvisible method does exactly that! – real_yggdrasil Jun 21 '17 at 11:51
  • 1
    In this answer (https://stackoverflow.com/a/7509036/10310053) it is statet that GetChildAtPoint only looks down one level. Why is it then bad to use child.Parent ? – Eric Pitz Aug 11 '21 at 12:47
7

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.

  • 1
    Tried this out, returns true(visible) when overlapped by other controls/windows, returns false only when control/window is minimized. Are you sure this approach works? – Nemo Aug 09 '12 at 18:52
  • 1
    That is a tough case. Windows will still draw the contol, to ensure you can alpha blend controls. It will work if you make a parent control invisible, or scroll the control outside its parents visible rectangle, which are important use cases. –  Aug 11 '12 at 21:43
  • Thanks for the reply, but Alpha blend shouldn't happen as the window overlapping the control has zero transparency and also completely covers the control, don't understand why the GetUpdateRect() still returns true. – Nemo Aug 15 '12 at 21:04
  • It boils down to that by default, the OS does not optimize for this scenario. But if you're interested, you could check if the ClipSiblings windowstyle on the parent control helps. –  Aug 16 '12 at 07:06
6

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.

Community
  • 1
  • 1
Fábio Antunes
  • 16,984
  • 18
  • 75
  • 96
6

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;
    }
Sebastian Piu
  • 7,838
  • 1
  • 32
  • 50
4

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

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

GvS
  • 52,015
  • 16
  • 101
  • 139
2

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;
        }
    }
}
Shai Volvovsky
  • 123
  • 1
  • 10
1

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

Add080bbA
  • 1,818
  • 1
  • 18
  • 25
-1

You can check for visibility of parent control.

    protected override void OnParentVisibleChanged(EventArgs e)
    {
        base.OnParentVisibleChanged(e);
        isVisible = true;
    }
Track
  • 109
  • 1
  • 4
-1

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;
    }
real_yggdrasil
  • 1,213
  • 4
  • 14
  • 27
  • This is the same as Han's answer but worse. The only difference is that you do not use return but if-statements and a result variable instead. – CSharpie Jul 10 '19 at 05:42