W WPF, jak Mogę określić, czy kontrolka jest widoczna dla użytkownika?

Wyświetlam bardzo duże drzewo z wieloma przedmiotami w nim. Każda z tych pozycji wyświetla informacje użytkownikowi za pośrednictwem powiązanej z nią kontroli UserControl, a informacje te muszą być aktualizowane co 250 milisekund, co może być bardzo kosztowne zadanie, ponieważ używam również reflection, aby uzyskać dostęp do niektórych z ich wartości. Moim pierwszym podejściem było użycie właściwości IsVisible, ale nie działa tak, jak oczekiwałem.

Czy jest jakiś sposób na określenie, czy kontrolka jest "widoczna" dla użytkownik?

Uwaga: Używam już właściwości IsExpanded, aby pominąć aktualizowanie zwiniętych węzłów, ale niektóre węzły mają ponad 100 elementów i nie mogą znaleźć sposobu, aby pominąć te, które znajdują się poza obszarem widoku siatki.

Author: Trap, 2009-10-05

4 answers

Możesz użyć tej małej funkcji pomocniczej, którą napisałem, która sprawdzi, czy element jest widoczny dla użytkownika w danym kontenerze. Funkcja zwraca true, Jeśli element jest częściowo widoczny. Jeśli chcesz sprawdzić, czy jest w pełni widoczny, zastąp ostatnią linię przez 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);
}

W Twoim przypadku, {[3] } będzie twoją kontrolą Użytkownika, a container Twoim oknem.

 73
Author: Julien Lebosquain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-23 13:17:54
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);
}
 15
Author: Andreas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-18 19:23:52

Użyj tych właściwości dla kontrolki zawierającej:

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

A następnie podłącz nasłuchiwanie pozycji danych INotifyPropertyChanged.PropertyChanged subscribers like this

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

Aby uzyskać bardziej szczegółowe informacje patrz: http://joew.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DWPF

 4
Author: Timoi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-04 10:20:15

Zaakceptowana odpowiedź (i inne odpowiedzi na tej stronie) rozwiązują konkretny problem, który miał oryginalny plakat, ale nie dają odpowiedniej odpowiedzi na pytanie napisane w tytule, tj. Jak określić, czy kontrolka jest widoczna dla użytkownika. Problem polega na tym, że kontrolka, która jest objęta innymi kontrolkami, nie jest widoczna, mimo że może być renderowana i znajduje się w granicach swojego kontenera, do czego rozwiązują inne odpowiedzi.

To określenie, czy kontrolka a jest widoczna dla użytkownika czasami musisz być w stanie określić, czy interfejs użytkownika WPF jest klikalny (lub możliwy do osiągnięcia na komputerze) przez użytkownika

Napotkałem ten problem, gdy próbowałem sprawdzić, czy przycisk może być kliknięty myszką przez użytkownika. Szczególnym przypadkiem, który mnie wkurzył, było to, że przycisk może być rzeczywiście widoczny dla użytkownika, ale pokryty jakąś przezroczystą (lub półprzezroczystą lub nieprzezroczystą w ogóle) warstwą, która uniemożliwia mysz kliknięć. W takim przypadku kontrolka może być widoczna dla użytkownika, ale niedostępna dla użytkownika, co w pewnym sensie nie jest w ogóle widoczna.

Więc musiałem wymyślić własne rozwiązanie.

EDIT - mój oryginalny post miał inne rozwiązanie, które zastosowało metodę InputHitTest. Jednak w wielu przypadkach nie działało i musiałem go przeprojektować. To rozwiązanie jest znacznie bardziej wytrzymałe i wydaje się działać bardzo dobrze bez żadnych fałszywych negatywów lub pozytywy.

Rozwiązanie:

  1. uzyskanie bezwzględnej pozycji obiektu względem głównego okna aplikacji
  2. wywołanie VisualTreeHelper.HitTest na wszystkich jego rogach (górny lewy, dolny lewy, górny prawy, dolny prawy)
  3. nazywamy obiektw pełni klikalny jeśli obiekt uzyskany z VisualTreeHelper.HitTest jest równy oryginalnemu obiektowi lub wizualnemu rodzicowi dla wszystkich jego narożników, aczęściowo klikalny dla jednego lub więcej narożników.

Uwaga #1: definicja w pełni klikalnych lub częściowo Klikalne nie są dokładne-sprawdzamy tylko wszystkie cztery rogi obiekt można kliknąć. Jeśli np. przycisk ma 4 klikalne rogi, ale jest centrum ma miejsce, które nie jest klikalne, nadal będziemy go traktować jako W Pełni Klikalne. Aby sprawdzić wszystkie punkty w danym obiekcie byłoby zbyt marnotrawstwo.

Uwaga # 2: czasami wymagane jest ustawienie obiektu IsHitTestVisible właściwość true (jest to jednak domyślna wartość dla wielu wspólnych kontrolki) jeśli chcemy VisualTreeHelper.HitTest aby go znaleźć

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

Wtedy wszystko, co jest potrzebne, aby dowiedzieć się, czy przycisk (na przykład) jest klikalny, to wywołać:

 if (isElementClickable<Button>(Application.Current.MainWindow, myButton, out isPartiallyClickable))
 {
      // Whatever
 }
 3
Author: Ofer Barasofsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-25 08:56:55