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

Estoy mostrando un árbol muy grande con muchos elementos. Cada uno de estos elementos muestra información al usuario a través de su control UserControl asociado, y esta información debe actualizarse cada 250 milisegundos, lo que puede ser una tarea muy costosa ya que también estoy usando el reflection 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 usuario?

Nota: Ya estoy usando la propiedad IsExpanded para omitir la actualización de nodos contraídos, pero algunos nodos tienen más de 100 elementos y no pueden encontrar la manera de omitir aquellos que están fuera de la ventana de la grilla.

Puedes usar esta pequeña función de ayuda que acabo de escribir que verificará 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 verificar si es 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, el element será su control de usuario y container su ventana.

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

Use estas propiedades para el control que lo contiene:

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

y luego se conecta a escuchar los suscriptores INotifyPropertyChanged.PropertyChanged de su elemento de datos 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 obtener información más detallada, consulte: http://joew.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DWPF

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 aunque se pueda representar y esté dentro de los límites de su contenedor, para lo cual las otras respuestas están resolviendo.

Para determinar si un control es visible para el usuario, a veces debe ser capaz de determinar si un UIElement de WPF puede hacer clic (o puede acceder al mouse en una PC) el usuario

Encontré este problema cuando estaba tratando de verificar si el usuario puede hacer clic en un botón. Un caso especial que me molestó fue que un botón puede ser realmente visible para el usuario pero cubierto con una capa transparente (o semi transparente o no transparente) que evita los clics del mouse. En tal caso, un control puede ser visible para el usuario pero no accesible para el usuario, que es como que no es visible en absoluto.

Entonces tuve que encontrar mi propia solución.

EDITAR – Mi publicación 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 funcionar muy bien sin falsos negativos o positivos.

Solución:

  1. Obtener posición absoluta del objeto relativa a la ventana principal de la aplicación
  2. Llame 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 Totalmente Accesible si el objeto obtenido de VisualTreeHelper.HitTest igual al objeto original o un elemento primario visual para todas sus esquinas, y Clickable Parcialmente para una o más esquinas.

Tenga en cuenta que el n. ° 1: La definición aquí de Completamente cliqueable o Parcialmente clicable no es exacta; solo estamos comprobando que se puede hacer clic en las cuatro esquinas de un objeto. Si, por ejemplo, un botón tiene 4 esquinas en las que se puede hacer clic, pero su centro tiene un lugar en el que no se puede hacer clic, seguiremos considerándolo como Totalmente clicable. Revisar todos los puntos en un objeto dado sería un desperdicio excesivo.

Tenga en cuenta el n. ° 2: a veces se requiere establecer una propiedad IsHitTestVisible objeto en true (sin embargo, este es el valor predeterminado para muchos controles comunes) si deseamos que VisualTreeHelper.HitTest encuentre

  private bool isElementClickable(UIElement container, UIElement element, out bool isPartiallyClickable) { isPartiallyClickable = false; Rect pos = GetAbsolutePlacement((FrameworkElement)container, (FrameworkElement)element); bool isTopLeftClickable = GetIsPointClickable(container, element, new Point(pos.TopLeft.X + 1,pos.TopLeft.Y+1)); bool isBottomLeftClickable = GetIsPointClickable(container, element, new Point(pos.BottomLeft.X + 1, pos.BottomLeft.Y - 1)); bool isTopRightClickable = GetIsPointClickable(container, element, new Point(pos.TopRight.X - 1, pos.TopRight.Y + 1)); bool isBottomRightClickable = GetIsPointClickable(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(UIElement container, UIElement element, Point p) { DependencyObject hitTestResult = HitTest< T>(p, container); if (null != hitTestResult) { return isElementChildOfElement(element, hitTestResult); } return false; } private DependencyObject HitTest(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 elemList = FindVisualChildren((DependencyObject)parent); foreach (DependencyObject obj in elemList) { if (obj.GetHashCode() == child.GetHashCode()) return true; } return false; } private IEnumerable FindVisualChildren(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(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); } 

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

  if (isElementClickable