2009-10-04 151 views
55

我正在显示一个有很多项目的非常大的树。这些项目中的每一个都通过其相关的UserControl控件向用户显示信息,并且该信息必须每250毫秒更新一次,这可能是一项非常昂贵的任务,因为我也在使用反射来访问它们的一些值。我的第一种方法是使用IsVisible属性,但它不像我预期的那样工作。在WPF中,如何确定控件是否对用户可见?

有什么方法可以确定控件是否对用户可见?

注意:我已经使用IsExpanded属性来跳过更新折叠节点,但有些节点有100多个元素,无法找到跳过网格视口外部的节点的方法。

+5

我曾经有过类似的问题。在编写代码来检测控件是否可见之后,事实证明,要检测的代码比实际更新隐藏控件要慢。基准你的结果,因为它可能不值得。 – 2009-10-05 01:21:24

回答

70

您可以使用我刚刚写的这个小助手函数,它会检查给定容器中的用户是否可见元素。如果元素部分可见,则函数返回true。如果您想检查它是否完全可见,请将最后一行替换为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); 
} 

在你的情况,element将是您的用户控制和container你的窗口。

+23

这不包括元素超过容器大小的情况。返回rect.IntersectsWith(边界),而不是修复。 – Amanduh 2011-08-29 20:48:38

+3

您通常希望使用大量数据使用UI虚拟化。为此,您不要直接设置项目(即'ItemsContro.Items.Add(new ...)'),而是使用数据绑定。 但是,数据绑定会破坏视觉层次结构,因为子项添加到您的数据对象(例如'ObservableList')中不会有父项。 'TransformToAncestor'(或'TransformToVisual')将不起作用。在这种情况下我们应该做什么? – Shakaron 2015-07-21 11:48:53

3

使用这些属性包含控制:

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

然后挂钩听你的数据项的INotifyPropertyChanged.PropertyChanged用户这样

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

有关详细信息,请参阅: http://joew.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DWPF

+1

初始化事件比这更合适。请注意,虚拟化可能会比可见的更早初始化和布线对象,所以无论如何,这种方法并不能保证您的对象是可见的。 – Doug 2012-05-15 16:27:36

+0

上述链接已损坏。你可以更换更换吗?谢谢! – 2015-06-20 00:21:03

15
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); 
    } 
+0

这是否说明因窗口最小化或隐藏在其他窗口后面而导致元素未被看到? – Wobbles 2017-07-18 13:45:25

3

被接受的答案(以及本页面上的其他答案)解决了原始海报具有的特定问题,但他们没有给标题中写的问题提供充分的答案,即如何确定控件是否可见用户。问题是其他控件覆盖的控件不可见即使它可以被渲染并且它在其容器的边界内,这是其他答案正在解决的问题。

要确定AA控件是否对用户可见有时必须能够确定一个WPF的UIElement是否是可点击(或鼠标在电脑上可到达)由

我的时候遇到这个问题的用户我试图检查一个按钮是否可以由用户点击鼠标。一个让我感到困惑的特殊情况是,一个按钮对用户来说实际上是可见的,但却覆盖了一些防止鼠标点击的透明(或半透明或不透明)图层。在这种情况下,控件可能对用户可见,但对用户来说不可访问,这有点像根本不可见。所以我不得不提出我自己的解决方案。

编辑 - 我原来的文章有不同的解决方案,使用InputHitTest方法。然而,它在很多情况下都不起作用,我不得不重新设计它。这个解决方案更强大,并且似乎工作得很好,没有任何错误的否定或肯定。

解决方案:

  1. 获取绝对相对于应用程序的主窗口
  2. 呼叫​​它的各个角落(左上,左下,右上,右下)
  3. 对象位置我们调用对象完全可点击如果从​​获得的对象等于原始对象或其所有角落的可见父对象,并且部分地可点击一个或多个角落。

请注意#1:这里定义的完全可点击或部分 可点击不准确的 - 我们只是检查的 对象的所有四个角落都可以点击。例如,如果一个按钮有4个可点击的角落,但它的中心点有一个不可点击的点,我们仍将其视为 完全可点击。要检查给定对象中的所有点,太浪费了。

请注意#2:有时需要设定一个对象IsHitTestVisible 属性真正(然而,这是许多常见 控件的默认值),如果我们希望​​找到它

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

然后就是需要找出如果(例如)按钮点击都是调用:

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

我想尝试一下,但看起来我缺少对GetAbsolutePlacement()和FindVisualChildren()的引用。我错过了什么? – 2017-12-21 03:56:34

+0

糟糕!我以前的编辑意外删除了这些方法,现在他们又回来了。谢谢! – 2017-12-25 08:58:30

相关问题