Подтвердить что ты не робот

В WPF, как я могу определить, является ли элемент управления видимым для пользователя?

Я показываю очень большое дерево с большим количеством предметов в нем. Каждый из этих элементов отображает информацию пользователю через связанный с ним элемент управления UserControl, и эта информация должна обновляться каждые 250 миллисекунд, что может быть очень дорогостоящей задачей, поскольку я также использую рефлексию для доступа к некоторым из их значений. Мой первый подход заключался в использовании свойства IsVisible, но он не работает, как я ожидал.

Можно ли каким-либо образом определить, является ли элемент управления видимым для пользователя?

Примечание. Я уже использую свойство IsExpanded, чтобы пропустить обновление свернутых узлов, но некоторые узлы имеют более 100 элементов и не могут найти способ пропускать те, которые находятся вне окна просмотра сетки.

4b9b3361

Ответ 1

Вы можете использовать эту небольшую вспомогательную функцию, которую я только что написал, чтобы проверить, является ли элемент видимым для пользователя в данном контейнере. Функция возвращает 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 вашим окном.

Ответ 2

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

Ответ 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

Ответ 4

Принятый ответ (и другие ответы на этой странице) решает конкретную проблему, которую имел исходный плакат, но они не дают адекватного ответа на вопрос, записанный в названии, т.е. Как определить, элемент управления отображается пользователю. Проблема в том, что элемент A, который покрыт другими элементами управления, не отображается, даже если он может быть отображен, и он находится в пределах его контейнера, что и решает другие ответы.

Чтобы определить, является ли элемент управления видимым для пользователя, вы иногда должны иметь возможность определять, является ли пользовательский интерфейс WPF UIElement доступным (или доступным для пользователя на ПК) пользователем

Я столкнулся с этой проблемой, когда пытался проверить, может ли пользователь щелкнуть мышью пользователем. Особый случайный сценарий, который беспокоил меня, заключался в том, что кнопка может быть на самом деле видимой для пользователя, но покрыта прозрачным (или полупрозрачным или не прозрачным на всех) слоем, который предотвращает щелчки мыши. В таком случае элемент управления может быть виден пользователю, но недоступен для пользователя, который подобен тому, как он вообще не отображается.

Поэтому мне пришлось придумать свое решение.

EDIT. У моего исходного сообщения было другое решение, которое использовало метод InputHitTest. Однако во многих случаях это не сработало, и мне пришлось перепроектировать его. Это решение гораздо более надежное и, похоже, работает очень хорошо, без ложных негативов или положительных результатов.

Решение:

  • Получить абсолютное положение объекта относительно основного окна приложения
  • Вызов VisualTreeHelper.HitTest на всех его углах (верхний левый, нижний левый, верхний правый, правый нижний)
  • Мы вызываем объект Полностью Clickable, если объект, полученный из VisualTreeHelper.HitTest, равен исходному объекту или визуальному родительскому элементу для всех его углов и Частично Clickable для один или несколько углов.

Обратите внимание: # 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
 }