.net - 在WPF中,如何確定控件是否對用戶可見?




user-interface wpf-controls (3)

我正在展示一棵非常大的樹,裡面有很多物品。 這些項目中的每一項都通過其關聯的UserControl控件向用戶顯示信息,並且此信息必須每250毫秒更新一次,這可能是一項非常昂貴的任務,因為我還使用反射來訪問其某些值。 我的第一種方法是使用IsVisible屬性,但它不能像我預期的那樣工作。

有什麼方法可以確定控件是否對用戶“可見”?

注意:我已經使用IsExpanded屬性來跳過更新折疊節點,但是有些節點有100多個元素,無法找到跳過網格視口之外的節點的方法。


將這些屬性用於包含控件:

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%3DWPFhttp://joew.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DWPF


接受的答案(以及本頁上的其他答案)解決了原始海報所具有的具體問題,但是他們沒有對標題中寫的問題給出足夠的答案,即如何確定控件是否對於用戶 。 問題是, 其他控件所涵蓋的控件是不可見的,即使它可以被渲染,並且它位於其容器的邊界內,這是其他答案正在解決的問題。

要確定用戶是否可以看到控件,您有時必須能夠確定用戶是否可以點擊WPF UIElement(或在PC上可以訪問)

當我試圖檢查用戶是否可以用鼠標單擊按鈕時,我遇到了這個問題。 一個讓我感到困惑的特殊情況是,按鈕實際上對用戶可見,但覆蓋了一些透明(或半透明或非透明)層,阻止鼠標點擊。 在這種情況下,控件可能對用戶可見,但是用戶不可訪問,這有點像根本不可見。

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

編輯 - 我的原始帖子有一個使用InputHitTest方法的不同解決方案。 然而,它在許多情況下都不起作用,我不得不重新設計它。 這種解決方案更加強大,似乎運行良好,沒有任何誤報或肯定。

解:

  1. 獲取相對於應用程序主窗口的對象絕對位置
  2. 在所有角上調用VisualTreeHelper.HitTest (左上角,左下角,右上角,右下角)
  3. 如果從VisualTreeHelper.HitTest獲取的對像等於原始對像或其所有角落的可視父對象,則可以將對象稱為完全可單擊對象,並且對於一個或多個角可以部分可單擊

請注意#1:完全可點擊或部分可點擊的定義並不准確 - 我們只是檢查對象的所有四個角都是可點擊的。 例如,如果一個按鈕有4個可點擊的角落,但它的中心有一個不可點擊的點,我們仍然認為它是完全可點擊的。 檢查給定對像中的所有點都太浪費了。

請注意#2:如果我們希望VisualTreeHelper.HitTest找到它,有時需要將對象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;
    }

    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
 }

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




visibility