In WPF, how can I determine whether a control is visible to the user? In WPF, how can I determine whether a control is visible to the user? wpf wpf

In WPF, how can I determine whether a control is visible to the user?


You can use this little helper function I just wrote that will check if an element is visible for the user, in a given container. The function returns true if the element is partly visible. If you want to check if it's fully visible, replace the last line by rect.Contains(bounds).

private bool IsUserVisible(FrameworkElement element, FrameworkElement container){    if (!element.IsVisible)        return false;    Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));    Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);    return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);}

In your case, element will be your user control, and container your Window.


public static bool IsUserVisible(this UIElement element){    if (!element.IsVisible)        return false;    var container = VisualTreeHelper.GetParent(element) as FrameworkElement;    if (container == null) throw new ArgumentNullException("container");    Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.RenderSize.Width, element.RenderSize.Height));    Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);    return rect.IntersectsWith(bounds);}


The accepted answer (and the other answers on this page) solve the specific problem that the original poster had but they don't give an adequate answer to the question written in the title, i.e., How to determine if a control is visible to the user. The problem is that A control that is covered by other controls is not visible even though it can be rendered and it is within the borders of its container which is what the other answers are solving for.

To determine whether a a control is visible to the user you sometimes have to be able to determine whether a WPF UIElement is Clickable (or mouse reachable on a PC) by the user

I encountered this problem when I was trying to check if a button can be mouse-clicked by the user. A special case scenario which bugged me was that a button can be actually visible to the user but covered with some transparent (or semi transparent or non transparent at all) layer that prevent mouse clicks. In such case a control might be visible to the user but not accessible to the user which is kind of like it is not visible at all.

So I had to come up with my own solution.

EDIT - My original post had a different solution that used InputHitTest method. However it didn't work in many cases and I had to redesign it. This solution is much more robust and seems to be working very well without any false negatives or positives.

Solution:

  1. Obtain object absolute position relative to the Application Main Window
  2. Call VisualTreeHelper.HitTest on all its corners (Top left, bottom left, top right, bottom right)
  3. We call an object Fully Clickable if the object obtained from VisualTreeHelper.HitTest equal the original object or a visual parent of it for all it's corners, and Partially Clickable for one or more corners.

Please note #1: The definition here of Fully Clickable or Partially Clickable are not exact - we are just checking all four corners of an object are clickable. If, for example, a button has 4 clickable corners but it's center has a spot which is not clickable, we will still regard it as Fully Clickable. To check all points in a given object would be too wasteful.

Please note #2: it is sometimes required to set an object IsHitTestVisible property to true (however, this is the default value for many common controls) if we wish VisualTreeHelper.HitTest to find it

    private bool isElementClickable<T>(UIElement container, UIElement element, out bool isPartiallyClickable)    {        isPartiallyClickable = false;        Rect pos = GetAbsolutePlacement((FrameworkElement)container, (FrameworkElement)element);        bool isTopLeftClickable = GetIsPointClickable<T>(container, element, new Point(pos.TopLeft.X + 1,pos.TopLeft.Y+1));        bool isBottomLeftClickable = GetIsPointClickable<T>(container, element, new Point(pos.BottomLeft.X + 1, pos.BottomLeft.Y - 1));        bool isTopRightClickable = GetIsPointClickable<T>(container, element, new Point(pos.TopRight.X - 1, pos.TopRight.Y + 1));        bool isBottomRightClickable = GetIsPointClickable<T>(container, element, new Point(pos.BottomRight.X - 1, pos.BottomRight.Y - 1));        if (isTopLeftClickable || isBottomLeftClickable || isTopRightClickable || isBottomRightClickable)        {            isPartiallyClickable = true;        }        return isTopLeftClickable && isBottomLeftClickable && isTopRightClickable && isBottomRightClickable; // return if element is fully clickable    }    private bool GetIsPointClickable<T>(UIElement container, UIElement element, Point p)     {        DependencyObject hitTestResult = HitTest< T>(p, container);        if (null != hitTestResult)        {            return isElementChildOfElement(element, hitTestResult);        }        return false;    }                   private DependencyObject HitTest<T>(Point p, UIElement container)    {                               PointHitTestParameters parameter = new PointHitTestParameters(p);        DependencyObject hitTestResult = null;        HitTestResultCallback resultCallback = (result) =>        {           UIElement elemCandidateResult = result.VisualHit as UIElement;            // result can be collapsed! Even though documentation indicates otherwise            if (null != elemCandidateResult && elemCandidateResult.Visibility == Visibility.Visible)             {                hitTestResult = result.VisualHit;                return HitTestResultBehavior.Stop;            }            return HitTestResultBehavior.Continue;        };        HitTestFilterCallback filterCallBack = (potentialHitTestTarget) =>        {            if (potentialHitTestTarget is T)            {                hitTestResult = potentialHitTestTarget;                return HitTestFilterBehavior.Stop;            }            return HitTestFilterBehavior.Continue;        };        VisualTreeHelper.HitTest(container, filterCallBack, resultCallback, parameter);        return hitTestResult;    }             private bool isElementChildOfElement(DependencyObject child, DependencyObject parent)    {        if (child.GetHashCode() == parent.GetHashCode())            return true;        IEnumerable<DependencyObject> elemList = FindVisualChildren<DependencyObject>((DependencyObject)parent);        foreach (DependencyObject obj in elemList)        {            if (obj.GetHashCode() == child.GetHashCode())                return true;        }        return false;    }    private IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject    {        if (depObj != null)        {            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)            {                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);                if (child != null && child is T)                {                    yield return (T)child;                }                foreach (T childOfChild in FindVisualChildren<T>(child))                {                    yield return childOfChild;                }            }        }    }    private Rect GetAbsolutePlacement(FrameworkElement container, FrameworkElement element, bool relativeToScreen = false)    {        var absolutePos = element.PointToScreen(new System.Windows.Point(0, 0));        if (relativeToScreen)        {            return new Rect(absolutePos.X, absolutePos.Y, element.ActualWidth, element.ActualHeight);       }        var posMW = container.PointToScreen(new System.Windows.Point(0, 0));        absolutePos = new System.Windows.Point(absolutePos.X - posMW.X, absolutePos.Y - posMW.Y);        return new Rect(absolutePos.X, absolutePos.Y, element.ActualWidth, element.ActualHeight);   }

Then all that is needed to find out if a button (for example) is clickable is to call:

 if (isElementClickable<Button>(Application.Current.MainWindow, myButton, out isPartiallyClickable)) {      // Whatever }