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

时间:2009-10-04 23:55:52

标签: .net wpf user-interface wpf-controls visibility

我正在展示一棵非常大的树,里面有很多物品。这些项目中的每一项都通过其关联的UserControl控件向用户显示信息,并且此信息必须每250毫秒更新一次,这可能是一项非常昂贵的任务,因为我还使用反射来访问其某些值。我的第一种方法是使用IsVisible属性,但它不能像我预期的那样工作。

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

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

4 个答案:

答案 0 :(得分:77)

您可以使用我刚刚编写的这个小助手函数来检查给定容器中的元素是否对用户可见。如果元素部分可见,则函数返回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将成为您的窗口。

答案 1 :(得分: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);
}

答案 2 :(得分:4)

将这些属性用于包含控件:

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

答案 3 :(得分:3)

接受的答案(以及本页上的其他答案)解决了原始海报所遇到的具体问题,但他们没有对标题中的问题给出足够的答案,即如何确定是否控件对用户可见。问题是其他控件覆盖的控件不可见,即使它可以渲染,并且它位于其容器的边框内,这是其他答案正在解决的问题。

要确定某个控件是否对用户可见,您有时必须确定用户是否可以点击(或在PC上鼠标可访问)WPF UIElement

当我尝试检查用户是否可以通过鼠标单击按钮时遇到此问题。一个让我感到困惑的特殊情况是,按钮实际上对用户可见,但覆盖了一些透明(或半透明或非透明)层,阻止鼠标点击。在这种情况下,控件可能对用户可见,但用户无法访问,有点像根本看不到。

所以我不得不想出自己的解决方案。

编辑 - 我的原帖有一个使用InputHitTest方法的不同解决方案。然而,它在许多情况下都不起作用,我不得不重新设计它。这种解决方案更加强大,似乎运行良好,没有任何误报或肯定。

<强>解决方案:

  1. 获取相对于应用程序主窗口的对象绝对位置
  2. 在其所有角落(左上角,左下角,右上角,右下角)呼叫VisualTreeHelper.HitTest
  3. 如果从VisualTreeHelper.HitTest获取的对象等于原始对象或其所有角落的视觉父对象,则称为完全可点击对象部分可点击一个或多个角落。
  4.   

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

         

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

        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
     }