Как в WPF определить, является ли элемент управления видимым для пользователя? - PullRequest
59 голосов
/ 05 октября 2009

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

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

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

Ответы [ 4 ]

77 голосов
/ 05 октября 2009

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

15 голосов
/ 28 января 2014
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);
}
4 голосов
/ 04 декабря 2009

Используйте эти свойства для содержащего элемента управления:

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 голосов
/ 15 февраля 2017

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

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

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

Так что мне пришлось придумать собственное решение.

РЕДАКТИРОВАТЬ - В моем исходном сообщении было другое решение, в котором использовался метод InputHitTest. Однако во многих случаях это не сработало, и мне пришлось его переделать. Это решение намного надежнее и, кажется, работает очень хорошо без каких-либо ложных негативов или позитивов.

Решение:

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

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