在前面一篇文章中,我通过对三种Infragistics 控件(UltraToolBarManager、UltraGird和UltraListView)进行扩展,以实现对ToolTip样式的定义,今天我来介绍如何采用相同的方式实现另外一个更为常用的功能:在UltraGrid的Header中动态添加CheckBox,从而实现对所有数据行进行全选的功能。[Source Code从这里下载]
一、我们的目标:在UltraGird的选择列的Header添加CheckBox实现对所有数据行的全选
我们现有的绝大部分UltraGird都具有如下图(点击查看大图)所示的结构:第一行为UnBound列,单元格中的CheckBox用于对当前行的选择,即通过勾选相应的CheckBox代表选中某一行。现在的新的要求是:在CheckBox列的列头添加一个总的CheckBox,用于选中所有数据行,即当勾选CheckBox时,下面所有数据行对应的均自动被勾选,反之,解除现有数据行对应的CheckBox的勾选状态。
熟悉Infragistics控件的朋友应该知道,UltraGird具有一个很有用的动态分局的特性:你可以将可被用于分组的列通过鼠标拖到最上方的区域(Drag a column header here to group by the column),那么UltraGird会自动为你将所有的数据行按照该列的值进行动态分组。这个分组功能为我们要扩展的UltraGird又增加了一个新的特性:如果在分组状态,需要在每一个分组行中添加CheckBox,该CheckBox用于对当前组范围内所有数据行的全选。最后的效果如右图(点击查看大图)所示。
二、关于UIElement和UIElementCreationFilter
在具体介绍实现的之前,我们先来谈谈相关的一些背景知识:关于UIElement和UIElementFilter。Infragistics 基于Windows Forms应用的控件具有非常高的扩展型。通过合理使用UIElement,开发者可以很容易地添加一些现有控件没有提供的功能。
基本上所有的Infragistics 控件(这里我们仅仅指基于Window Forms应用控件)都有一个具有层级关系的UIElement组成。比如,一个UltraTree 由一个UltraTreeUIElement构成,而一个UltraTreeUIElement又包含一组TreeNodeUIElement 对象和NodeConnectorUIElement对象。而TreeNodeUIElements又由一组PreNodeAreaUIElement 和NodeSelectableAreaUIElement组成。对于NodeSelectableAreaUIElements,其组成元素又包含两种:ExpansionIndicatorUIElements 和NodeTexUIElements。
所有的UIElement相关的操作,比如改变其显示风格和位置,可以通过两个Filter对象控制,即CreationFilter和DrawFilter。而CreationFilter还能用于为现有控件动态的添加或者移除子控件,我们将要实现的对于CheckBox的动态添加就是通过自定义CreationFilter实现的。
三、自定义UIElementCreationFilter实现对CheckBox的动态添加
对现有UltraGrid的扩展的核心在于自定义UIElementCreationFilter实现对CheckBox的动态添加,在具体介绍如何自定义UIElementCreationFilter之前,我们先看看我们扩展出来的UltraGrid的定义。从下面的代码片段可以看出,扩展控件ExtendedUltraGrid的定义其实很简单。其中,SelectAllColumnName表示CheckBox列的名称;而IsGroupMode属性表示当前是否处于分组模式;CheckState表示在未分组情况下Select-All CheckBox应有的状态;在构造函数中,我们指定了UltraGrid的CreationFilter属性。
1: using System.Windows.Forms;
2: using Infragistics.Win.UltraWinGrid;
3:
4: namespace Artech.ExtendedUltraGrid4SelectAll
5: {
6: public class ExtendedUltraGrid : UltraGrid
7: {
8: public string SelectAllColumnName{get;set;}
9:
10: internal bool IsGroupMode
11: {
12: get
13: {
14: foreach (var row in this.Rows)
15: {
16: if (row.IsGroupByRow)
17: {
18: return true;
19: }
20: }
21: return false;
22: }
23: }
24:
25: internal CheckState CheckState{ get; set; }
26:
27: public ExtendedUltraGrid()
28: {
29: this.CreationFilter = new CheckableHeaderCreationFilter(this);
30: }
31: }
32: }
在构造函数中指定的Creation属性就是我们上面介绍的自定义的UIElementCreationFilter:CheckableHeaderCreationFilter。所有的UIElementCreationFilter均实现接口IUIElementCreationFilter,该接口具有两个方法BeforeCreateChildElements和AfterCreateChildElements。
1: using Infragistics.Win;
2: public class MyUltraExplorerBarCreationFilter : IUIElementCreationFilter
3: {
4: public void AfterCreateChildElements(UIElement parent)
5: {
6: // called for a UIElement (parent) after it creates its child elements.
7: }
8: public bool BeforeCreateChildElements(UIElement parent)
9: {
10: // called for a UIElement (parent) before it creates its child elements.
11: return false;
12: }
13: }
在CheckableHeaderCreationFilter中,我们将在Select列的列头添加CheckBox的操作实现在AfterCreateChildElements方法中。其主要的逻辑是:通过parent的类型(必须是HeaderUIElement)、Column的类型(比如是Boolean)和Column Name(必须是在ExtendedUltraGrid的SelectAllColumnName属性指定)确定parent正是我们需要位置添加Check子控件的UIElement。然后创建CheckBoxUIElement,并将其添加到parent中,并通过Rect属性确定其显示的位置。然后我们会根据分组行(UltraGridGroupByRow)的Tag(这个会在自定义CheckBoxUIElement中设置)设置新创建的CheckBoxUIElement的CheckState状态,如果没有在分组模式下,我们根据ExtendedUltraGrid的CheckState属性指定该CheckBoxUIElement的状态。
1: using System;
2: using System.Collections.Generic;
3: using System.Drawing;
4: using System.Windows.Forms;
5: using Infragistics.Win;
6: using Infragistics.Win.UltraWinGrid;
7:
8: namespace Artech.ExtendedUltraGrid4SelectAll
9: {
10: public class CheckableHeaderCreationFilter : IUIElementCreationFilter
11: {
12: public ExtendedUltraGrid Control
13: { get; private set; }
14:
15: public CheckableHeaderCreationFilter( ExtendedUltraGrid control)
16: {
17: if (null == control)
18: {
19: throw new ArgumentNullException("control");
20: }
21: this.Control = control;
22: }
23:
24: public void AfterCreateChildElements(UIElement parent)
25: {
26: //Filter the HeaderUIElement element.
27: HeaderUIElement headerUIElement = parent as HeaderUIElement;
28: if (null == headerUIElement)
29: {
30: return;
31: }
32:
33: //Filter by DataType and SelectAll column name.
34: if (headerUIElement.Header.Column.DataType != typeof(bool) || headerUIElement.Header.Column.Key != this.Control.SelectAllColumnName)
35: {
36: return;
37: }
38:
39: //Check the exitence of CheckBoxUIElement.
40: //If not, create a new one and wrap it with a WeakReference.
41: CheckBoxUIElement checkBoxUIElement = parent.GetDescendant(typeof(CheckBoxUIElement)) as CheckBoxUIElement;
42: if (null == checkBoxUIElement)
43: {
44: checkBoxUIElement = new ExtendedCheckBoxUIElement(parent);
45: }
46: //Add the CheckBoxUIElement and set its position.
47: parent.ChildElements.Add(checkBoxUIElement);
48: checkBoxUIElement.Rect = new Rectangle(
49: parent.Rect.X + (parent.Rect.Width - checkBoxUIElement.CheckSize.Width) / 2 + 1,
50: parent.Rect.Y + ((parent.Rect.Height - checkBoxUIElement.CheckSize.Height) / 2),
51: checkBoxUIElement.CheckSize.Width,
52: checkBoxUIElement.CheckSize.Height
53: );
54: //For GroupRow, set the Tag as the current CheckState.
55: UltraGridRow ultraGridRow = headerUIElement.GetContext(typeof(UltraGridRow)) as UltraGridRow;
56: UltraGridGroupByRow parentRow = ultraGridRow.ParentRow as UltraGridGroupByRow;
57: if (null != parentRow && null != parentRow.Tag)
58: {
59: checkBoxUIElement.CheckState = (CheckState)parentRow.Tag;
60: }
61:
62: if (!this.Control.IsGroupMode)
63: {
64: checkBoxUIElement.CheckState = this.Control.CheckState;
65: }
66: }
67:
68: public bool BeforeCreateChildElements(UIElement parent)
69: {
70: return false;
71: }
72: }
73: }
四、自定义CheckBoxUIElement
从CheckableHeaderCreationFilter的定义我们可以看到:动态添加的CheckBoxUIElement的类型为ExtendedCheckBoxUIElement,这是我们自定义的类型。我们通过该类型来设置分组行或者整个UltraGrid(没有在分组模式下)应有的状态,并最终对相应的数据行(在分组模式下为当前分组的所有行,而没有分组情况下为整个UltraGrid的所有行)的Check状态。所有的实现具有体现在重写的OnCheckStateChange上面。
1: using System.Windows.Forms;
2: using Infragistics.Win;
3: using Infragistics.Win.UltraWinGrid;
4:
5: namespace Artech.ExtendedUltraGrid4SelectAll
6: {
7: public class ExtendedCheckBoxUIElement : CheckBoxUIElement
8: {
9: public ExtendedCheckBoxUIElement(UIElement parent)
10: : base(parent)
11: {
12: this.ThreeState = false;
13: }
14:
15: public override CheckState CheckState
16: {
17: get
18: {
19: return base.CheckState;
20: }
21: set
22: {
23: CheckState checkState = this.CheckState;
24: base.CheckState = value;
25: if (checkState != value)
26: {
27: this.OnCheckStateChange();
28: }
29: }
30: }
31:
32: protected override void OnCheckStateChange()
33: {
34: base.OnCheckStateChange();
35: ExtendedUltraGrid ultraGrid = this.Control as ExtendedUltraGrid;
36: HeaderUIElement headerUIElement = this.GetAncestor(typeof(HeaderUIElement))as HeaderUIElement;
37: UltraGridRow ultraGridRow = headerUIElement.GetContext(typeof(UltraGridRow))as UltraGridRow;
38: UltraGridGroupByRow parentRow = ultraGridRow.ParentRow as UltraGridGroupByRow;
39: if (null != parentRow)
40: {
41: parentRow.Tag = this.CheckState;
42: this.SetAllGridRowSelected(parentRow.Rows, this.CheckState);
43: }
44: else
45: {
46: this.SetAllGridRowSelected((this.Control as UltraGrid).Rows, this.CheckState);
47: }
48:
49: if (!ultraGrid.IsGroupMode)
50: {
51: ultraGrid.CheckState = this.CheckState;
52: }
53: }
54:
55: private void SetAllGridRowSelected(RowsCollection rows, CheckState state)
56: {
57: foreach (var row in rows)
58: {
59: if (row.IsGroupByRow)
60: {
61: row.Tag = this.CheckState;
62: SetAllGridRowSelected(((UltraGridGroupByRow)row).Rows, state);
63: }
64: else
65: {
66: string selectAllColumnName = (this.Control as ExtendedUltraGrid).SelectAllColumnName;
67: if (row.Activation == Activation.Disabled) continue;
68: if (!row.Band.Columns.Exists(selectAllColumnName)) continue;
69: if (row.Band.Columns[selectAllColumnName].CellActivation == Activation.Disabled) continue;
70: if (null == row.Cells) continue;
71: row.Cells[selectAllColumnName].Value = state;
72: row.Update();
73: }
74: }
75: }
76: }
77: }
P.S. 上面的例子只是提供了一个解决问题的思路,还有一些细节问题。比如,当我应用了一些风格文件之后,发现每当鼠标移至UltraGrid的Select列的列头时,CheckableHeaderCreationFilter的AfterCreateChildElements方法会被调用,并且总是会导致新的CheckBoxUIElement被创建。但是一旦我将不采用风格文件,就不是出现这样的问题。这个问题也同样出现在Infragistics 官方提供的解决方案中,有兴趣的朋友可以从这里下载,并通过StyleManager.Load方法加载某个风格文件,通过Debug你会发现CheckBoxUIElement被频繁创建。Infragistics 提供的例子和我对UltraGrid的扩展方式,本质上是一致的,虽有被创建出来的CheckBoxUIElement会成为垃圾对象,可以被垃圾回收,但是频繁的创建这样的对象总归会对内存造成一定的压力。