WPF - переход от DataGridRow к DataGridCell без VisualTreeHelper

Я вижу это изображение, когда отлаживаю свое приложение WPF (.NET Framework 4.8)

Я вычислил часть дерева:

Теперь мне нужно пройти от DataGridCellsPresenter к DataGridCell массиву.

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

enter image description here

версия 1 — с использованием обычного обхода дерева:

child = UIHelper.FindChild<Grid>(row, "_b");

версия 2 - жестко заданный обход дерева

var bx =  VisualTreeHelper.GetChild(row, 0) as Border;
var cp =  ((bx.Child as SelectiveScrollingGrid).Children[0] as DataGridCellsPresenter);
var ip =  VisualTreeHelper.GetChild(cp,  0) as ItemsPresenter;
var cpl = VisualTreeHelper.GetChild(ip,  0) as DataGridCellsPanel;
var c =   VisualTreeHelper.GetChild(cpl, 2) as DataGridCell;
var g =   VisualTreeHelper.GetChild(c,   0) as Grid;

1 000 000 итераций версии 1 против 2 (галочки на моей машине), т.е. версия 2 на 750% быстрее:

FindChild: 12,845,015
Hardcode: 1,706,232

Можете ли вы предложить более быстрый способ?

Я не могу понять, как избавиться от GetChild - многие методы и свойства защищены или приватны.


Найтиребенка:

public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
    int childrenCount = VisualTreeHelper.GetChildrenCount(parent);

    for (int i = 0; i < childrenCount; i++)
    {
        var child = VisualTreeHelper.GetChild(parent, i);

        T child_Test = child as T;

        if (child_Test == null)
        {
            var c = FindChild<T>(child, childName);

            if (c != null) return c;
        }
        else
        {
            FrameworkElement child_Element = child_Test as FrameworkElement;

            if (child_Element.Name == childName)
            {
                return child_Test;
            }

            var c = FindChild<T>(child, childName);

            if (c != null) return c;
        }
    }

    return null;
}

Я в замешательстве, вы можете простым языком выразить, что вы от нас хотите? Вы говорите, что версия VisualTreeHelper в 75 раз быстрее вашего кода, но вы не хотите использовать VisualTreeHelper и .. что? Хотите, чтобы мы переделали его с нуля для вас или что-то в этом роде? Чего ты хочешь?

Blindy 16.05.2022 20:42

Кроме того, «я должен жестко закодировать это» - нет, вы этого не сделаете, вы просто решили сделать это таким образом. Ваша задача состоит в том, чтобы реализовать и/или исправить что-то (из того немногого, что вы показали, бог знает только, насколько этот код нуждается в исправлении), и есть гораздо лучшие способы сделать такие вещи в WPF. На самом деле, одно слово для вас: шаблоны.

Blindy 16.05.2022 20:43

Я не хочу ходить по дереву, как в методе FindChild, который довольно распространен. Я нашел более быстрый способ, но он все еще использует visualtreehelper. Я спрашиваю - можно ли сделать быстрее? P.S. мне нравится ваш вклад в С++, в котором я сосать. Но в .NET мне комфортно, и я знаю, что делаю, и лорд дефо со мной согласен :) (чтобы объяснить, почему, потребуется 2 страницы формата А4..

Boppity Bop 16.05.2022 22:09

Было бы интересно узнать вашу цель. Вам действительно нужно содержимое ячейки?

BionicCode 17.05.2022 00:40

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

Boppity Bop 17.05.2022 01:41
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
Четыре эффективных способа центрирования блочных элементов в CSS
Четыре эффективных способа центрирования блочных элементов в CSS
У каждого из нас бывали случаи, когда нам нужно отцентрировать блочный элемент, но мы не знаем, как это сделать. Даже если мы реализуем какой-то...
2
5
32
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это не VisualTreeHelper "медленно". Это способ обхода дерева для поиска целевого элемента. Использование VisualTreeHelper «правильно» значительно улучшит поиск.

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

В зависимости от сценария разные алгоритмы или их комбинации могут работать лучше.

Лучший сценарий — это когда вы знаете дерево и можете полагаться на то, что порядок его узлов будет постоянным. Но в целом, если вы не строитель дерева, вы не можете полагаться на то, что расположение дерева будет постоянным.
Основываясь на характере визуального дерева, мы можем предположить, что поиск в ширину работает намного лучше, чем широко распространенный предварительный поиск, если ожидаемое дерево не слишком широкое, а целевой узел не является листом.
Основываясь на наблюдениях, мы можем предположить, что визуальное дерево имеет тенденцию расти в глубину, а не в ширину. У большинства контейнеров есть один дочерний элемент. Контейнеры, такие как Grid, обычно не имеют большого количества столбцов, т. е. содержат много братьев и сестер.

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

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

Ниже приведены четыре примера алгоритмов, которые работают лучше, чем обычный поиск по предварительному заказу (в данном сценарии). Обход разделен на два этапа, чтобы избежать ненужного обхода ветвей: сначала найдите хост DataGridCell (ItemsControl), а затем используйте ItemContainerGenerator, чтобы найти корневой элемент. Использование ItemContainerGenerator еще больше улучшит эффективность поиска.

Я не измерял их эффективность, но на основе проверки я дал им рейтинг.
Более высокое число означает лучшую производительность. 2 и 3 могут поменяться местами. Все примеры пытаются найти первую ячейку первой строки:

1 (предзаказ)

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

int rowIndexToVisit = 0;
int columnIndexToVisit = 0;

DependencyObject rowItemContainer = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndexToVisit);
if (TryFindVisualChildElementByName(rowItemContainer, "_b", out Border border))
{  
  DependencyObject cellVisualRoot = border;
}
public static bool TryFindVisualChildElementByName<TChild>(
  DependencyObject parent,
  string childElementName,
  out TChild resultElement) where TChild : FrameworkElement
{
  resultElement = null;

  if (parent is Popup popup)
  {
    parent = popup.Child;
    if (parent == null)
    {
      return false;
    }
  }

  for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
  {
    DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);

    if (childElement is TChild frameworkElement)
    {
      if (string.IsNullOrWhiteSpace(childElementName)
            || frameworkElement.Name.Equals(childElementName, StringComparison.Ordinal))
      {
        resultElement = frameworkElement;
        return true;
      }
    }

    if (TryFindVisualChildElementByName(childElement, childElementName, out resultElement))
    {
      return true;
    }
  }

  return false;
}

2 (Сначала в ширину / ш ItemContainerGenerator)

Требуется знание иерархии типов DataGrid (в частности, что DataGridCellsPresenter является хостом DataGridCell элементов).

int rowIndexToVisit = 0;
int columnIndexToVisit = 0;

DependencyObject rowItemContainer = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndexToVisit);
if (TryFindVisualChildElementBreadthFirst(rowItemContainer, out DataGridCellsPresenter dataGridCellsPresenter))
{
  var cellItemContainer = dataGridCellsPresenter.ItemContainerGenerator.ContainerFromIndex(columnIndexToVisit) as DataGridCell;
  DependencyObject cellVisualRoot = VisualTreeHelper.GetChild(cellItemContainer, 0);
}
public static bool TryFindVisualChildElementBreadthFirst<TChild>(
  DependencyObject parent,
  string name,
  out TChild resultElement) where TChild : FrameworkElement
{
  resultElement = null;

  if (parent is Popup popup)
  {
    parent = popup.Child;
    if (parent == null)
    {
      return false;
    }
  }

  var pendingSubtree = new Queue<DependencyObject>();
  for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
  {
    DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
    if (childElement is TChild frameworkElement)
    {
      resultElement = frameworkElement;
      return true;
    }

    pendingSubtree.Enqueue(childElement);
  }

  while (pendingSubtree.TryDequeue(out DependencyObject subtreeRoot))
  {
    if (TryFindVisualChildElementBreadthFirst(subtreeRoot, name, out resultElement))
    {
      return true;
    }
  }

  return false;
}

3 (ручное логическое дерево)

Требуется точное знание ControlTemplate:

int rowIndexToVisit = 0;
int columnIndexToVisit = 0;

DependencyObject rowItemContainer = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndexToVisit);
var rowItemContainerTemplate = rowItemContainer.Template as ControlTemplate;
var templateRootBorder = rowItemContainerTemplate.FindName("DGR_Border", rowItemcontainer) as Border;
var selectiveScrollingGrid = templateRootBorder.Child as Panel;
var cellsPresenter = selectiveScrollingGrid.Children.OfType<DataGridCellsPresenter>().First();
var cellItemContainer = cellsPresenter.ItemContainerGenerator.ContainerFromIndex(columnIndexToVisit) as DataGridCell;

DependencyObject cellVisualRoot = VisualTreeHelper.GetChild(cellItemContainer, 0);

4 (прямой доступ к столбцам сетки данных)

Должен быть самым быстрым и вдобавок не требует знания внутренностей.

int rowIndexToVisit = 0;
int columnIndexToVisit = 0;

DependencyObject rowItemContainer = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndexToVisit);
DataGridColumn column = dataGrid.Columns[columnIndexToVisit];
DependencyObject cellContent = column.GetCellContent(rowItemContainer);
DependencyObject cellVisualRoot = cellContent;
while ((cellContent = VisualTreeHelper.GetParent(cellContent)) is not DataGridCell)
{
  cellVisualRoot = cellContent;
}

Другие вопросы по теме