Reputation: 7719
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
Upvotes: 35
Views: 29417
Reputation: 123
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;
}
}
}
Upvotes: 2
Reputation: 942257
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;
}
Upvotes: 21
Reputation: 1263
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;
}
Upvotes: -1
Reputation: 109
You can check for visibility of parent control.
protected override void OnParentVisibleChanged(EventArgs e)
{
base.OnParentVisibleChanged(e);
isVisible = true;
}
Upvotes: -1
Reputation: 1876
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
Upvotes: 1
Reputation: 17174
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.
Upvotes: 6
Reputation: 8008
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;
}
Upvotes: 6
Reputation:
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.
Upvotes: 8
Reputation: 52528
If a control is visible the Paint event will be called (repeatedly).
Normally for not visible controls, this event will not be called.
Upvotes: 4