В WPF, как я могу определить, является ли элемент управления видимым для пользователя?
Я показывать очень большое дерево с большим количеством элементов в нем. Каждый из этих элементов показывает информацию пользователю через связанный с ним элемент управления UserControl, и эта информация должна обновляться каждые 250 миллисекунд, что может быть очень дорогостоящей задачей, поскольку я также использую отражение для доступа к некоторым из их значений. Мой первый подход состоял в том, чтобы использовать свойство IsVisible, но оно не работает так, как я ожидал.
есть ли способ определить, является ли элемент управления "видимый" для пользователя?
Примечание: я уже использую свойство IsExpanded, чтобы пропустить обновление свернутых узлов, но некоторые узлы имеют 100+ элементов и не могут найти способ пропустить те, которые находятся за пределами видового экрана сетки.
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. Однако это не сработало во многих случаях, и мне пришлось перепроектировать его. Это решение является гораздо более надежным, и, кажется, работает очень хорошо без каких-либо ложных негатив или позитив.
устранение:
- получить абсолютное положение объекта относительно главного окна приложения
- вызов
VisualTreeHelper.HitTestна всех его углах (верхний левый, нижний левый, верхний правый, нижний правый)- мы называем объект Полностью Кликабельный если объект получен из
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