简体   繁体   English

WPF - 在没有 VisualTreeHelper 的情况下从 DataGridRow 走到 DataGridCell

[英]WPF - walk from DataGridRow to DataGridCell without VisualTreeHelper

I see this picture when I debug my WPF app (.NET Framework 4.8)我在调试 WPF 应用程序(.NET Framework 4.8)时看到了这张图片

I figured out part of the tree:我想出了树的一部分:

Now I need to walk down from DataGridCellsPresenter to DataGridCell array.现在我需要从DataGridCellsPresenter走到DataGridCell数组。

I have to hardcode it - please refrain from telling me that I dont - its the task at hand.我必须对其进行硬编码——请不要告诉我我没有——这是手头的任务。 I must get to _b with hardcoded path without using any VisualTreeHelpers etc.我必须在不使用任何VisualTreeHelpers等的情况下使用硬编码路径到达_b

在此处输入图像描述

version 1 - using conventional tree walking:版本 1 - 使用传统的树行走:

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

version 2 - hardcoded tree walking版本 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 iterations version 1 vs 2 (ticks on my machine) ie version 2 is 750% faster: 1,000,000 次迭代版本 1 与 2(我的机器上的滴答声)即版本 2 快 750%:

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

Can you offer a faster method?你能提供一个更快的方法吗?

I cant figure how to get rid off of GetChild - lots of methods and properties are protected or private.我不知道如何摆脱GetChild - 许多方法和属性是受保护的或私有的。


FindChild:查找孩子:

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

EDIT: final solution has 1100% efficiency boost:编辑:最终解决方案具有 1100% 的效率提升:

DependencyObject cellContent = bColumn.GetCellContent(row);
child = VisualTreeHelper.GetParent(VisualTreeHelper.GetParent(cellContent)) as Grid;

where row is DataGridRow I have in LoadingRow event, and bColumn is a known column I want to adorn其中row是我在LoadingRow事件中拥有的DataGridRow ,而bColumn是我想要装饰的已知列

It's not the VisualTreeHelper that is "slow". “慢”的不是VisualTreeHelper It's the way you traverse the tree to find the target element.这是您遍历树以查找目标元素的方式。 Using the VisualTreeHelper "properly" will improve the search significantly. “正确”使用VisualTreeHelper将显着改善搜索。

There are different algorithms to traverse a tree data structure.遍历树数据结构有不同的算法。 Your current version implements Pre-order traversal: it visits every node of a branch from root to the leaf.您当前的版本实现了预购遍历:它访问从根到叶的分支的每个节点。 In the worst case, the target is the leaf of the last branch.在最坏的情况下,目标是最后一个分支的叶子。 At this point, you have already visited every node of the tree.此时,您已经访问了树的每个节点。

Depending on the scenario a different algorithms or combinations of them can perform better.根据场景,不同的算法或它们的组合可以表现得更好。

The best scenario is, where you know the tree and can rely on its node order to be constant.最好的情况是,您知道树并且可以依靠其节点顺序保持不变。
But in general, if you are not the builder of the tree, you can't rely on the tree arrangement to be constant.但总的来说,如果你不是树的建造者,你就不能依靠树的排列保持不变。
Based on the nature of the visual tree, we can assume that a Breadth First search performs much better than the wide spread Pre-orde search, if the expected tree is not too wide and the target node is not a leaf.基于可视树的性质,如果预期树不是太宽且目标节点不是叶子,我们可以假设广度优先搜索比广泛传播的 Pre-orde 搜索执行得更好。
Based on observation, we can assume that the visual tree tends to grow in depth rather than in width.根据观察,我们可以假设视觉树倾向于在深度而不是宽度上增长。 Most containers have a single child.大多数容器都有一个孩子。 Containers like the Grid usually don't have many columns ie contain many siblings.像 Grid 这样的容器通常没有很多列,即包含很多兄弟。

This means, that based on this assumptions, the Pre-order search achieves the worst results as it will walk down the complete depth branch by branch.这意味着,基于此假设,预排序搜索会获得最差的结果,因为它将逐个分支遍历完整的深度分支。
Thus, inspecting the siblings of a node before entering the next level will potentially hurt less than going down the complete branch first.因此,在进入下一个级别之前检查一个节点的兄弟姐妹可能会比先走完整个分支带来的伤害要小。
A Breadth First traversal must therefore outperform the common Pre-order traversal in a scenario where the previous assumptions are met.因此,在满足先前假设的情况下,广度优先遍历必须优于常见的预排序遍历。

In case of visiting a node that is an ItemsControl (containing a potentially high number of siblings), we obtain the child tree by using the ItemContainerGenerator .如果访问一个ItemsControl节点(可能包含大量同级),我们使用ItemContainerGenerator获取子树。 This way, we can also ensure to visit every container, in a scenario where UI virtualization is enabled.这样,我们还可以确保在启用 UI 虚拟化的场景中访问每个容器。 This scenario is not targeted and would only require to bring the item of interest into view (to trigger container realization).这个场景没有针对性,只需要将感兴趣的项目带入视野(以触发容器实现)。

Below are a four examples of algorithms that perform better than the common Pre-order search (in this given scenario).下面是四个比常见的预购搜索(在这个给定的场景中)表现更好的算法示例。 Traversal is split into two steps to avoid unnecessary branch traversal: find the DataGridCell host (an ItemsControl ) first and then use the ItemContainerGenerator to find the root element.遍历分为两个步骤以避免不必要的分支遍历:首先找到DataGridCell主机(一个ItemsControl ),然后使用ItemContainerGenerator找到根元素。 Using the ItemContainerGenerator will further improve the search performance.使用ItemContainerGenerator将进一步提高搜索性能。

I have not measured their efficiency, but based on examination, I gave them a ranking.我没有衡量他们的效率,但根据考试,我给了他们一个排名。
A higher number means a better performance.更高的数字意味着更好的性能。 2 and 3 might switch positions. 23可能会互换位置。 All examples try to find the first cell of the first row:所有示例都试图找到第一行的第一个单元格:

1 (Pre-order) 1(预购)

The common Pre-order traversal.常见的预购遍历。
This case requires knowledge of the template (element name).这种情况需要知道模板(元素名称)。

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 (Breadth First /w ItemContainerGenerator ) 2(广度优先/w ItemContainerGenerator

Requires knowledge of the DataGrid type hierarchy (in particular that DataGridCellsPresenter is the DataGridCell items host).需要了解DataGrid类型层次结构(特别是DataGridCellsPresenterDataGridCell项的宿主)。

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 (manual logical tree) 3(手动逻辑树)

Requires exact knowledge of the ControlTemplate :需要准确了解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 (access data grid columns directly) 4(直接访问数据网格列)

Should be the fastest and in addition doesn't require any knowledge of the internals.应该是最快的,而且不需要任何内部知识。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM