今天小编给大家分享一下自动扩张WPF树型表格列宽问题怎么解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
原问题如下:
图1 问题描述
背景
树型控件在GIX4系统中已经被大量使用。这个控件是一年前其它同事在网上搜索到,再引入的。
一开始的时候,要解决这个问题,想到的最直接的方案是这样的:找到***列中的Expander控件(加号:),然后监听它的“Expanded”事件;在事件处理程序中,计算所需要的宽度,然后设置为控件的宽度。
按照这个方案去实际写代码时,发现并没有想象中那么简单,发现了很多问题。例如,Expander并不是Expander控件,而是一个ToggleButton,而且是写在模板中的,TreeGridRowPresenter中的Expander的类型也只是UIElement,也就是说,不能把Expander从UIElement转换为ToggleButton,这样程序会写得很死。又如,如何计算***列的所需要宽度。
这些问题是要上面提及的BUG所需要解决的:
四个待解决的问题
何时触发是最合适的?在何处触发调整宽度的代码?
如何找到树型控件的所有GridViewRowPresenter。
GridViewRowPresenter中,如何把***列的控件找到。
***列控件的组成结构是怎么样的,它所需要的大小如何求出,是否可以直接使用Measure和DesiredSize。
一步一步解决
***个问题,何时触发这个功能?其实我是要在点击后,当子节点都加载好后,然后计算出合适的大小,再设置给列对象。我先在TreeListView的OnExpanded事件处理程序中尝试编写代码获取每一个TreeListView,但是发现这个事件在发生时,所有的子节点并没有生成,所以不能通过ItemContainerGenerator.GetContainerForItem方法获取到窗口,此方案失败。接着,我查看了ItemsControl的接口声明,发现ItemContainerGenerator属性有事件StatusChanged。所以我就改为监听这个事件,并判断如果当它的Status变为ContainersGenerated时,就表示所有子节点已经生成了。代码如下:
this.ItemContainerGenerator.StatusChanged += (o, e) => { if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { this.AdjustFirstColumnWidth(); } };
但是同样发现新的问题,这时候虽然窗口对象TreeListView已经生成,但是它下面的所有Visual Child都没有生成,这样同样无法获取到它里面用来显示每一行的GridRowPresenter。所以只有改成了这样:
public TreeListViewItem() { this.PrepareToAdjustFirstColumnWidth(); } private void PrepareToAdjustFirstColumnWidth() { this.ItemContainerGenerator.StatusChanged += (o, e) => { if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { if (this.Items.Count > 0) { var item = this.Items[this.Items.Count - 1]; var treeItem = this.ItemContainerGenerator.ContainerFromItem(item) as TreeListViewItem; treeItem.Loaded += (oo, ee) => { this.AdjustFirstColumnWidth(); }; } } }; }
这样,***一个孩子的可视内容都加载好后,才会触发调整宽度的代码。
第二个问题比较简单,看了TreeListView的源码后,发现它在TreeListViewItem类的模板中使用了GridViewRowPresenter类,然后为它定义了名字:“PART_Header”。
private TreeGridViewRowPresenter FindGridRow() { var rowPresenter = this.Template.FindName("PART_Header", this) as TreeGridViewRowPresenter; return rowPresenter; }
要解决第三个问题,我们需要知道GridViewRowPresenter中如何生成一行,并知道***生成的控件结构。先看看GridViewRowPresenter***生成的控件结构,这里我使用的是Snoop:
图2 用Snoop查看TreeGridViewRowPresenter的可视化结构
我们发现,GridViewRowPresenter下只是简单的包含了几个可视元素,它们刚好是每一列所显示的内容。再查看GridViewRowPresenter的源代码,发现它拥有以下属性:public GridViewColumnCollection Columns{get;set;}、internal UIElementCollection InternalCollection{get;set;},进一步分析后,我猜测性地得出以下结论:GridViewRowPresenter.InternalCollection简单地包含了所有列的显示元素,它会根据Columns属性中各行对这些可视元素进行维护,让它们显示得跟表格一样。
至此,第三个问题解决了:
var firstColumn = VisualTreeHelper.GetChild(rowPresenter, 0) as UIElement;
***一个问题,是过程中最麻烦的一个问题。我们看到,图2中该行下的***个元素是***列的显示元素,显示了“2.1”。但是文本左边的Expander控件却是TreeGridViewRowPresenter的***一个可视化孩子。而且缩进并不是一个控件。那么这是怎么一回事呢?看了TreeGridViewRowPresenter的源码后,发现原来是它主动把Expander放在了***:
public class TreeGridViewRowPresenter : GridViewRowPresenter { protected override System.Windows.Media.Visual GetVisualChild(int index) { // Last element is always the expander // called by render engine if (index < base.VisualChildrenCount) return base.GetVisualChild(index); if (index == base.VisualChildrenCount) return this.lbRowNo; return this.Expander; } protected override int VisualChildrenCount { get { // Last element is always the expander if (this.Expander != null) return base.VisualChildrenCount + 2; else return base.VisualChildrenCount + 1; } } }
而文本前面先显示缩进,然后再显示Expander的原因是由于TreeGridViewRowPresenter类重写了FrameworkElement.ArrangeOverride方法。在该方法中,它把***列的元素显示的长度变短在之前显示一段缩进的空白和Expander控件:
protected override Size ArrangeOverride(Size arrangeSize) { Size s = base.ArrangeOverride(arrangeSize); if (this.Columns == null || this.Columns.Count == 0) return s; UIElement expander = this.Expander; double current = 0; double max = arrangeSize.Width; for (int x = 0; x < this.Columns.Count; x++) { GridViewColumn column = this.Columns[x]; // Actual index needed for column reorder UIElement uiColumn = (UIElement)base.GetVisualChild((int)ActualIndexProperty.GetValue(column, null)); // Compute column width double w = Math.Min(max, (Double.IsNaN(column.Width)) ? (double)DesiredWidthProperty.GetValue(column, null) : column.Width); // First column indent if (x == 0 && expander != null) { double indent = FirstColumnIndent + expander.DesiredSize.Width; uiColumn.Arrange(new Rect(current + indent, 0, w - indent, arrangeSize.Height)); } else { uiColumn.Arrange(new Rect(current, 0, w, arrangeSize.Height)); } max -= w; current += w; } // Show expander if (expander != null) { expander.Arrange(new Rect(this.FirstColumnIndent, 0, expander.DesiredSize.Width, expander.DesiredSize.Height)); } return s; }
分析到这里,就知道如何计算出***列的最终宽度了:
private double GetFirstColumnDesiredWidth() { var rowPresenter = this.FindGridRow(); if (VisualTreeHelper.GetChildrenCount(rowPresenter) <= 0) return 0; //GridViewRowPresenter中的每一个元素表示一列。 var firstColumn = VisualTreeHelper.GetChild(rowPresenter, 0) as UIElement; var desiredWidth = firstColumn.DesiredSize.Width; //需要的宽度前,需要加上列的缩进和Expander的宽度。 var indent = rowPresenter.FirstColumnIndent + rowPresenter.Expander.DesiredSize.Width; return indent + desiredWidth + ENSURE_SIZE; }
以上就是“自动扩张WPF树型表格列宽问题怎么解决”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网行业资讯频道。