在WPF中,如何确定控件是否对用户可见?

我正在展示一个很大的树,里面有很多物品。 这些项目中的每一个通过与其相关的UserControl控件向用户显示信息,并且该信息必须每250毫秒更新一次,这可能是非常昂贵的任务,因为我也使用reflection来访问它们的一些值。 我的第一个方法是使用IsVisible属性,但它不工作,如我所料。

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

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

你可以使用我刚才写的这个小助手函数,它会检查给定容器中的用户是否可见元素。 如果元素部分可见,该函数返回true 。 如果要检查它是否完全可见,请用rect.Contains(bounds)replace最后一行。

 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的窗口。

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

为包含控件使用这些属性:

 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

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

要确定用户是否可以看到某个控件,您有时必须能够确定WPF UIElement是否可由用户点击(或在PC上可以鼠标到达)

当我试图检查一个button是否可以被用户点击时,我遇到了这个问题。 一个特殊的情况是,一个button对用户来说实际上是可见的,但却覆盖了一些透明的(或者半透明的或者不透明的)图层来防止鼠标点击。 在这种情况下,用户可以看到一个控件,但是用户不能访问,就好像根本看不见。

所以我必须拿出我自己的解决scheme。

编辑 – 我原来的post有一个不同的解决scheme,使用InputHitTest方法。 然而,在许多情况下,它不起作用,我不得不重新devise它。 这个解决scheme更强大,似乎运作良好,没有任何错误的消极或积极的。

解:

  1. 获取相对于应用程序主窗口的对象绝对位置
  2. 调用VisualTreeHelper.HitTest的所有angular落(左上angular,左下angular,右上angular,右下angular)
  3. 如果从VisualTreeHelper.HitTest获得的对象等于原始对象或其所有angular落的可视父对象,并且对于一个或多个angular落可部分可点击 ,我们称该对象为“ 完全可点击 ”对象。

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

请注意#2:如果我们希望VisualTreeHelper.HitTestfind它,有时需要将对象IsHitTestVisible属性设置为true (但是,这是许多常用控件的默认值)

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

然后,只需要找出一个button(例如)是否可点击就可以调用:

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