En WPF, ¿cómo puedo determinar si un control es visible para el usuario?


Estoy mostrando un árbol muy grande con muchos elementos en él. Cada uno de estos elementos muestra información al usuario a través de su control UserControl asociado, y esta información tiene que actualizarse cada 250 milisegundos, lo que puede ser una tarea muy costosa ya que también estoy usando reflexión para acceder a algunos de sus valores. Mi primer enfoque fue usar la propiedad IsVisible, pero no funciona como esperaba.

¿ Hay alguna manera de que pueda determinar si un control es 'visible' para el de usuario?

Nota: Ya estoy usando la propiedad IsExpanded para omitir la actualización de nodos colapsados, pero algunos nodos tienen más de 100 elementos y no puedo encontrar una manera de omitir aquellos que están fuera de la ventana de cuadrícula.

Author: Trap, 2009-10-05

4 answers

Puede usar esta pequeña función auxiliar que acabo de escribir que comprobará si un elemento es visible para el usuario, en un contenedor dado. La función devuelve true si el elemento es parcialmente visible. Si desea comprobar si está completamente visible, reemplace la última línea por 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);
}

En su caso, element será su control de usuario, y container su Ventana.

 73
Author: Julien Lebosquain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-23 13:17:54
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);
}
 15
Author: Andreas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-18 19:23:52

Utilice estas propiedades para el control contenedor:

VirtualizingStackPanel.IsVirtualizing="True" 
VirtualizingStackPanel.VirtualizationMode="Recycling"

Y luego conectar escuchando INotifyPropertyChanged de su elemento de datos.PropertyChanged suscriptores como este

    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            Console.WriteLine(
               "WPF is listening my property changes so I must be visible");
        }
        remove
        {
            Console.WriteLine("WPF unsubscribed so I must be out of sight");
        }
    }

Para información más detallada ver: http://joew.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DWPF

 4
Author: Timoi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-04 10:20:15

La respuesta aceptada (y las otras respuestas en esta página) resuelven el problema específico que tenía el póster original pero no dan una respuesta adecuada a la pregunta escrita en el título, es decir, Cómo determinar si un control es visible para el usuario. El problema es que Un control que está cubierto por otros controles no es visible a pesar de que puede ser renderizado y está dentro de los límites de su contenedor que es lo que las otras respuestas están resolviendo.

A determinar si un control a es visible para el usuario a veces tiene que ser capaz de determinar si un WPF UIElement es Clicable (o accesible con el ratón en un PC) por el usuario

Me encontré con este problema cuando estaba tratando de comprobar si el usuario puede hacer clic en un botón con el ratón. Un escenario de caso especial que me molestó fue que un botón puede ser realmente visible para el usuario, pero cubierto con alguna capa transparente (o semi transparente o no transparente en absoluto) que evitan que el ratón clic. En tal caso, un control podría ser visible para el usuario, pero no accesible para el usuario, lo que es como si no fuera visible en absoluto.

Así que tuve que idear mi propia solución.

EDIT - Mi post original tenía una solución diferente que usaba el método InputHitTest. Sin embargo, no funcionó en muchos casos y tuve que rediseñarlo. Esta solución es mucho más robusta y parece estar funcionando muy bien sin falsos negativos o positivo.

Solución:

  1. Obtener la posición absoluta del objeto en relación con la Ventana Principal de la aplicación
  2. Llama a VisualTreeHelper.HitTest en todas sus esquinas (Arriba a la izquierda, abajo a la izquierda, arriba a la derecha, abajo a la derecha)
  3. Llamamos a un objeto Completamente Clicable si el objeto obtenido de VisualTreeHelper.HitTest es igual al objeto original o un padre visual de él para todas sus esquinas, y Parcialmente Clicable para una o más esquinas.

Tenga en cuenta #1: El definición aquí de Totalmente Clicable o Parcialmente Clickable no son exactos - solo estamos comprobando las cuatro esquinas de un se puede hacer clic en el objeto. Si, por ejemplo, un botón tiene 4 esquinas clicables pero es centro tiene un lugar que no se puede hacer clic, todavía lo consideraremos como Totalmente Interactiva. Para comprobar todos los puntos en un objeto dado sería demasiado derrochador.

Tenga en cuenta #2: a veces es necesario establecer un objeto IsHitTestVisible propiedad a true (sin embargo, este es el valor predeterminado valor para muchos comunes controles) si queremos VisualTreeHelper.HitTest encontrarlo

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

Entonces todo lo que se necesita para saber si se puede hacer clic en un botón (por ejemplo) es llamar:

 if (isElementClickable<Button>(Application.Current.MainWindow, myButton, out isPartiallyClickable))
 {
      // Whatever
 }
 3
Author: Ofer Barasofsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-25 08:56:55