首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >WPF -从DataGridRow步行到没有VisualTreeHelper的DataGridCell

WPF -从DataGridRow步行到没有VisualTreeHelper的DataGridCell
EN

Stack Overflow用户
提问于 2022-05-16 18:31:27
回答 1查看 79关注 0票数 -1

当我调试我的WPF应用程序(.NET Framework4.8)时,我看到了这张图片。

我想出了这棵树的一部分

现在我需要从DataGridCellsPresenter DataGridCell 走到DataGridCell数组.

我必须硬编码 it -请不要告诉我,我没有-这是手头的任务。我必须使用硬编码路径到达_b,而不使用任何VisualTreeHelpers等等。

版本1 -使用传统的树遍历:

代码语言:javascript
复制
child = UIHelper.FindChild<Grid>(row, "_b");

版本2 -硬编码树行走

代码语言:javascript
复制
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 vs 2(在我的机器上滴答作响),即版本2快750%:

代码语言:javascript
复制
FindChild: 12,845,015
Hardcode: 1,706,232

你能提供一个更快的方法吗?

我不知道如何摆脱GetChild --许多方法和属性都是受保护的或私有的。

FindChild:

代码语言:javascript
复制
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;
}

编辑:最终解决方案提高了1100%的效率:

代码语言:javascript
复制
DependencyObject cellContent = bColumn.GetCellContent(row);
child = VisualTreeHelper.GetParent(VisualTreeHelper.GetParent(cellContent)) as Grid;

其中rowDataGridRow,我在LoadingRow事件中有,而bColumn是我想要修饰的已知列

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-05-17 14:09:40

“慢”的并不是VisualTreeHelper。这是遍历树以找到目标元素的方式。使用VisualTreeHelper“适当”将显着地改进搜索。

遍历树的数据结构有不同的算法。当前版本实现了预顺序遍历:它访问从根到叶的分支的每个节点。在最坏的情况下,目标是最后一枝的叶子。此时,您已经访问了树的每个节点。

根据场景的不同,不同的算法或它们的组合可以更好地执行。

最好的情况是,您知道树,并且可以依赖它的节点顺序来保持常量。

但是一般来说,如果你不是树的建设者,你就不能依靠树的排列来保持常量。

根据视觉树的性质,如果期望树不太宽且目标节点不是叶,则可以假定宽度优先搜索比宽扩展预序搜索性能好得多。

根据观察,我们可以假设视觉树趋向于深度而不是宽度的增长。大多数容器只有一个孩子。像Grid这样的容器通常没有很多列,即包含许多兄弟姐妹。

这意味着,在这个假设的基础上,预序搜索获得了最坏的结果,因为它将逐个分支地沿着完整的深度走下去。

因此,在进入下一个级别之前检查节点的同级可能会比先沿着完整的分支下降所带来的伤害小。

因此,在满足先前假设的情况下,广度优先遍历必须优于常规的预顺序遍历。

如果访问一个节点是一个ItemsControl (包含大量的兄弟节点),我们可以使用ItemContainerGenerator获得子树。这样,我们还可以确保在启用UI虚拟化的情况下访问每个容器。此场景不是针对的,只需要将感兴趣的项放到视图中(以触发容器实现)。

下面是四个算法的示例,这些算法的性能要好于常规的预排序搜索(在这个给定的场景中)。遍历分为两个步骤,以避免不必要的分支遍历:首先查找DataGridCell主机(一个ItemsControl),然后使用ItemContainerGenerator查找根元素。使用ItemContainerGenerator将进一步提高搜索性能。

我没有衡量他们的效率,但根据考试,我给了他们一个排名。

更高的数字意味着更好的表现。2和3可能转换立场。所有示例都试图找到第一行的第一个单元格:

1(预购)

常见的预顺序遍历。

这种情况需要了解模板(元素名称)。

代码语言:javascript
复制
int rowIndexToVisit = 0;
int columnIndexToVisit = 0;

DependencyObject rowItemContainer = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndexToVisit);
if (TryFindVisualChildElementByName(rowItemContainer, "_b", out Border border))
{  
  DependencyObject cellVisualRoot = border;
}
代码语言:javascript
复制
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(宽度第一/w ItemContainerGenerator)

需要了解DataGrid类型层次结构(特别是DataGridCellsPresenterDataGridCell项宿主)。

代码语言:javascript
复制
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);
}
代码语言:javascript
复制
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有准确的了解

代码语言:javascript
复制
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(直接访问数据网格列)

应该是最快的,而且不需要任何的内部知识。

代码语言:javascript
复制
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;
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72263999

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档