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



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



есть ли способ определить, является ли элемент управления "видимый" для пользователя?



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

772   4  

4 ответов:

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

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

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

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

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

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

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

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

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

устранение:

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

обратите внимание #1: определение здесь полностью кликабельно или частично Кликабельны не точно - мы просто проверяем все четыре угла объект кликабельны. Если, например, кнопка имеет 4 кликабельных угла, но это центр имеет место, которое не является кликабельным, мы все равно будем рассматривать его как Полностью Кликабельный. Проверить все точки в данном объекте было бы тоже расточительный.

обратите внимание #2: Иногда требуется установить объект IsHitTestVisible собственность на правда (однако это значение по умолчанию для многих распространенных управления) если мы хотим 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
 }

Comments

    Ничего не найдено.