Page tree
Skip to end of metadata
Go to start of metadata

你可以使用 C1FlexGrid 控件来对一个表格格式中的数据进行显示,编辑,组和总结。该表格可以绑定到一个数据源,它可以对自己的数据进行管理。
C1FlexGrid 控件有一个包含以下元素的丰富的对象模型:

以下的几个标题将引导你熟悉 C1FlexGrid 控件的几个主要特点:行与列(第 41 页)
介绍了应该如何设置表格尺寸和布局。
单元格选择(第 43 页)
介绍了"当前单元格"和"选择"的概念。

单元格区域

介绍了应该如何将一个单元格的集合作为一个单元来操作。
单元格图像介绍了应该如何在一个单元格中显示图像。
设置单元格格式
介绍了应该如何通过设置数字、日期和布尔值的格式,或为一个单个的单元格或一个单元格的集合改变字体、颜色、对齐方式来自定义表格的外观。

编辑单元格

介绍了应该如何实现简单的文字编辑,下拉列表和组合列表,单元格按钮,编辑掩码和数据验证。合并单元格
介绍了应该如何通过建立"分类"意见来突出数据的关系并更改表格的显示,以便使具有类似内容的单元格合并到一起。
概述和汇总数据
介绍了应该如何将分类汇总添加到表格和如何建立一个树形大纲。
保存,加载,并打印
介绍了你应该如何对一个表格的内容或格式进行保存,并在稍后重新加载,或利用其他应用程序,例如 Microsoft Access 和 Excel,来互换表格的数据。本节还介绍了你应该如何对表格进行打印。
C1FlexGrid 属性组
介绍了一个按功能交叉引用的 C1FlexGrid 主要属性的图。

行和列

一个 C1FlexGrid 控件包括由行和列。行和列的集合受到行和列属性的影响。
当把表格绑定到一个数据源,行和列的数目取决于可用的数据源中的数据有多少。在绑定模式下,你可以使用集合的 Count 属性将它们设置为任意值。
例如,下面的代码将表格的尺寸设置为 500 行乘以 10 列:

  • Visual Basic

_flex.Rows.Count = 500
_flex.Cols.Count = 10

  • C#


     flex.Rows.Count = 500;
     _flex.Cols.Count = 10;
行和列有两个基本类型:固定的和滚动的。(通过 Count 属性被还原的数值包括了固定的和滚动的单元格)。当用户垂直滚动表格时,固定行停留在表格顶部;而当用户水平滚动表格时,固定列停留在表格左侧。

固定的单元格用于显示行和列的标题信息。

你可以通过使用行和列的集合中"固定的"属性来设置固定的行和列的数目。例如,下面的代码可以创建一个具有两个固定行和无固定列的表格:

  • Visual Basic

flex.Rows.Fixed = 1
_flex.Cols.Fixed = 0

  • C#

      flex.Rows.Fixed = 1;
      _flex.Cols.Fixed = 0;
行和列的集合也包含了在表格中插入、删除和移动行和列的方法。你可以使用"条目"属性(索引)来访问在每个集合中的独立的各个元素(行和列)。
只要你愿意,你就可以在设计时通过使用 C1FlexGrid 列编辑器来设置好表格列,而不是靠编写代码来执行这一步操作。有关如何使用 C1FlexGrid 列编辑器来对列进行编辑的更多详细信息,请参阅 C1FlexGrid 列编辑器。

列宽度

列的宽度取决于它的"宽度"属性。在设计时,可以在表格中直接设置

"宽度"属性,或通过 C1FlexGrid 列编辑器来进行。在表格中,单击并拖动出现在列标题的右边缘的水平的双箭头可以重新调整列的尺寸。

垂直的虚线显示了表格将会如何被重新调整尺寸。向左拖动指针使列宽变小;将其向右边拖动使列宽变大。当调整的操作完成,列的"宽度"属性将被调整。
C1FlexGrid 列编辑器中或代码中,为列指定"宽度"属性值。有关
C1FlexGrid 列编辑器的更多详细信息,请参阅"C1FlexGrid 列编辑器"。下面的代码可以设置第一列到第十列的 "宽度"的属性:

  • Visual Basic

    _flex.Cols(1).Width = 10

  • C#
      _flex.Cols[1].Width = 10;



为了防止一个特定的列被调整,请在无论是列任务菜单上,还是列编辑器或代码中,都将这一列的"允许调整"属性设置为"假"。有关列任务菜单的更多详细信息,请参阅"列任务菜单"(第 36 页)。下面的代码可以将第一列的"允许调整"属性设置为"假":


  • Visual Basic

    _flex.Cols(1).AllowResizing = False

  • C#
      _flex.Cols[1].AllowResizing = false;



单元格选择

表格中有一个光标单元格,当表格时是激活的,它会显示一个焦点矩形。当表格是可编辑的,用户可以使用键盘或鼠标来移动这个光标并且编辑单元格的内容。

请注意,Office Visual Styles 也会通过高亮显示光标单元格所在的行和列标题,来显明光标单元格的位置。要想了解更多有关设置"视觉样式"的详细信息,请参阅"自定义外观,使用视觉样式"。
你可以在代码中通过使用"行与列"属性来获取或设置当前单元格。例如,将任一属性设置为-1,可以隐藏光标。
表格支持扩展选择,单元格的矩形范围由两个对立的角落决定,即光标所在单元格和单元格选择的单元格。

请注意,Office Visual Styles 也通过高亮显示选定的单元格所在的行和列的标题来显明了扩展选择的位置。要想了解更多有关设置"视觉样式"的详细信息,请参阅"自定义外观,使用视觉样式"。
你可以在代码中通过使用"行选择"和"列选择"属性来获取或设置单元格选择,或通过使用"选择"的方法也可以。注意:当光标单元格变化了,该"选择"会自动复位。要在代码中创建扩展选项,无论是通过在"行选择"和"列选择"之前先设置"行与列"或直接使用"选择"的方法都可以。
"选择"的外观是由以下属性决定的:
定焦矩形决定了焦点矩形的类型是为了表明光标单元格而绘制的。
高亮显示决定了什么时候该"选择"应该被突出显示(通常是,当该控件有一个突出的重点时,或者从不)。
高亮和定焦都是决定了"选择"的外观的单元格样式(字体,颜色和边框)。
可供选择的类型是由"选择模式"的属性决定的。在默认情况下,表格支持常规的范围选择。你可以修改此行为来防止扩展选择,或者来按行、按列,或在列表框模式下进行选择("列表框模式"可以允许你来选择个别行)。
当使用列表框选择模式时,你可以通过使用"选择"属性来获取或设置个别行的选择状态。你还可以通过使用"选择"属性来恢复选定的行的集合。例如,下面的代码可以选择符合一个条件的所有行:

  • Visual Basic

' 在"销售"列中选择多于 8000 名销售的所有行。
_flex.SelectionMode = C1.Win.C1FlexGrid.SelectionModeEnum.ListBox
Dim index As Integer
For index = _flex.Rows.Fixed To _flex.Rows.Count - 1
If Val(_flex(index, "Sales")) > 80000 Then
_flex.Rows(index).Selected = True
End If
Next
Console.WriteLine("There are now {0} rows selected",
_flex.Rows.Selected.Count)

  • C#

// 在"销售"列中选择多于 8000 名销售的所有行。
_flex.SelectionMode = SelectionModeEnum.ListBox; for (int index = _flex.Rows.Fixed ; index < _flex.Rows.Count; index++)
{

  if (Microsoft.VisualBasic.Conversion.Val(System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(_flex\[index, "Sales"\])) > 80000)

  {

   _flex.Rows[index].Selected = true;

  }
}
Console.WriteLine("There are now {0} rows selected",
_flex.Rows.Selected.Count);



单元格区域

"单元格区域"对象可以使你将单元格的任意组合作为一个简单的单元来工作。例如,下面的代码创建了一个"单元格区域"的对象,清除了范围内的数据,并赋予了它一个自定义样式:

  • Visual Basic

    Dim rg As CellRange = _flex.GetCellRange(3, 3, 10, 10) rg.Data = Nothing rg.Style = _flex.Styles("MyRangeStyle")

  • C#
      CellRange rg = _flex.GetCellRange(3, 3, 10, 10);  rg.Data = null;  rg.Style = _flex.Styles["MyRangeStyle"];


"单元格区域"的对象具有可以恢复区域样式的 StyleNew 的属性, 如果存在一个,或创建了一个新的,将其分配给该区域并返回它。在你不需要对格式进行全面控制的情况下,此属性是很方便的。例如,如果你想要做的是给这个区域设定一个红色的背景,你可以编写如下代码来实现:


  • Visual Basic

    Dim rg As CellRange = _flex.GetCellRange(3, 3, 10, 10) rg.StyleNew.BackColor = Color.Red

  • C#

    CellRange rg = _flex.GetCellRange(3, 3, 10, 10); rg.StyleNew.BackColor = Color.Red;

单元格图像

每个表格内的单元格除了在单元格中存储数据以外还都可以显示图像。这可以通过两种方式来实现:你可以通过使用"设置单元格图像"的方法,来将一个"图像"对象分配到一个单元格。这种方法可以允许你指定任意图像到每个单元格,并且当图像与单元格中的数据没有什么关系时这是很有用的。例如,你可能想使用图片作为一项指标来表明单元格中的数据是无效的。
你可以将一个"图像映射"指定到一列,表格就会将单元格中的数据自动映射到一个相应的图像。当图像包含了代表性的数据的情况下,这种方法是很有用的。例如,图像可能含有表示产品类型的图标。

设置单元格格式

C1FlexGrid 控件的主要优势之一就是,具有自定义整个表格和每个个体的单元格外观的几乎任一方面的能力。

单元格的内容

为了控制单元格的内容如何被格式化,请将"格式化"属性设置到一个类似于你在.NET framework 中通过 String.Format 方法使用的格式字符串。例如,下面的代码显示了第一列的短日期和第二列的货币值:

  • Visual Basic

 ' 短日期。
 _flex.Cols(1).Format = "d"
 ' 货币值。
 _flex.Cols(2).Format = "c"

  • C#
    // 短日期
    _flex.Cols\[2\].Format = "d";
    // 货币值
    _flex.Cols\[1\].Format = "c";

单元格内容的格式也是可以通过使用格式字符串对话框在设计时就进行设置的。格式字符串对话框的访问路径有两种,一种是可通过列任务菜单。另一种是可通过 C1FlexGrid 列编辑器。在"列任务"菜单上,单击"格式字符串"中的省略号按钮。
在 C1FlexGrid 列编辑器中,在左窗格中找到"格式"属性,并单击它旁边的省略号按钮。
注意:格式字符串对话框是特定列,而且只会更改所选定的列的"格式" 属性。
你还是可以使用自定义格式,就像你在 Visual Basic "格式"功能中所使用
的一样(例如,"#,###",等等)。
检索单元格数据你可以使用索引或"获取数据"方法来检索原始的表格数据。要检索格式
过的数据,请改为使用 GetDataDisplay 的方法。例如:


  • Visual Basic

' 短日期。
_flex.Cols(1).Format = "d"
' 货币值。
_flex.Cols(2).Format = "c"
_flex(1, 2) = 10000
Console.WriteLine("Raw value: {0}", _flex(1, 2))
Console.WriteLine("Display value: {0}", _flex.GetDataDisplay(1, 2)) ' 行值:10000

  • C#

// 短日期。

_flex.Cols[1\].Format = "d";


// 货币值。

_flex.Cols[2].Format = "c";
_flex[1, 2] = 10000;
Console.WriteLine("Raw value: {0}", _flex[1, 2]);

Console.WriteLine("Display value: {0}", _flex.GetDataDisplay(1, 2));
// 行值:10000


单元格的外观

单元格的外观(对齐方式、字体、颜色、边框,等)是由"单元格样式" 对象处理的。表格有一个"样式"属性,它包含了用于设置单元格格式的所有样式的集合。这个集合有一些可以定义表格元素的外观的内置成员,如固定的和滚动的单元格,单元格选择,单元格定焦,等等。你可以更改这些样式来修改表格的外观,你也可以创建自己的自定义样式,并将它们分配给单元格,行或列。
改变内置的样式是改变表格外观的最简单的方式。例如,下面的代码可以
将所选定的部分的背景显示为红色,字体为绿色粗体:

  • Visual Basic

    Dim cs As CellStyle = _flex.Styles.Highlight cs.Font = New Font(_flex.Font, FontStyle.Bold) cs.ForeColor = Color.Green cs.BackColor = Color.Red

  • C#

    CellStyle cs = _flex.Styles.Highlight; cs.Font = new Font(_flex.Font, FontStyle.Bold); cs.ForeColor = Color.Green; cs.BackColor = Color.Red;
你还可以创建你自己的样式,并将它们分配到单元格,行和列。例如,下
面的代码可以创建一个自定义单元格样式并敬爱那个它分配给每一个第五行:

  • Visual Basic

   Dim cs As CellStyle = _flex.Styles.Add("Fifth") cs.BackColor = Color.Gray
   Dim idex%
   For idex = _flex.Rows.Fixed To _flex.Rows.Count - 1 Step 5
   _flex.Rows(idex).Style = cs Next 

  • C#

CellStyle cs = _flex.Styles.Add("Fifth"); cs.BackColor = Color.Gray; for (int index = _flex.Rows.Fixed ; index <= _flex.Rows.Count - 1; index += 5)
{

_flex.Rows[index].Style = cs;


下面是一个例子,显示了如何创建自定义样式,并将它们分配到行,列和单元格区域:


  • Visual Basic

' 创建一个新的自定义样式。
Dim s As CellStyle = _flex.Styles.Add("MyStyle") s.BackColor = Color.Red
s.ForeColor = Color.White
' 将新的样式分配到一列。
_flex.Cols(3).Style = _flex.Styles("MyStyle")
' 将新的样式分配到一行。
_flex.Rows(3).Style = _flex.Styles("MyStyle")
' 将新的样式分配到一个单元格区域。
Dim rg As CellRange = _flex.GetCellRange(4, 4, 6, 6) rg.Style = _flex.Styles("MyStyle")
 C#
// 创建一个新的自定义样式。
CellStyle s = _flex.Styles.Add("MyStyle"); s.BackColor = Color.Red;
s.ForeColor = Color.White;
// 将新的样式分配到一列。

_flex.Cols[3].Style = _flex.Styles["MyStyle"];


// 将新的样式分配到一行。

_flex.Rows[3].Style = _flex.Styles["MyStyle"];


// 将新的样式分配到一个单元格区域。

CellRange rg = _flex.GetCellRange(4,4,6,6); rg.Style = _flex.Styles["MyStyle"];


只要你愿意,你就可以在设计时使用 C1FlexGrid 样式编辑器来设置好样

式,而不是靠编写代码来执行这一步操作。有关如何使用 C1FlexGrid 样式编辑器来自定义单元格的外观的更多详细信息,请参阅"C1FlexGrid 样式编辑器" (第 27 页)。

有条件地设置格式

要根据单元格的内容设置其格式,你可以基于其内容使用"单元格变更"
事件来为单元格选择一种样式。例如,下面的代码可以根据单元格的内容为
"大货币值"创建一个新的样式,并将其应用于单元格:

  • Visual Basic

    Dim cs As C1.Win.C1FlexGrid.CellStyle
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    ' 为"大货币值"创建一个自定义样式。
    cs = _flex.Styles.Add("LargeValue") cs.Font = New Font(Font, FontStyle.Italic) cs.BackColor = Color.Gold End Sub
    ' 根据单元格的内容来设置其格式。
    Private Sub _flex_CellChanged(ByVal sender As Object, ByVal e As
    RowColEventArgs) Handles _flex.CellChanged
    ' 将货币值大于 50,000 的标记为"大货币值"(通过将"样式"设置为 "无"来重新设置其他的)。
    Dim cs As CellStyle
    If _flex(e.Row, e.Col).ToString >= 50000 Then cs = _flex.Styles("LargeValue")
    _flex.SetCellStyle(e.Row, e.Col, cs)
    End If

  • C#

    CellStyle cs; private void Form1_Load(object sender, EventArgs e)
    {
    // 为大货币值创建一个自定义样式。
    cs = _flex.Styles.Add("LargeValue"); cs.Font = new Font(Font, FontStyle.Italic); cs.BackColor = Color.Gold;
    }
    // 根据单元格的内容来设置其格式。
    private void _flex_CellChanged( object sender, RowColEventArgs e)
    {
    // 将货币值大于 50,000 的标记为"大货币值"(通过将"样式"设置为
    "无"来重新设置其他的)。 if

    (Microsoft.VisualBasic.CompilerServices.Conversions.ToDouble(_flex[e.Row, e.Col].ToString()) >= 50000)


    {

    cs = _flex.Styles["LargeValue"];


    _flex.SetCellStyle(e.Row, e.Col, cs);
    }


    自绘单元格

    即使 "单元格样式"对象已经通过单元格外观(背景色和前景色、对齐方式、字体、边距、边框,等等)提供了很多控件,有时这仍是不够的。你可能想使用渐变背景,或直接在单元格中画一些自定义的图形。在这些情况下,你可以使用"绘制模式"属性和"自绘单元格"事件,通过每一个单元格是如何绘制的来实现全面控制。
    "绘制模式"属性决定了 "自绘单元格"事件是否被激发。该事件可以是你兼顾到每一个单元格的视觉效果。"自绘单元格"事件中的参数,允许你对所显示的数据以及用于显示数据的样式,做出更改,甚至允许你完全接管并绘制单元格中你所想要的任何东西。
    你可以通过在事件处理程序中设置 e.Text 和 e.Image 参数,来改变即将显示在单元格中的文本和图像。你也可以通过设置 e.Style 属性来改变将用于显示单元格的样式。请注意,你不应该修改样式参数的属性,因为这可能会影响到其他单元格。相反,你要分配一个新的"单元格样式"对象到"样式"参数。
    例如,不要设置 e.Style.ForeColor= Color.Red,而应该通过使用 e.Style =
    _flex.Styles["RedStyle"]将一个全新的样式分配给参数。
    你还可以使用自己的绘图代码在单元格中进行绘制,甚至用命令将你的自定义代码与 e.DrawCell 方法合并到一起。例如,你可以使用 GDI 命令来绘制单元格的背景,然后调用 e.DrawCell 来显示单元格边框和内容。
    可用的示例项目
    有关高级的自绘单元格的一个范例,请参阅"ComponentOne 帮助中心" 的 RtfGrid 样品。
    下面的代码显示了一个例子。它使用了渐变画笔来绘制所选定的单元格的背景。首先,代码会设置"绘制模式"属性,显示一个"线性渐变画笔"的对象并且在表格重新调整大小时更新画笔:
     Visual Basic
    <span style="color: #1f497d">Dim m_GradientBrush As System.Drawing.Drawing2D.LinearGradientBrush</span>
    <span style="color: #1f497d">Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load</span>
    <span style="color: #1f497d">' 通过自绘单元格来使用画笔。</span>
    <span style="color: #1f497d">m_GradientBrush = New</span>
    <span style="color: #1f497d">System.Drawing.Drawing2D.LinearGradientBrush(ClientRectangle, Color.SteelBlue, Color.White, 45)</span>
    <span style="color: #1f497d">' 使用自绘来添加渐变。</span>
    <span style="color: #1f497d">_flex.DrawMode = C1.Win.C1FlexGrid.DrawModeEnum.OwnerDraw</span>
    <span style="color: #1f497d">End Sub</span>
    <span style="color: #1f497d">Private Sub _flex_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles _flex.Resize</span>
    <span style="color: #1f497d">' 当控件调整大小时更新渐变画笔。</span>
    <span style="color: #1f497d">m_GradientBrush = New</span>
    <span style="color: #1f497d">System.Drawing.Drawing2D.LinearGradientBrush(ClientRectangle,</span>
    <span style="color: #1f497d">Color.SteelBlue, Color.White, 45)</span>
    <span style="color: #1f497d">End Sub</span>
     C#
    <span style="color: #1f497d">System.Drawing.Drawing2D.LinearGradientBrush m_GradientBrush; private void Form1_Load(object sender, EventArgs e)</span>
    <span style="color: #1f497d">

    Unknown macro: {</span> <span style="color}

    </span>
    <span style="color: #1f497d">private void _flex_Resize( object sender, System.EventArgs e)</span>
    <span style="color: #1f497d">

    Unknown macro: {</span> <span style="color}

    </span>

    第二步是处理"自绘单元格"事件,并使用自定义画刷来绘制单元格的背

    景。在这个例子中,通过在事件参数中使用 "绘制单元格"方法,表格自己处理了自己的前景:
     Visual Basic
    Private Sub _flex_OwnerDrawCell(ByVal sender As Object, ByVal e As C1.Win.C1FlexGrid.OwnerDrawCellEventArgs) Handles _flex.OwnerDrawCell
    ' 使用渐变画笔来绘制所选定的单元格的背景。
    If _flex.Selection.Contains(e.Row, e.Col) Then
    ' 绘制背景。
    e.Graphics.FillRectangle(m_GradientBrush, e.Bounds)
    ' 让表格绘制其内容。
    e.DrawCell(C1.Win.C1FlexGrid.DrawCellFlags.Content)
    ' 我们已完成这个单元格的绘制。
    e.Handled = True End If
     C#

    private void _flex_OwnerDrawCell( object sender, OwnerDrawCellEventArgs e) {
    // 使用渐变画笔来绘制所选定的单元格的背景。
    if (_flex.Selection.Contains(e.Row, e.Col))
    {
    // 绘制背景。
    e.Graphics.FillRectangle(m_GradientBrush, e.Bounds);
    // 让表格绘制其内容。
    e.DrawCell(DrawCellFlags.Content);
    // 我们已完成这个单元格的绘制。
    e.Handled = true;
    }

    编辑单元格

    默认情况下,C1FlexGrid 控件允许用户通过在单元格中打字来编辑单元格。你可以通过"允许编辑"属性设置为"假"来阻止用户对表格进行编辑。
    你还可以通过将"允许编辑"属性设"假"来阻止用户对特定列进行编辑。
    (当表格被绑定到一个数据源上,它会检测哪些列是可编辑的并自动设置"允许编辑"属性。)
    要开始编辑一个单元格,用户可以:
  • 开始在单元格中输入。这将替换单元格原有的内容。
  • 按 F2 键或 ENTER 键。这使得表格处于编辑模式下,并将当前单元格的内容放到编辑器。
  • 双击一个单元格。这与按 F2 键有相同的效果,但光标会出现在被点击的单元格。

基本的编辑模式允许用户在单元格中输入值。如果正在被编辑的列有一个特定的数据类型,由用户输入的值会自动转换成适当的数据类型。如果用户输入了不能转换成适当的数据类型的值,表格会激活"表格错误"事件并忽略该编辑。
对于许多应用来说,基本的编辑就足够了,但 C1FlexGrid 含有这样的属性和事件,它能让你控制编辑过程,并提供选择清单,编辑按钮和高级的验证控制。从版本 2.5 开始,C1FlexGrid 也有了外部编辑器的内置支持。这使你可以将任何一个控件作为表格编辑器使用(例如,现在你可以将 C1Input 控件作为表格编辑器使用。)
在以下主题中主要介绍了这些功能。

列表和组合

在许多应用中,单元格都有一个关于可能值的定义明确的列表。在这些情况下,你可以让用户从下拉列表中选择值。要做到这一点,必须建立一个字符串,其中包含所有的以管道字符分隔出来的选择(例如,"真|假|不知
道"),并将它分配到"组合列表"属性。每一列都可能有不同的列表。设置 "组合列表"属性,使表格在单元格旁边显示一个下拉框。用户可以单击此下拉框(或按 F2 键)来显示该单元格中可用选择的列表。
另一种常见的情况是即使单元格有共同价值的一个列表,但也应允许用户键入别的东西。这可以通过下拉组合,文本框和下拉列表的组合来实现。要创建一个组合,只需用管道字符(例如"|真|假不知道")来启动选择列表,然后将它像以前一样分配到"组合列表"属性。
例如,下面的代码会使表格在第一列上显示包含了颜色名称的下拉组合列表,并在第二列上显示一个下拉组合。当对第一列进行编辑时,用户必须从列表中选择一个值。当对第二列进行编辑时,用户也要选择一个值或输入什么别的东西:

  • Visual Basic

' 下拉列表。
_flex.Cols(1).ComboList = "Red|Green|Blue|Red|White"
' 下拉组合。
_flex.Cols(2).ComboList = "|Red|Green|Blue|Red|White"

  • C#

// 下拉列表。

_flex.Cols[1].ComboList = "Red|Green|Blue|Red|White";


// 下拉组合。

_flex.Cols[2].ComboList = "|Red|Green|Blue|Red|White";


你也可以在设计时使用"组合列表"对话框来设置"组合列表"属性。
"组合列表"对话框允许你选择你是想让列表显示为一个下拉列表下拉组合省略号按钮,还是文本框省略号按钮

你可以通过"列任务"菜单,或通过 C1FlexGrid 列编辑器来访问"组合列表"对话框。


  • 在"列任务"菜单上,单击组合列表框中的省略号按钮。
  • C1FlexGrid 列编辑器中,在左窗格中找到的"组合列表"属性,并单击它旁边的省略号按钮。

注意组合列表对话框是特定的列,并且只会更改所选定列的"组合列表"属性。
在某些情况下,在同一列中的单元格可能需要不同的列表。例如,一个属性列表可能会在第一列上显示显示所有的属性,并在第二列上显示他们的值。该值取决于属性本身,所以有效的选项一行接一行地改变。在这种情况下,你应该捕获"编辑前"事件,并且为当前单元格设置"组合列表"属性到一个适当的列表。"组合列表"属性适用于整个表格。
内置的"组合框"提供了一个默认的自动搜索功能。当用户输入一个值,选中的部分将移动到下一个适配的对象。您可以通过使用"编辑选项"属性来禁用此功能,也可以通过使用"自动搜索延迟"属性,在表格重新设置自动搜索缓冲之前来控制时间。
内置的"组合框"也有一个像 Visual Studio 的"属性"窗口中的编辑器那样的自动循环功能。当你双击一个具有与它相关联的列表的单元格,表格会自动选择下一个值。你也可以通过使用"编辑选项"属性来禁用此功能。

复选框

默认情况下,表格会将值以复选框的形式在布尔列显示(列的类型是由列对象的"数据类型"属性决定的)。如果你不想让让布尔值以复选框的形式显示出来,请将列的格式属性设置为一个字符串,此字符串包含会显示为"真" 和"假"的值。例如:

  • Visual Basic

_flex.Cols("bools").Format = "Yes;No"

  • C#


_flex.Cols["bools"].Format = "Yes;No";



在未绑定模式下,你可以通过使用 GetCellCheck 和 SetCellCheck 属性将复选框添加到任意单元格。复选框将会随着单元格中的任何文本来显示,而且你可以使用列的"图像对齐"属性来设置他们的位置。
复选框有两种类型:布尔和三态。布尔复选框可以在 CheckEnum.Checked 和 CheckEnum.Unchecked 状态之间进行切换。三态复选框可以在
CheckEnum.TSChecked, CheckEnum.TSUnchecked 和 CheckEnum.TSGrayed 之间进行循环切换。
如果某单元格有一个复选框并且"允许编辑"属性被设置为"真",用户可以通过用鼠标点击或按空格键或回车键来改变复选框的状态。
默认情况下,用鼠标或键盘切换复选框的值会将所有选中的复选框都切换掉。你可以通过使用"编辑选项"属性来禁用此功能。


值映射列表

如上所述的"组合列表"属性可以确保从列表中选择单元格的值。由用户选择的值被转换成适合于列的类型并存储在表格中,确切地说,是如果用户输入了值的话。
在许多情况下,单元格可以从定义明确的列表中采用一个值,但你想要显示的是一个包含实际值的〉用户界面友好的版本。例如,如果一个列包含产品编码,你可能想存储它的代码,但显示的产品名称来代替。
这可以通过"数据映射"属性来实现。此属性包含一个 IDictionary 对象的引用,它在什么是存储在表格中的什么是用户可见的之间建立了一个映射
(IDictionary 接口界面是在系统中定义的。集合的命名空间是由哈希表之类实现的)。
例如,下面的代码可以创建一个的包含颜色值和他们的名字的数据映射。
颜色存储于表格中,而向用户显示的是他们的名字:

  • Visual Basic

Dim dtMap As Hashtable = New Hashtable() dtMap.Add(Color.Red, "Apple") dtMap.Add(Color.Green, "Forest") dtMap.Add(Color.Blue, "Sky") dtMap.Add(Color.Black, "Coal") dtMap.Add(Color.White, "Snow")
_flex.Cols(1).DataType = GetType(Color)
_flex.Cols(1).DataMap = dtMap

  • C#

System.Collections.Hashtable dtMap = new System.Collections.Hashtable(); dtMap.Add(Color.Red, "Apple"); dtMap.Add(Color.Green, "Forest"); dtMap.Add(Color.Blue, "Sky"); dtMap.Add(Color.Black, "Coal"); dtMap.Add(Color.White, "Snow");

_flex.Cols[1].DataType = typeof(Color);


_flex.Cols[1].DataMap = dtMap;


配备了 Idictionary 接口的任何一类都可以作为一个"数据映射"来使用。例如,HashtableListDictionarySortedList 都提供了有效的数据映射。所不同的是,当他们在可编辑的列中使用时,下拉列表中条目的顺序将取决于类。
SortedList 类通过键值将各条目分类,Hashtable 通过任意顺序,而
ListDictionary 保持了那些条目被添加到列表中的顺序。正因为如此,
ListDictionary 通常是"数据映射"的最佳选择。
请注意,当单元格正在编辑中时,数据映射的键值必须是同一类型的。例如,如果一个列包含短整数(Int16),那么,与此列相关联的任何数据映射也应该是短整型的键值。如果将正整数(INT32)作为键值将不起作用。
下面的例子显示了它们的区别:


  • Visual Basic

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
    System.EventArgs) Handles MyBase.Load
    ' 按 key 排序。
    Dim sl As New SortedList() sl.Add("0", "Zero") sl.Add("1", "One") sl.Add("2", "Two")
    sl.Add("3", "Three")
    ' 保持添加顺序。
    Dim ld As New Specialized.ListDictionary() ld.Add(0, "Zero") ld.Add(1, "One") ld.Add(2, "Two")
    ld.Add(3, "Three") ' 任意顺序。
    Dim ht As New Hashtable() ht.Add(0, "Zero") ht.Add(1, "One") ht.Add(2, "Two") ht.Add(3, "Three")
    _flex.Cols(1).DataMap = sl
    _flex.Cols(1).Caption = "SortedList"
    _flex.Cols(2).DataMap = ld
    _flex.Cols(2).Caption = "ListDictionary"
    _flex.Cols(3).DataMap = ht
    _flex.Cols(3).Caption = "HashTable"
    End Sub

  • C#

    private void Form1_Load(object sender, System.EventArgs e);
    {
    // 按 key 排序。
    System.Collections.SortedList sl = new System.Collections.SortedList(); sl.Add("0", "Zero"); sl.Add("1", "One"); sl.Add("2", "Two");
    sl.Add("3", "Three"); // 保持添加顺序。
    System.Collections.Specialized.ListDictionary ld = new System.Collections.Specialized.ListDictionary(); ld.Add(0, "Zero"); ld.Add(1, "One"); ld.Add(2, "Two");
    ld.Add(3, "Three");
    // 任意顺序。
    System.Collections.Hashtable ht = new System.Collections.Hashtable(); ht.Add(0, "Zero"); ht.Add(1, "One"); ht.Add(2, "Two"); ht.Add(3, "Three");

    _flex.Cols[1].DataMap = sl;

    _flex.Cols[1].Caption = "SortedList";

    _flex.Cols[2].DataMap = ld;

    _flex.Cols[2].Caption = "ListDictionary";

    _flex.Cols[3].DataMap = ht;

    _flex.Cols[3].Caption = "HashTable";


    }



    此外,如果列的"数据类型"属性被设置为一个枚举,表格将自动建立并使用一个含枚举中每个值的名称的数据映射。例如,下面的代码可以创建一个包含国家的枚举。国家的值存储在表格中,向用户显示的是他们的名字:

  • Visual Basic

Private Enum Countries
NewYork
Chicago
NewOrleans
London
Paris
End Enum
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
_flex.Cols(1).DataType = GetType(Countries) End Sub
 C#
private enum Countries
{
NewYork,
Chicago,
NewOrleans,
London,
Paris
}
private void Form1_Load(object sender, EventArgs e)
{

_flex.Cols[1].DataType = typeof(Countries);


}


单元格按钮

某些类型的单元格,除了文本框或下拉列表以外,可能还需要更复杂的编辑器。例如,如果一个列包含文件名称或颜色,它应该可以用一个对话框来编辑。
在这些情况下,你应该将 "组合列表"属性设置为省略号("...")。该控件将在单元格旁边显示一个按钮,当用户点击它时,将触发"单元格按钮点击"事件。你可以捕获该事件,显示对话框,并使用用户的选择来更新单元格的内容。
如果你在省略号前添加管道字符,那么用户也将被允许通过在单元格输入的方式来编辑单元格的内容。
默认情况下,单元格的按钮显示省略号。您可以使用"单元格按钮图像" 属性将图片指定到单元格按钮。
下面的示例演示了你应该如何使用单元格按钮来显示一个拾色器对话框,并在一列中选择一种颜色。

  • Visual Basic

' 建立色列。
Dim c As C1.Win.C1FlexGrid.Column = _flex.Cols(1) c.DataType = GetType(Color)
' 显示单元格按钮。
c.ComboList = "..."

  • C#

// 建立色列。

Column c = _flex.Cols[1];


c.DataType = typeof(Color);
// 显示单元格按钮。
c.ComboList = "...";
通过此代码设置该列,用户可以通过点击一个按钮来从一个对话框中选择一个颜色。下一步处理单元格按钮点击的代码:


  • Visual Basic


Private Sub _flex_CellButtonClick(ByVal sender As Object, ByVal e As
C1.Win.C1FlexGrid.RowColEventArgs) Handles _flex.CellButtonClick
' 创建"拾色器"对话框。
Dim clrDlg As New ColorDialog()
' 初始化对话框。
If _flex(e.Row, e.Col) Is GetType(Color) Then clrDlg.Color = _flex(e.Row, e.Col) End If
' 从对话框中获取新的颜色,并将它分配到单元格。
If clrDlg.ShowDialog() = Windows.Forms.DialogResult.OK Then
_flex(e.Row, e.Col) = clrDlg.Color
End If
End Sub


  • C#

private void _flex_CellButtonClick( object sender, RowColEventArgs e)
{
// 创建"拾色器"对话框。
ColorDialog clrDlg = new ColorDialog();
// 初始化对话框。

if (_flex[e.Row, e.Col] == typeof(Color))


{

clrDlg.Color = (Color)_flex[e.Row, e.Col];


}
// 从对话框中获取新的颜色,并将它分配到单元格。
if (clrDlg.ShowDialog() == DialogResult.OK)
{

_flex[e.Row, e.Col] = clrDlg.Color;


}
}


掩码

C1FlexGrid 控件还支持屏蔽编辑。当用户输入时,这种类型的编辑可以使用一种输入掩码来提供一个模板,并自动验证该输入。该掩码由"编辑掩码" 属性明确指定,它可以通过普通的文本字段和下拉组合区域来使用。
掩码字符串包括两种类型的字符:文字字符,这可以成为输入的一部分;模板字符,这是属于特定类别的占位字符(例如,数字或字母)。例如,下面的代码将一个编辑掩码"(999)999-9999"指定到第一列,该掩码包含的是电话号码,(数字"9"是一个可以代表任何数字的占位字符):

  • Visual Basic

' 建立一个电话号码的编辑掩码。
_flex.Cols(1).EditMask = "(999) 999-9999"

  • C#

// 建立一个电话号码的编辑掩码。

_flex.Cols[1].EditMask = "(999) 999-9999";


当你将"编辑掩码"属性设置为一个非空的字符串,会导致它必须使用内
置的屏蔽编辑,即便该列包含日期/时间值(通常,一个"日期时间提取器"控件可以用于编辑这些列)。如果你有只含时间(没有日期)的"日期时间" 列,这使用起来将格外方便。在这些情况下,你可以设置以下属性来使用一个屏蔽编辑器,而不是一个"日期时间提取器"控件:


  • Visual Basic

flex.Cols(1).DataType = GetType(DateTime)
_flex.Cols(1).Format = "hh:mm tt"
_flex.Cols(1).EditMask = "99:99 LL"

  • C#


flex.Cols[1].DataType = typeof(DateTime);


_flex.Cols[1].Format = "hh:mm tt";


_flex.Cols[1].EditMask = "99:99 LL";


你也可以在设计时通过使用"输入掩码"对话框来设置"编辑掩码"属性。
要访问输入掩码对话框可通过以下两种途经,"列任务"菜单或
C1FlexGrid 列编辑器


  • 进入"列任务"菜单,在编辑掩码框中单击省略号按钮。
  • 进入 C1FlexGrid 列编辑器,找到左窗格中的编辑掩码属性,并单击它旁边的省略号按钮。

注意:输入掩码对话框是特定的列,并且只会更改所选定的列的编辑掩码属性。
有关建立掩码字符串的语法的详细信息,请参阅该控件参考资料部分的编辑掩码属性。
如果在同一列的不同单元格需要不同的掩码,需要捕获编辑前事件,并为当前单元格将编辑掩码属性设置一个的适当的值。

 验证

在许多情况下,为确保用户的数据输入是有效的,只有编辑掩码是不够的。例如,掩码不会让你指定一个可能值的范围,或以另一个单元格的内容为基础来验证当前单元格。
在这些情况下,需要捕获验证编辑事件,看看在"编辑器-文本"属性中包含的值是不是当前单元格的有效条目(在这一点上,单元格仍然有它的原始值)。如果该输入的是无效的,将"取消"参数设置为"",表格将保持编辑模式直到用户输入一个有效的条目。
例如,将下面的代码验证输入到一个货币列,以确保输入的值处于 1000 和 10000 之间:

  • Visual Basic

    Private Sub _flex_ValidateEdit(ByVal sender As Object, ByVal e As C1.Win.C1FlexGrid.ValidateEditEventArgs) Handles _flex.ValidateEdit
    ' 验证金额。
    If _flex.Cols(e.Col).DataType Is GetType(Decimal) Then Try
    Dim dec As Decimal = Decimal.Parse(_flex.Editor.Text())
    If (dec < 1000) Or (dec > 10000) Then
    MessageBox.Show("Value must be between 1,000 and 10,000") e.Cancel = True
    End If
    Catch
    MessageBox.Show("Value not recognized as a Currency") e.Cancel = True
    End Try
    End If
    End Sub

  • C#

    private void _flex_ValidateEdit( object sender, ValidateEditEventArgs e)
    {
    // 验证金额。

    if (_flex.Cols[e.Col].DataType == typeof(Decimal))


    { try
    {
    Decimal dec = Decimal.Parse(_flex.Editor.Text); if ( dec < 1000 || dec > 10000 ) {
    MessageBox.Show("Value must be between 1,000 and 10,000"); e.Cancel = true;
    }
    }
    catch
    {
    MessageBox.Show("Value not recognized as a Currency"); e.Cancel = true;
    }
    }
    }


    自定义编辑器

    内置的编辑器可以具备很大的灵活性和能力,但在某些情况下,你可能要使用外部控件作为专门的编辑器。例如,你可能想使用提供了一个下拉计算器的 C1NumericInput 控件来输入数字,或使用一个编辑器来从多列的列表中进行选择,或你可以用自己写的一个专门的控件来编辑你的业务对象。
    从基本的"控件"类型派生出的任何控件都可以作为一个基本的表格编辑
    器使用。实现了 IC1EmbeddedEditor 接口(在 C1.Common.dll 的定义)的控件可
    以用表格提供更好的整合和更先进的功能。有关 IC1EmbeddedEditor 接口的更多详细信息,请参阅"编辑属性"。
    要将一个控件作为自定义编辑器来使用,所有你必须要做的就是使用其
    "编辑"属性将控件的一个实例与一个表格列或一个样式关联到一起。你可以在设计器(使用列编辑器)或在代码中实现这步操作。在此之后,表格将自动使用该控件。
    要在设计时定义自定义编辑器,先要将编辑器控件的一个实例添加到窗
    体,然后从 C1FlexGrid 任务菜单上选择设计器来打开 C1FlexGrid 列编辑器。选择应使用自定义编辑器的列,并将其编辑器的属性设置为新的编辑器控件的名称。
    例如,要将 NumericUpDown 控件作为一个表格编辑器使用,请按照下列步骤操作:
  1. 添加一个 C1FlexGrid 控件到窗体。
  2. NumericUpDown 控件添加到窗体,并将其"边框样式"属性设置为

""。

  1. C1FlexGrid 任务菜单上选择设计器。有关访问 C1FlexGrid 列编辑器的更多详细信息,请参阅"访问 C1FlexGrid 的列编辑器"。
  2. 在 C1FlexGrid 列编辑器上,选择第一个滚动的表格列,并将它的"编辑器"属性设置到 NumericUpDown1。

运行该项目并编辑第一列中的一些值。请注意表格如何定位和初始化
NumericUpDown 控件,以便你可以编辑单元格的值。当你完成对一个单元格的编辑,单击不同的单元格或按 TAB 键来移动到下一个。请注意新的值是如何应用到单元格的。
你也可以使用代码来给表格指定自定义编辑器:

  • Visual Basic

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' 创建自定义编辑器。
Dim editor as New NumericUpDown()
editor.BorderStyle = BorderStyle.None
' 将自定义编辑器分配到表格。
_flex.Cols(1).Editor = editor End Sub

  • C#

private void Form1_Load(object sender, System.EventArgs e)
{
// 创建自定义编辑器。
NumericUpDown editor = new NumericUpDown();
editor.BorderStyle = BorderStyle.None;
// 将自定义编辑器分配到表格。

_flex.Cols[1].Editor = editor;


}
创建自定义编辑器从 Control 基类派生出的任何控件都可以用来作为一个表格编辑器使用。这
是完全有可能的,因为在访问诸如"文本"和"界限"之类的属性,或诸如 "请假"和"文本变更"之类的事件时,表格对基类控件有足够的了解。在很多情况下,这种程度的支持就足够了。然而,在某些情况下,你可能想使用一些不那么密切地遵守基类的控件。例如,"日期时间提取器"控件有一个""属性,它应该用来检索编辑过的值,而不是文本的值。在这些情况下,你可以在 IC1EmbeddedEditor 接口上实现一个或多个方法来覆盖默认的行为。例如,在 C1Input 库中所有控件都支持
IC1EmbeddedEditor,因此,它与 C1FlexGrid(还有 C1TrueDBGrid)整合密切。
IC1EmbeddedEditor 接口相当简单,并且由于表格是使用后期绑定与之相结合的,你甚至不需要实现其所有成员。只需要落实那个对你的编辑器控件有意义的即可。
接口确实提供了足够的灵活性来使几乎所有的控件都能作为一个表格编辑
器使用。你甚至可以使用 UITypeEditor 这些类型来作为表格编辑器。要做到这一点,你需要一个包装类:


  1. 派生于 Control(UITypeEditor 不可以)。
  2. 实现 IC1EmbeddedEditor 接口。
  3. 封装相应的 UITypeEditor。我们在 CustomEditors 样品中为这个包装类提供的源代码。

使用 UITypeEditor 包装类,你可以与 C1FlexGrid 一起使用任何
UITypeEditors。.NET 可以为编辑颜色,字体,文件名等提供几个 UITypeEditors。你也可以自己写 UITypeEditors,在某些情况下,这比写控件容易。
可用的示例项目
例如与 C1FlexGrid 一起使用内置的、自定义的和 UITypeEditor 编辑器,请在 ComponentOne 帮助中心上参阅"自定义编辑器"样本。

编辑模式

你可以通过阅读编辑属性值来确定表格是否处于编辑模式。如果这个属性恢复为"无效",则表格不是在编辑模式。不然,属性恢复一个引用值到 正在用于编辑该单元格的控件(该控件可能是一个"文本框",一个"组合框",或其他类型的控件),表格就是处于编辑模式。你可以"开始编辑"的方法将表格程式化地设定在编辑模式下,并使用"完成编辑"的方法来完成编辑。
你可以通过处理表格触发的编辑事件来进一步地控制整个编辑过程。在编辑一个单元格的过程中,表格会触发以下一系列事件:

事件

描述

编辑前

一个可编辑的单元格被选中时会触发该事件。你可以通过将事件的"取消"参数设置为"真"来防止单元格被编辑。你还可以修改"组合列表"的属性,以便使适当的下拉按钮被画在单元格
中。请注意,用户可能不会在这之后真正开始编辑,他可以简单地将选择移动到一个不同的单元格或控件。

开始编辑

除了用户已经在单元格中敲下了一个键或点击了下拉按钮并真正开始编辑以外,该事件与"编辑前"事件类似。你仍然可以在这一点上取消编辑。请注意,在这一点上"编辑器"属性仍然是
空,因为该控件目前还没有确定应该使用的编辑器的类型。您可以在这一点上指定自定义编辑器到"编辑器"属性。

安装编辑器

该事件触发的时间是编辑器控件已经创建并且配置好去编辑该单元格之后,但在它被显示之前。

 

在这一点上,你可以改变编辑器的属性(例如,在"文本框"编辑器中设置最大长度或密码字
符)。你还可以将自己的事件处理程序添加到编辑器。

验证编辑

该事件触发的时间是当用户完成编辑且编辑器的值被复制回表格之前。你可以通过从表格中检索它来检查原始值 (该事件可以提供单元格的坐标)。你可以通过
编辑器属性(例如,Editor.Text)来检查被分配到表格的新值。如果新的值对单元格无效,请将
"取消"参数设置为"",则表格将保持在编辑模式。相反,如果不想将单元格保持在编辑模式下,你最好还是恢复原来的值并离开编辑模式,设置"取消"
为"",然后使用"完成编辑"方法。

编辑后

该事件触发的时间是新的值已经被应用到单元格且编辑器已停用之后。你可以使用该事件来更新任何依赖于单元格值的东西(例如,分类汇总或排序)。

合并单元格

C1FlexGrid 控件允许你合并单元格,使他们跨越多个行或列。这个功能可以用来增强在表格上显示的数据的外观和清晰度。这些设置的作用与
HTML<ROWSPAN>和<COLSPAN>标签类似。
若要使单元格能够合并,以下两件事你必须要做:

  1. 将表格 "允许合并"属性设置为"无"以外的任何值。(每个设置的效果都会在参考部分得到解释。)
  2. 如果你想要合并列,必须将你想合并的每一列的"允许合并"属性设置为"真"。如果你想要合并行,必须将你想合并的每一行的"允许合并"属性设置为"真"。

如果相邻的单元格中含有相同的非空字符串,合并就会发生。没有一种方法能迫使一对单元格合并到一起。合并会在单元格内容的基础上自动完成。当相邻行中的值呈现重复数据的时候,这可以很容易地提供排序数据的合并后的视图。
单元格合并有几种可能的用途。例如,当文本溢出到相邻列时,你可以用它来创建合并的表头,合并后的数据视图,或表格。

合并表头

要创建合并表头,你必须通过将表格的"允许合并"属性设置到 FixedOnly 来启动。然后,通过设置行和列的"允许合并"属性来指定要合并的行和列。
最后,将文本分配到标题单元格,以便你要合并的单元格具有相同的内容。
下面的代码显示了一个例子:

  • Visual Basic

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
    System.EventArgs) Handles MyBase.Load Dim i%
    ' 初始化该控件。
    _flex.Styles.Normal.WordWrap = True
    _flex.Cols.Count = 9
    _flex.Rows.Fixed = 2
    _flex.AllowMerging = C1.Win.C1FlexGrid.AllowMergingEnum.FixedOnly
    ' 创建行标题。
    _flex.Rows(0).AllowMerging = True
    ' 合并含有相同内容的四个单元格。
    Dim rng As C1.Win.C1FlexGrid.CellRange = _flex.GetCellRange(0, 1, 0, 4) rng.Data = "North"
    ' 合并含有相同内容的四个单元格。
    rng = _flex.GetCellRange(0, 5, 0, 8) rng.Data = "South"
    For i = 1 To 4
    _flex(1, i) = "Qtr " & i
    _flex(1, i + 4) = "Qtr " & i Next
    ' 创建列标题。
    _flex.Cols(0).AllowMerging = True
    ' 合并含有相同的内容的两个单元格。
    rng = _flex.GetCellRange(0, 0, 1, 0)
    rng.Data = "Sales by Product"
    ' 对齐单元格和自动调整单元格大小。
    _flex.Styles.Fixed.TextAlign =
    C1.Win.C1FlexGrid.TextAlignEnum.CenterCenter
    _flex.AutoSizeCols(1, _flex.Cols.Count - 1, 10)

  • C#

    private void Form1_Load(System.object sender, System.EventArgs e)
    {
    int i;
    // 初始化该控件。
    _flex.Styles.Normal.WordWrap = true;
    _flex.Cols.Count = 9;
    _flex.Rows.Fixed = 2;
    _flex.AllowMerging = C1.Win.C1FlexGrid.AllowMergingEnum.FixedOnly;
    // 创建行标题。

    _flex.Rows[0].AllowMerging = true;


    // 合并含有相同内容的四个单元格。
    C1.Win.C1FlexGrid.CellRange rng = _flex.GetCellRange(0, 1, 0, 4); rng.Data = "North";
    // 合并含有相同内容的四个单元格。
    rng = _flex.GetCellRange(0, 5, 0, 8); rng.Data = "South";
    for ( i = 1 ; i <= 4; i++)
    {

    _flex[1, i] = "Qtr " + i;

    _flex[1, i + 4] = "Qtr " + i;


    }
    // 创建列标题。

    _flex.Cols[0].AllowMerging = true;


    // 合并含有相同的内容的两个单元格。
    rng = _flex.GetCellRange(0, 0, 1, 0);
    rng.Data = "Sales by Product";
    // 对齐单元格和自动调整单元格大小。
    _flex.Styles.Fixed.TextAlign =
    C1.Win.C1FlexGrid.TextAlignEnum.CenterCenter;
    _flex.AutoSizeCols(1, _flex.Cols.Count - 1, 10);
    }


    以下是结果:

    合并后的数据视图

    当表格被绑定到一个数据源时,单元格合并会以同样的方式进行。下面的代码显示了一个在设计时表格绑定到数据源的例子。有关绑定到一个数据源的更多详细信息,请参阅"绑定到一个数据源"(第 95 页)。
  • Visual Basic

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
    System.EventArgs) Handles MyBase.Load Dim i%
    ' 设置单元格合并。
    _flex.AllowMerging = C1.Win.C1FlexGrid.AllowMergingEnum.RestrictCols
    For i = _flex.Cols.Fixed To _flex.Cols.Count - 1
    _flex.Cols(信息).AllowMerging = True
    Next
    End Sub

  • C#

private void Form1_Load( System.object sender, System.EventArgs e)
{
int i;
// 设置单元格合并。
_flex.AllowMerging = C1.Win.C1FlexGrid.AllowMergingEnum.RestrictCols; for (int i = _flex.Cols.Fixed; i <= _flex.Cols.Count - 1; i++)
{
_flex.Cols(信息).AllowMerging = true;
}
以下是结果:


请注意合并单元格是如何在分组数据和使表格中的信息更容易理解上具有视觉效果的。
可用的示例项目
有关合并后数据视图用 C1FlexGrid 显示的一个示例,请参阅
ComponentOne 帮助中心中的"单元格合并"样本。

溢出文本

"允许合并"属性有两个设置,它们不同于其他的操作,也不需要你在特定的行和列来设置"允许合并"属性。
溢出设置
溢出设置会导致太长而不适合于一个单元格的文本蔓延到相邻的空单元格中。这种结果式的行为与 Microsoft Excel 中的类似。如果你在一个单元格中键入一个长项而相邻的单元格是空的,则该项可能会溢出所在单元格来占据所需要的尽可能多的空间。
例如,下面的图片显示了当"允许合并"属性设置为"溢出"且用户输入了不同长度的条目时,一个表格看起来是什么样子的:
节点设置
节点设置是与"溢出"类似,但只适用于勾勒节点。当数据被组合成组,且在节点行中所包含的格式中的信息与数据行不同时,此设置是非常有用的。
例如,下面的图片显示了,当数据被用 C1FlexGridBase.Subtotal 方法分组和总结,随后节点被设置为"允许合并"时,一个表格看起来是什么样子的:

此图片与在"创建汇总"(第 67 页)主题中的那个类似。所不同的是,现在小计行(节点)溢出到相邻的空单元格,改善了表格的外观。

3.7.4 自定义合并

你可以通过两种途径自定义默认的合并行为:
 将一个自定义的 IComparer 类分配到 CustomComparer 属性。

  • 默认情况下,表格会将包含相同的非空值的相邻的单元格合并到一起。字符串比较是区分大小写的,且包括空白。
  • 如果你想让表格用大小不敏感的比较和修剪空白来合并单元格,你可以写一个自定义的类,用它来实现 Icomparer,并且将它分配

到 CustomComparer 属性。
 写一个从 C1FlexGrid 派生出的新类别,并通过提供自己的自定义合并逻辑来覆盖 GetMergedRange 虚拟方法。
可用的示例项目

有关可以显示如何实现自定义的合并逻辑的样本,请参阅如下样本,

"ComponentOne 帮助中心"的"自定义合并","自定义合并 2","自定义合并 3","自定义合并 4"。

 概述和汇总数据

C1FlexGrid 控件具有可以让你汇总数据并以分层方式显示的方法和属性。
若要汇总数据并添加聚合值,请使用 C1FlexGrid.Subtotal 方法。若要显示数据的分层视图,请使用"树型"属性。

 创建分类汇总

C1FlexGrid.Subtotal 方法可以增加包含普通(非小计)行的汇总数据的分类汇总行。
分类汇总支持分层聚合。例如,如果你的表格包含销售数据,你可能会通过产品、地区和推销员来小计一下以得出总的销售数字。下面的代码说明了这一点:
 Visual Basic
Private Sub ShowTotals()
' 在第零列显示大纲栏。
_flex.Tree.Column = 0
_flex.Tree.Style = TreeStyleFlags.Simple
' 清除现有的分类汇总。
_flex.Subtotal(AggregateEnum.Clear)
' 获得总计(使用-1,而不是列索引)。
_flex.Subtotal(AggregateEnum.Sum, -1, -1, 3, "Grand Total")
' 每个产品的总计(第零列)。
_flex.Subtotal(AggregateEnum.Sum, 0, 0, 3, "Total {0}")
' 每个区域的总计(第一列)。
_flex.Subtotal(AggregateEnum.Sum, 1, 1, 3, "Total {0}")
' 基于内容来调整列宽。
_flex.AutoSizeCols() End Sub
 C#
private void ShowTotals()
{
// 在第零列显示大纲栏。
_flex.Tree.Column = 0;
_flex.Tree.Style = TreeStyleFlags.Simple;
// 清除现有的分类汇总。
_flex.Subtotal(AggregateEnum.Clear);
// 获得总计(使用-1,而不是列索引)。
_flex.Subtotal(AggregateEnum.Sum, -1, -1, 3, "Grand Total");
// 每个产品的总计(第零列)。
_flex.Subtotal(AggregateEnum.Sum, 0, 0, 3, "Total {0}");
// 每个区域的总计(第一列)。
_flex.Subtotal(AggregateEnum.Sum, 1, 1, 3, "Total {0}");
// 基于内容来调整列宽。
_flex.AutoSizeCols();
}
当 C1FlexGrid.Subtotal 方法添加了汇总信息行,它会自动分配汇总样式到新的行(有五个层级的分类汇总内置样式)。你可以使用"样式编辑器"或代码在设计器中改变大纲样式的属性,以此来自定义分类汇总行的外观。例如:

  • Visual Basic

' 设置分类汇总的样式。
Dim cs As C1.Win.C1FlexGrid.CellStyle cs = _flex.Styles(C1.Win.C1FlexGrid.CellStyleEnum.GrandTotal) cs.BackColor = Color.Black cs.ForeColor = Color.White cs.Font = New Font(Font, FontStyle.Bold) cs = _flex.Styles(C1.Win.C1FlexGrid.CellStyleEnum.Subtotal0) cs.BackColor = Color.DarkRed cs.ForeColor = Color.White cs.Font = New Font(Font, FontStyle.Bold) cs = _flex.Styles(C1.Win.C1FlexGrid.CellStyleEnum.Subtotal1) cs.BackColor = Color.DarkBlue cs.ForeColor = Color.White

  • C#

// 设置分类汇总的样式。

CellStyle cs; cs = _flex.Styles[CellStyleEnum.GrandTotal]; cs.BackColor = Color.Black; cs.ForeColor = Color.White; cs.Font = new Font(Font, FontStyle.Bold); cs = _flex.Styles[CellStyleEnum.Subtotal0]; cs.BackColor = Color.DarkRed; cs.ForeColor = Color.White; cs.Font = new Font(Font, FontStyle.Bold); cs = _flex.Styles[CellStyleEnum.Subtotal1]; cs.BackColor = Color.DarkBlue; cs.ForeColor = Color.White;


执行此代码后,表格看起来是这样的:

总计行中包含的所有产品,地区和销售人员的销售总额。它是使用
groupOn 参数-1 在调用 C1FlexGrid.Subtotal 方法时被创建的。其他分类汇总显示产品和地区的销售总额。他们是用 Groupon 的参数值 0 和 1 创造的。
除了总量之外,你也可以计算其他分类汇总(例如,平均值或百分比),并计算每一行的几个汇总(例如,毛销售额及净销售额)。
分类汇总的方法创建的小计行不同于其他普通行,主要体现在三个方面:


  1. flexSTClear 参数来调用分类汇总的方法,小计行可以被自动删除。在用户可以移动列并对数据进行重新排序的地方,重新计算分类汇总很有必要,而这在提供数据的动态视图方面非常有用。
  2. 小计行可作为一个大纲中的节点使用,它可以让你折叠和展开行组来展现数据的概述或透露其细节。要看到大纲的树型图,你需要设置列和

"树型样式"的属性来确定大纲树型图的位置和外观。

  1. 小计行可以被视为树型结构上的的节点。通过"节点"属性,你可以为任何小计行获得一个"节点"对象。
  2. 当表格被绑定到一个数据源,小计行并不符合实际的数据。如果你在数据源中移动光标,则小计行将在表格中被跳过。

大纲树型图可以允许用户通过点击节点来折叠和展开表格的各部分。你可以使用大纲树型图来显示许多类型的信息,不仅仅是汇总这一种。下一主题将会指出如何创建一个自定义的大纲树型图来显示目录信息。

 创建自定义树型图

要想不使用分类汇总法来创建大纲树型图,你需要遵循以下步骤:

  1. 将行添加到表格中。
  2. 通过将他们的"是节点"属性设置为"真",将一些行转变成大纲的节点。
  3. 获得每个节点行的节点对象,并设置其"等级"属性来确定树型层次结构中节点的位置值越高意味着该节点在大纲树型图中越深入(缩进得多)。

例如,下面的代码可以创建一个目录树:
 Visual Basic
' 在窗体的顶部添加这些输入声明。
Imports System.IO
Imports C1.Win.C1FlexGrid
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' 初始化表格布局。
_flex.Cols.Fixed = 0
_flex.Cols.Count = 1
_flex.Rows.Count = 1
_flex.ExtendLastCol = True
_flex.Styles.Normal.TextAlign = TextAlignEnum.LeftCenter _flex.Styles.Normal.Border.Style = BorderStyleEnum.None
' 显示大纲树型图。
_flex.Tree.Column = 0
_flex.Tree.Style = TreeStyleFlags.SimpleLeaf
_flex.Tree.LineColor = Color.DarkBlue
' 填充表格。
AddDirectory("c:\", 0) End Sub

  • C#


// 在窗体的顶部添加这些输入声明。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using C1.Win.C1FlexGrid; private void Form1_Load(object sender, EventArgs e)
{
// 初始化表格布局。
_flex.Cols.Fixed = 0;
_flex.Cols.Count = 1;
_flex.Rows.Count = 1;
_flex.ExtendLastCol = true;
_flex.Styles.Normal.TextAlign = TextAlignEnum.LeftCenter; _flex.Styles.Normal.Border.Style = BorderStyleEnum.None;
// 显示大纲树型图。
_flex.Tree.Column = 0;
_flex.Tree.Style = TreeStyleFlags.SimpleLeaf;
_flex.Tree.LineColor = Color.DarkBlue;
// 填充表格。
AddDirectory(@"c:
", 0);

}

以上的代码初始化了表格布局,并且调用了"添加目录"程序,这意味着做了填充网格和建立的树型结构的工作:

  • Visual Basic

    Private Sub AddDirectory(ByVal dir As String, ByVal level As Integer)
    ' 添加该目录。
    Dim thisDir As String
    thisDir = Path.GetFileName(dir)
    If thisDir.Length = 0 Then thisDir = dir
    _flex.AddItem(thisDir)
    ' 使这个新行成为一个节点。
    Dim row As Row row = _flex.Rows(_flex.Rows.Count - 1) row.IsNode = True
    ' 设置该节点的层级。
    Dim nd As Node nd = row.Node
    nd.Level = level
    ' 在此目录中添加文件。
    Dim file As String, cnt As Integer, r As Row cnt = 0
    For Each file In Directory.GetFiles(dir)
    _flex.AddItem(Path.GetFileName(file).ToLower()) r = _flex.Rows(_flex.Rows.Count – 1) r.IsNode = True
    r.Node.Level = level + 1 cnt = cnt + 1
    If cnt > 10 Then Exit For Next
    ' 添加子目录(到 4 级)。
    If level <= 4 Then Dim subdir As String cnt = 0
    For Each subdir In Directory.GetDirectories(dir) AddDirectory(subdir, level + 1) cnt = cnt + 1
    If cnt > 10 Then Exit For
    Next

  • C#

"添加目录"是一个递归程序,它横跨当前目录及其所有子目录。在这个

private void AddDirectory(string dir, int level)
{
// 添加该目录。
string thisDir = Path.GetFileName(dir); if (thisDir.Length == 0) { thisDir = dir; }
_flex.AddItem(thisDir);
// 使这个新行成为一个节点。

Row row = _flex.Rows[_flex.Rows.Count - 1]; row.IsNode = true;


// 设置该节点的层级。
Node nd = row.Node;
nd.Level = level;
// 在此目录中添加文件。
int cnt = 0; Row r; foreach (string file in Directory.GetFiles(dir))
{
_flex.AddItem(Path.GetFileName(file).ToLower());
// 将没有子行的行标记为节点。

r = _flex.Rows[_flex.Rows.Count - 1]; r.IsNode = true;


r.Node.Level = level + 1; cnt = cnt + 1; if (cnt > 10) break;
}
// 添加子目录(到 4 级)。
if (level <= 4)
{
cnt = 0; foreach (string subdir in Directory.GetDirectories(dir))
{
AddDirectory(subdir, level + 1); cnt = cnt + 1; if (cnt > 10) break;
}
}
}


例子中,为了节省时间,树的大小局限在四个目录层次。在实际应用中,当它们被扩大时该程序应改为仅填充树型分支(请参阅式 FlexGrid for WinForms 教程(第 107 页))。
此代码可以创建一个表格,看起来如下图:

用 C1FlexGrid 控件来创建大纲和树型图

C1FlexGrid 控件独特的和流行的特点之一是能够添加层次分组到常规的非结构化数据。

为了实现这一目标,C1FlexGrid 介绍了节点行的概念。节点行不包含常规的数据。相反,他们作为表头在类似数据的分组下面运作,酷似一个常规
TreeView 控件中的节点。就像一个 TreeView 控件中的节点,节点行可以折叠和扩展,隐藏或显示它们所包含的数据。像一个 TreeView 控件中的节点的另一方面是,节点行有一个能定义节点层次的层级属性。较低级别的节点包含较高级别的节点。例如,假设你有一个可以显示客户名称,国家,城市,销售额的表格。这种典型的表格通常看起来是这样的 k

所有的信息都是存在的,但很难看到每一个国家或客户的总销售额。你可以使用 C1FlexGrid 的概述功能按国家(0 级)对数据进行分组,然后按每个国
家的城市(1 级),然后按每个城市的顾客(2 级)。下面是加入大纲之后的相同表格:
该表格会像前一个(被绑定到同一数据源的)一样显示相同的信息,但它增加了一个树型图,那里的每个节点都包含了它下面的数据摘要。节点可以折叠起来只显示摘要,或展开以显示细节。请注意,每个节点一行都可以显示多个列的摘要(在这种情况下,合计单位销售量并合计总金额)。
在这篇文章中,我们将引导你熟悉将一个普通的表格转变成一个更丰富的大纲型表格的过程。
加载数据

将数据加载到一个大纲型表格与将其加载到一个普通的表格是完全相同

的。如果你的数据源在设计时是可用的,你可以使用 Visual Studio 属性窗口来设置表格的"数据源"属性,并且无需编写任何代码就可以将表格绑定到数据。
如果数据源在设计时是不可用的,你可以在代码中设置表格的"数据源" 属性。数据绑定代码看起来通常是这样的:
代码使用一个 OleDbDataAdapter 来用数据填充一个数据表,然后,将数据表分配给表格的"数据源"属性。
运行此代码后,你会看到在第一张图片中显示了一个"普通的表格"。要使这个普通的表格变成第二张图片中显示的那种大纲型表格,我们需要插入节点行来整理这个大纲。
创建节点行
节点行几乎都是同样的普通行,但以下情况除外:
public Form1()
{
InitializeComponent();
// 获取数据
var fields = @"
Country,
City,
SalesPerson,
Quantity, ExtendedPrice"; var sql = string.Format("SELECT {0} FROM Invoices ORDER BY {0}", fields);
var da = new OleDbDataAdapter(sql, GetConnectionString()); da.Fill(_dt); // 将表格绑定到数据
this._flex.DataSource = _dt;
// 为"总价"列设置格式

_flex.Cols["ExtendedPrice"].Format = "n2";


}


  • 节点行不是数据绑定。当表格被绑定到一个数据源时,每个普通的行会对应数据源中的一个项目。而节点行则不然。相反,它们的存在是为了给包含类似数据的普通行分组。
  • 节点行可以折叠或展开。当一个节点行折叠起来时,它的所有数据和子节点都隐藏起来了。如果大纲树型图可见,用户可以用鼠标或键盘来折叠和展开节点。如果大纲树型图不可见,则只能用代码来扩展或折叠节点。你可以使用"是节点"属性来确定一个行是否是节点行:节点行可以用以下三种方法来创建:
  1. 使用 Rows.InsertNode 方法。这将在指定的索引中插入一个新的节点行。一旦该节点行被创建成功,你可以像使用任何其他行一样使用它(设置每列的数据、应用样式等)。
  2. 使用 C1FlexGrid.Subtotal 方法。这种方法会在表格中数据发生变化的地方用可选的分类汇总来扫描整个表格并自动插入节点行。这是插入汇总和构建大纲的"高层次"方式。它只需要非常少的代码,但对表格中的数据是如何排列的和大纲看起来应该像什么样子做了一些相关的假设。


var row = _flex.Rows[rowIndex]; if (row.IsNode)


{
// 该行是一个节点 var node = row.Node;
DoSomethingWithTheNode(node);
}
else
{
// 该行不是一个节点
}
3. 如果表格是未绑定的,那么你可以通过将 "是节点"属性设置为"真" 来将一些普通行变成节点行。请注意,这仅限于当表格处于未绑定的状态下。试图将一个普通的数据绑定行变成一个节点,可能会造成表格抛出一个异常。
以下的代码演示了你应该如何来执行一个"分组依据"程序,插入节点行并对一个给定列的近似值进行分组。


// 在给定列上将插入的同一个给定级别的节点分组
void GroupBy(string columnName, int level)
{
object current = null; for (int r = _flex.Rows.Fixed; r < _flex.Rows.Count; r++)
{

if (!_flex.Rows[r].IsNode)


{

var value = _flex[r, columnName]; if (!object.Equals(value, current))


{
// 值的变化:插入节点
_flex.Rows.InsertNode(r, level);
// 在第一个滚动列显示分组的名称

_flex[r, _flex.Cols.Fixed] = value;


// 更新当前值
current = value;
}
}
}
}


代码可以跳过现有的节点行(因此它可以被称为添加几个层次的节点)来扫描所有列,并记录分组列的当前值的轨迹。当当前值发生变化时,在第一滚动列会插入一个节点行,新组的名称会在此显示。
回到我们的例子,你可以使用此方法通过调用来创建两个级别的大纲:
void _btnGroupCountryCity_Click(object sender, EventArgs e)
{
GroupBy("Country", 0);
GroupBy("City", 1);
}
这很简单,但也有一些需要注意的事项。首先,该方法假定数据是按照大纲结构进行排序的。在这个例子中,如果数据是按照销售人员,而不是按照国别排序的,那么该大纲中每个国家都会对应好几个零级节点,这可能并不是你想要的。此外,"分组依据"程序可以插入许多行,这可能导致表格闪烁不定。为了避免这种情况,通常你应该先将"重绘"属性设置为"假",然后再作出更新,并且当完成后再将其设置回"真"。
为了解决这些问题,用来创建大纲的代码应该重新编写如下:
void _btnGroupCountryCity_Click(object sender, EventArgs e)
{
// 暂停重绘,同时更新
using (new DeferRefresh(_flex))
{
// 恢复原来的排序(按照国家、城市等)
ResetBinding();
// 按照国家、城市来分组
GroupBy("Country", 0);
GroupBy("City", 1);
}
}
"延迟刷新"类是一种简单的功能,它可以将表格的"重绘"属性设置为 "假",并且当它被破坏时可以恢复其原有的值。这将确保"重绘"属性 被完全地恢复,即便是在更新时发生例外的情况下。以下是 "延迟刷新"类的执行情况:

/// 实用工具类,用于封装在重绘版块的表格冗长的操作。
/// 这样就避免了闪烁,并且可以确保在发生万一的情况下"重绘"属性能够妥善地复位。
/// 在操作过程中抛出一个异常。
class DeferRefresh : IDisposable
{
C1FlexGrid _grid; bool _redraw; public DeferRefresh(C1FlexGrid grid)
{
_grid = grid;
_redraw = grid.Redraw; grid.Redraw = false;
}
public void Dispose()
{
_grid.Redraw = _redraw;
}
}

"绑定表格"的方法可以确保表格会按照我们的大纲结构所需的顺序排序。在我们的例子中,排序的顺序是按照国家、城市和销售人员。代码看起来则是像这个样子的:
如果你现在运行此代码,你会发现该节点行如预期一样创建,但大纲树型图是不可见的,所以你不能展开和折叠节点。下一节会对大纲树型图进行描述。

// unbind and re-bind grid in order to reset everything void ResetBinding()
{
// 解除绑定表格
_flex.DataSource = null;
// 重置任何自定义排序
_dt.DefaultView.Sort = string.Empty;
// 重新绑定表格
_flex.DataSource = _dt;
// 设置总价列的格式

_flex.Cols["ExtendedPrice"].Format = "n2";


// 自动调整列宽以适配其内容
flex.AutoSizeCols();
}



大纲树型图

大纲树型图与你在一个普通的"树型视图"控件中看到的非常相似。它显示了一个在每个节点行旁边有折叠或 扩展的图标的缩进结构,以便用户可以展开和折叠大纲以看到理想层级的细节。
大纲树型图可以在任何列中显示,这取决于由 Tree.Column 属性。在默认情况下,这个属性被设置为-1,这能导致大纲树型图根本不会被显示。在上面给出的例子中,为了显示大纲树型图,你可以使用这样的代码:
void _btnTreeCountryCity_Click(object sender, EventArgs e)
{
using (new DeferRefresh(_flex))
{
// 像以前一样按照国家和城市分组
_btnGroupCountryCity_Click(this, EventArgs.Empty);
// 显示大纲树型图
_flex.Tree.Column = 0;
// 自动调整尺寸以容纳树
_flex.AutoSizeCol(_flex.Tree.Column);
// 折叠细节节点
_flex.Tree.Show(1);
}
}
代码调用先前的方法来建立大纲,然后再将 Tree.Column 属性设置为零,以便在第一列显示大纲树型图。它还调用 C1FlexGrid.AutoSizeCol 的方法,以确保该列足够宽,能够容纳大纲树型图。最后,它调用 Tree.Show 的方法来显示所有零级节点(在这种情况下,就是城市那一层)并隐藏所有的细节。

  • "树"的属性使一个引用返回到公开了可以用于定制大纲树型图的几种方法和属性的 GridTree 对象,。其中主要的一些列举如下:
  • :获取或设置包含了大纲树型图的列的索引。将此属性设置为-1 可以导致用户看不到大纲树型图。
  • 缩进:以像素为单位,在相邻的节点层级之间,获取或设置缩进。更高的缩进层级会导致树变得越来越宽。
  • 样式:获取或设置即将显示的大纲树型图的样式。用这个属性来判断该树型图是否应包括顶部的一个按钮栏,来允许用户折叠/展开整个树型图,来判断线条和(或)符号是否应被显示,来判断线条是否应被显示既连接到了数据行又连接到了节点行。
  • 线条颜色:获取或设置树型图连接线的颜色。
  • 线条样式:获取或设置树型图连接线的样式。例如,通过改变上面的代码来包含这两个线条:

// 显示大纲树型图
_flex.Tree.Column = 0;
_flex.Tree.Style = TreeStyleFlags.CompleteLeaf;
_flex.Tree.LineColor = Color.White;
_flex.Tree.Indent = 30;
大纲树型图会改变如下:

请注意标有"1","2"和"*"的、在左上角单元格上的按钮。点击这些按钮,会导致整个树型图折叠或展开到相应的级别。还请注意,更宽的缩进和线条将树型图既连接到普通行("Anne Dodsworth")又连接到节点行。

添加分类汇总

到目前为止,我们已经介绍了节点行和大纲树型图的创建。然而,为了使大纲真正有用,节点行应该包括它们所包含的数据的汇总信息。
如果你使用 C1FlexGrid.Subtotal 方法来创建一个大纲树型图,然后分类汇总将会被自动添加。这将在后面的章节中描述。
如果你像上面所描述的使用 Rows.InsertNode 方法来创建大纲树型图,那么你应该使用 C1FlexGrid。总结用来计算每个行的分组中的分类汇总方法,并且将结果直接插入节点行。
下面列举出来的 C1FlexGrid.Subtotal 方法演示了如何做到这一点:

// 在一个给定层级的每个节点添加分类汇总
void AddSubtotals(int level, string colName)
{
// 获取我们将要进行汇总的列
int colIndex = _flex.Cols.IndexOf(colName);
// 为找到合适层级的节点对行进行扫描
for (int r = _flex.Rows.Fixed; r < _flex.Rows.Count; r++)
{

if (_flex.Rows[r].IsNode)


{

var node = _flex.Rows[r].Node; if (node.Level == level)


{
// 找到一个节点,计算总价的总和
var range = node.GetCellRange(); var sum = _flex.Aggregate(AggregateEnum.Sum, range.r1, colIndex, range.r2, colIndex, AggregateFlags.ExcludeNodes);
// 在表格上显示总和
// (将自动使用列格式)

_flex[r, colIndex] = sum;


}
}
}
}



"添加分类汇总"方法可以扫描所有表格中的行来寻找节点行。当一个理想层级的节点行被发现时, 该方法可以使用"获取单元格区域"的方法来检索节点的子行。然后它使用 C1FlexGrid.Aggregate 方法来计算整个范围内目标列中值的总和。聚合的调用包括一个可以避免重复计算的现有节点的"排除节点" 的标志。一旦小计已经被计算,它就会与通常的_flex[行,彩色]索引一起被分配到节点行的单元格。
请注意,这并不会以任何方式影响到数据源,因为节点行并没有绑定到数据。还要注意,该方法可以被用来将多个汇总添加到每个节点行。在这个例子中,我们将增加数量和总价列的汇总。除了总金额,你还可以添加其他的集合值,比如平均值,最高值,最低值等等。
现在我们可以用这种方法来创建一个包括节点行、大纲树型图以及分类汇
总的完整的大纲:
<span style="color: #1f497d">void _btnTreeCountryCity_Click(object sender, EventArgs e)</span>
<span style="color: #1f497d">{</span>
<span style="color: #1f497d">using (new DeferRefresh(_flex))</span>
<span style="color: #1f497d">

Unknown macro: {</span> <span style="color}

</span>
<span style="color: #1f497d">}</span>
如果你现在运行该项目,你会看到一个由节点行组成的树型图,其中显示
了每个国家和城市的销售总数量和金额。虽然这非常好,但还是有一个小问题。如果你展开任何节点行,你会看到大量的重复值。一个给定的城市节点下的所有行都有相同的国家和城市:
这是正确的,但它同时也是一种 屏幕实际使用面积的浪费。消除这些重复的值是很容易的;所有你所需要做的只是将正在被分组的列的宽度设置为零。
然而,当你这样做时,你应该记得将表格的"允许合并"属性设置为"节
点",以便使分配给节点行的文本可以溢出到可见列。(另一种选择是将节点的文本指定到第一个可见列,但合并通常是一个更好的解决方案,因为它可以让你在节点行使用更长的文本)。
下面是修改后的代码和最终结果:


void _btnTreeCountryCity_Click(object sender, EventArgs e)
{
using (new DeferRefresh(_flex))
{
// 恢复原来的排序(按照国家、城市、销售人员)
ResetBinding();
// 按照国家、城市来分组
GroupBy("Country", 0); // group by country (level 0)
GroupBy("City", 1); // group by city (level 1)
// 隐藏我们分过组的那些列
// (他们只有在树型图节点上已经出现过的那些重复的值)
// (但不要使它们不可见,这可能会隐藏节点的文本)

_flex.Cols["Country"].Width = 0; _flex.Cols["City"].Width = 0;


// 允许节点的内容溢出到下一个单元格
_flex.AllowMerging = AllowMergingEnum.Nodes;
// 添加每个国家和城市的总数
AddTotals(0, "ExtendedPrice"); // extended price per country
(level 0)
AddTotals(0, "Quantity"); // quantity per country (level
0)
AddTotals(1, "ExtendedPrice"); // extended price per city
(level 1)
AddTotals(1, "Quantity"); // quantity per city (level
1)
// 显示大纲树型图
_flex.Tree.Column = 0;
_flex.AutoSizeCol(_flex.Tree.Column);
_flex.Tree.Show(1);
}
}



国家城市列现在是不可见的,但它们的值仍然出现在节点行。折叠起来的的树型图可以显示每个国家和城市的总数。

使用分类汇总方法

在前面我们曾经提到,你也可以使用 C1FlexGrid 的 C1FlexGrid.分类汇总方法来创建树型图。除了可以在一个单一的步骤里同时做两件事以外,这种分类汇总的方法也可以像以上所描述的"分组依据"和"添加分类汇总"一样来执行相同的任务,因此它比其他的更高效一些。
下面的代码显示了你应该如何使用分类汇总的方法来完成我们之前做过的
同样的事情,只是要比之前更快一点而且不需要使用任何辅助方法:
void _btnTreeCountryCity_Click(object sender, EventArgs e)
{
using (new DeferRefresh(_flex))
{
// 恢复原来的排序(按照国家、城市、销售人员)
ResetBinding();
// group and total by country and city
_flex.Subtotal(AggregateEnum.Sum, 0, "Country",
"ExtendedPrice");
_flex.Subtotal(AggregateEnum.Sum, 0, "Country", "Quantity");
_flex.Subtotal(AggregateEnum.Sum, 1, "City", "ExtendedPrice");
_flex.Subtotal(AggregateEnum.Sum, 1, "City", "Quantity");
// 隐藏我们分过组的那些列
// (他们只有在树型图节点上已经出现过的那些重复的值)
// (但不要使它们不可见,这可能会隐藏节点的文本)

_flex.Cols["Country"].Width = 0;


_flex.Cols["City"].Width = 0;


_flex.AllowMerging = AllowMergingEnum.Nodes;
// 显示大纲树型图
_flex.Tree.Column = 0;
_flex.AutoSizeCol(_flex.Tree.Column);
_flex.Tree.Show(1);
}
}
分类汇总的方法非常方便和灵活。它包含多个重载,能够使你明确应该对
哪些列进行分组并按照索引或名称来合计总数,是否包括它插入节点行的标题,以及如何进行分组等等。下面的摘要介绍了可用的重载:


  1. Subtotal(AggregateEnum aggType)

该方法的这个版本只有一个聚合类型作为一个参数。只有消除现有的分类汇总,然后再插入新的,它才是有用的。在这种情况下, aggType 参数要设置到 AggregateEnum.Clear。

  1. Subtotal(AggregateEnum aggType, int groupBy, int totalOn)

Subtotal(AggregateEnum aggType, string groupBy, string totalOn)
这都是最常用的重载。参数都是聚合类型的,可以用来插入和分组和计算总数的列。列是可以通过索引或名称来引用的。而后者则是我们在上面的例子中使用过的。

  1. Subtotal(AggregateEnum aggType, int groupBy, int totalOn, string caption)

Subtotal(AggregateEnum aggType, string groupBy, string totalOn, string caption) 这些重载添加了一个额外的标题参数。标题参数明确了被添加到新的节点行的文本,以确定要进行分组的值。默认情况下,要分组的值会显示出来,所以如果你是按国家分组的,节点行会显示"阿根廷","巴西",等等。如果你设置的标题参数为字符串,如"国家:{0}",那么该节点的行会相应地显示
"国家:阿根廷"来代替。

  1. Subtotal(AggregateEnum aggType, int groupFrom, int groupTo, int totalOn, string caption)

Subtotal(AggregateEnum aggType, string groupFrom, string groupTo, string totalOn, string caption)
这些重载将 groupby 函数参数分成了两个:groupFrom 和 groupTo。在默认情况下,无论什么时候 GroupBy 的值或以往的任何列发生了变化,分类汇总的方法都可以插入一个节点行。例如,即使 GroupBy 列的值是相同的,如果某一行在"城市"这一列有与前行中相同的值,但在 "国家"一栏有不同的值,那么分类汇总的方法也会认为行应该在不同的分组里面并且插入一个新的节点
行。这些聚合使你撤销该行为,并且指定在确定一组时应该考虑的列的范围。

大纲维护

到目前为止,我们已经讨论了如何使用高级别的 C1FlexGrid. Subtotal 方法以及较低级别的 Rows.InsertNodeAggregate 方法来创建总计包含树型图和总计的大纲。
在这一点上,重要的是要记住,大纲树型图是基于数据而创建的,但是它不被任何方式约束,并且当表格或数据有变化时它不能自动维持现状。例如,如果用户在"总价"这一列修改了一个值,则分类汇总不会进行自动更新。如果用户将表格进行排序,那么数据将会被刷新,且分类汇总会消失。
有两种常见的方式可以用来维持大纲:
 防止用户做任何将会使大纲无效的更改。这是最简单的选择。你可以设置表格的"允许编辑"、"允许拖动"和"允许排序"属性为"假",并且阻止任何会影响到大纲的更改。
 当数据或表格有变化时更新大纲时。你会附加表格的"数据刷新后"、"排序后" 和"编辑后"事件的处理程序,并重新生成合适的大纲。
选项二通常是更有趣的,因为它提供了一个快捷而且简单的动态数据分析工具。这种方法是 C1FlexGrid 提供的"分析"示例来说明的。该示例创建了一个初始大纲,并允许用户对列进行重新排序。当列的顺序发生变化时,该示例自动对数据进行重新排序,并重新创建大纲。用户可以方便地创建简单的报告,来按国家、按产品、按销售人员等显示销售。

使用节点类

"节点"类提供了许多可以用来创建和管理大纲树型图的方法和属性。这些方法和属性中的许多都是基于标准的 TreeView 对象模型,因此他们应该熟悉大多数的开发者。
为了获得一个"节点"对象,你可以:
使用 Rows.InsertNode 方法的返回值:
Var node = _flex.Rows.InsertNode(index, level);
或者,你可以用行的节点属性来在现有的一行检索节点:

Var node = _flex.Rows[index].IsNode


_flex.Rows[index].Node null;


无论哪种方式,一旦你有了一个"节点"对象,你就可以使用以下属性和方法来操作它:


  • 等级:在大纲树型图中获取或设置节点级别。
  • 数据:在单元格中获取或设置由 Node.Row 和 Tree.Column 定义的值。
  • 图片:在单元格中获取或设置由 Node.Row 和 Tree.Column 定义的图像。
  • 选中:获取或设置由 Node.Row 和 Tree.Column 定义的单元格的选中状态。
  • 折叠/扩展:获取或设置节点的折叠/展开状态。

你还可以使用下列方法来探讨的大纲结构:

  • 获取单元格区域:获取单元格区域的对象,它描述了属于这个节点的行的范围。
  • 子节点:获取此节点下的子节点的数目。
  • 节点:获取一个节点的包含此节点的子节点数组。
  • 获取节点:获取与这个节点有一个给定关系的节点(父母,第一个孩子,下一个兄弟,等等)。

上面的讨论集中在绑定的情况下,即表格连接到提供数据的数据源的地方。你还可以创建绑定场景中的树型图和大纲。在这种情况下,事情其实比较简单,因为你可以通过将"是节点"属性设置为"真"来将任何一个节点变成一个节点行。
如果表格是未绑定的,它拥有所有显示的数据,并且当一个数据源拥有这些数据时,你做的是不可能的事情。例如,你可以像 C1FlexGrid 提供的"树型图节点"样品所显示的那样,使用 Move 方法来移动树型图周围的节点。
使用一个未绑定的表格中的节点与使用常规的"树型视图"控件的节点是非常相似的。

保存、加载和打印

C1FlexGrid 控件具有让你保存、加载并打印表格的方法。

 保存和载入表格到文本文件

"保存表格"方法可以将表格内容保存到一个文本文件。该方法具有可以控制使用的分隔符类型的参数(例如,逗号、制表符、自定义分隔符),可以确定是否要保存固定的单元格还是只保存滚动的单元格,和要使用的编码类型(例如,ASCII 或 Unicode)。生成的文本文件随后可以被装入控件,或装入到其他支持逗号或制表符分隔的文件的应用程序(例如,Microsoft Excel)。
"加载表格"方法可以从文本文件中加载数据。你可以加载用"保存表格"方法或与其他应用程序创建的文本文件。文本文件的格式是相当简单的。单元格的内容可以保存为格式化字符串(完全是因为他们在屏幕上显示)。如果单元格的文本包含引号字符或单元格分隔符,那么单元格会被引号引起来。

单元格的文本中包含的任何引用字符都增加了一倍。这也是在 Microsoft

Excel 文本文件中所使用的惯例。
文本文件不包含图片或格式信息。
"保存表格"方法有一个标志参数,它可以使你指定是否要保存整个表格或只保存其中的某些部分(滚动的、可见的、或选中的)。

 保存和加载 Microsoft Excel 文件

从版本 2.5 开始,你可以使用"保存表格"和"加载表格"方法来保存和加载 Microsoft Excel 文件(.XLS)以及文本文件。这可以使你除了保存数据之外,还可以保存格式信息。要使用"保存表格"和"加载表格"方法来保存和加载 Excel 文件,只需简单地设置格式参数到 FileFormatEnum.Excel,并且像往常一样调用方法。你不需要在你的计算机上安装 Microsoft Excel。
Excel 文件中包含由"工作表"组成的"工作簿"。"保存表格"和"加载表格"方法总是用一个单一的工作表来保存工作簿,并从现有工作簿来载入第
一页工作表。如果你想额外的控制工作来加载或保存,请使用 SaveExcel,
LoadExcel,LoadExcelSheetNames 方法来代替。Excel 文件保存和加载的过程将转换大多数的数据类型和格式信息,包括行和列的尺寸、字体、颜色、格式、单元格对齐方式。然而,并不是所有的格式元素可以转换。例如,表格将加载 Excel 单元格的值,但它不会加载其隐含的公式。其他特征,如固定和合并单元格,图片,数据映射,单元格边框等,也没有被翻译。

 从数据库中载入表格

你也可以从数据库中加载表格数据。这不同于数据绑定,它可以保持一个或多个控件和基础数据源之间的实时连接。为了从数据库加载数据,你可以使
用 DataReader 对象,如下所示:

  • Visual Basic

    Private Sub _btnData_Click(ByVal sender As Object, ByVal e As
    System.EventArgs) Handles _btnData.Click
    ' 准备好 DataReader。
    Dim strConn As String = "data source=MYMACHINE;initial catalog=Northwind;"
    Dim myConn As New SqlClient.SqlConnection(strConn)
    Dim myCMD As New SqlClient.SqlCommand("SELECT * FROM Employees", myConn) myConn.Open()
    Dim myReader As SqlClient.SqlDataReader = myCMD.ExecuteReader()
    ' 从 DB 模式来建立表格结构。
    Dim dt As DataTable = myReader.GetSchemaTable()
    _flex.Cols.Count = 1
    Dim dr As DataRow
    For Each dr In dt.Rows
    Dim c As C1.Win.C1FlexGrid.Column = _flex.Cols.Add() c.Caption =(c.Name <= CStr(dr("ColumnName")))
    c.DataType = CType(dr("DataType"), Type) Next dr
    ' 填充表格。
    _flex.Rows.Count = 1
    Dim row As Integer = 1
    Dim cols As Integer = dt.Columns.Count
    Dim v As Object() = CType(Array.CreateInstance(GetType(Object), cols), Object())
    While myReader.Read() myReader.GetValues(v)
    _flex.AddItem(v, row + 1, 1) End While
    ' 清理。
    _flex.AutoSizeCols() myReader.Close() myConn.Close()

  • C#

    private void _btnData_Click(object sender, System.EventArgs e)
    {
    // 准备好 DataReader。
    string strConn = "data source=MYMACHINE;initial catalog=Northwind;";
    System.Data.SqlClient.SqlConnection myConn = new
    System.Data.SqlClient.SqlConnection(strConn);
    System.Data.SqlClient.SqlCommand myCMD = new
    System.Data.SqlClient.SqlCommand("SELECT * FROM Employees", myConn); myConn.Open();
    System.Data.SqlClient.SqlDataReader myReader = myCMD.ExecuteReader();
    // DB 模式来建立表格结构。
    DataTable dt = myReader.GetSchemaTable();
    _flex.Cols.Count = 1; foreach (DataRow dr in dt.Rows)
    {
    Column c = _flex.Cols.Add();

    c.Caption = c.Name = (string)dr["ColumnName"];

    c.DataType = (Type)dr["DataType"];


    // 填充表格。

    _flex.Rows.Count = 1; int row = 1; int cols = dt.Columns.Count; object[] v = (object[])Array.CreateInstance(typeof(object), cols); while (myReader.Read())


    {
    myReader.GetValues(v);
    _flex.AddItem(v, row++, 1);
    }
    // 清理。
    _flex.AutoSizeCols(); myReader.Close(); myConn.Close();
    }


     打印表格

    使用"打印表格"方法来打印表格的内容。该方法具有可以让你选择缩放模式,是否显示打印/预览对话框,设置页眉和页脚,等等的参数。
    "打印参数"属性可以公开额外的打印性能,如字体,使用页眉和页脚,而一个.NET Framework "打印文档"对象可以用来选择打印机,纸张大小和方向,页边距等。
    下面的代码使用了"打印参数"属性来设置页面方向、页边距、页眉和页脚的字体。然后它调用"打印表格"方法来显示打印预览对话框窗口:
  • Visual Basic

    ' 获取表格的"打印文档"对象。
    Dim pd As Printing.PrintDocument pd = _flex.PrintParameters.PrintDocument()
    ' 设置页面(横向打印,左边页边距 1.5")。
    With pd.DefaultPageSettings
    .Landscape = True
    .Margins.Left = 150 End With
    ' 设置页眉和页脚的字体。
    _flex.PrintParameters.HeaderFont = New Font("Arial Black", 14,
    FontStyle.Bold)
    _flex.PrintParameters.FooterFont = New Font("Arial Narrow", 8,
    FontStyle.Italic)
    ' 预览表格。
    _flex.PrintGrid("C1FlexGrid",
    C1.Win.C1FlexGrid.PrintGridFlags.FitToPageWidth +
    C1.Win.C1FlexGrid.PrintGridFlags.ShowPreviewDialog, "C1FlexGrid" + Chr(9)
    +

  • C#


// 获取表格的"打印文档"对象。
System.Drawing.Printing.PrintDocument pd =
_flex.PrintParameters.PrintDocument;
// 设置页面(横向打印,左边页边距 1.5")。
pd.DefaultPageSettings.Landscape = true;
pd.DefaultPageSettings.Margins.Left = 150;
// 设置页眉和页脚的字体。
_flex.PrintParameters.HeaderFont = new Font("Arial Black", 14,
FontStyle.Bold);
_flex.PrintParameters.FooterFont = new Font("Arial Narrow", 8, FontStyle.Italic);
// 预览表格。
_flex.PrintGrid("C1FlexGrid", PrintGridFlags.FitToPageWidth |
PrintGridFlags.ShowPreviewDialog, "C1FlexGrid\t\t" +
Microsoft.VisualBasic.Strings.Format(DateTime.Now, "d"), "\t\tPage {0} of
{1}");


 C1FlexGrid 过滤

表格中的数据过滤通常有两种形式:

  • 基于表头:过滤器的图标出现在有一个过滤器适用于它的每一列。用户可以通过点击过滤器的图标来查看和编辑过滤器。这是 Windows 7 或 Vista 或 C1FlexGrid 控件使用的机制。这种类型的过滤器的主要优点是:(1)用户可以看到哪些列被过滤了,(2)过滤不需要屏幕上的额外的不动产,(3)这种类型的过滤器可以更好地过滤编辑器并更容易定制。
  • 过滤器行:过滤器行保持始终可见,使用户可以直接到该行中键入值或表达式。这种类型的过滤器的主要优点是,用户随时都可以看到哪些列正在被过滤和当前过滤器的标准是什么。主要缺点是过滤器占用一些不动产,且可能会干扰常规的表格运行。虽然过滤器行没有建立在 C1FlexGrid 控件上,但他们实施起来还是相对容易。我们提供了一个"过滤器行"示例来显示如何做到这一点。

下面介绍的代码样本,主要取自包括产品的两个新样本:列过滤器自定义过滤器。请参阅表明行动的特点的完整的项目样本。

 允许过滤属性

要使用以表头为基础的过滤器,C1FlexGrid 控件遵循了与用来实现列的移动和按大小排序的相同的模式。表格有一个新的"允许过滤"属性,可用于控制在控件级别过滤,并且表格的列对象也有一个"允许过滤"属性,可用于控制在列级别过滤。
要启用简单的过滤方案,用户只需设置表格的"允许过滤"属性为
""。然后,他们可以通过改变列的"允许过滤"属性的值来禁用或自定义过滤特定列的行为。列的"允许过滤"属性可以设置为下列值之一:

  • 默认:表格会自动创建一个 "列过滤"类型的过滤器。该过滤器将"值过滤"和

"条件过滤"结合了起来,两者都在下文中有所描述。

  • 根据值:表格会自动创建一个 "值过滤"类型的过滤器。该过滤器包含一个应显示的值的列表。任何列表上不存在的值,最终用户都是看不到的。
  • 根据条件:表格会自动创建一个"条件过滤"类型的过滤器。该过滤器会指定两个条件,如"大于"或"包含"。这些条件可以与 AND 或 OR 运算符号结合起来。
  • 自定义:表格不会自动创建一个过滤器。开发人员预计实例化一个过滤器,并明确将其指定道列的"过滤器"属性。
  • :该列不能进行过滤。

默认情况下,C1FlexGrid 控件会为使用指定的语言通过 CurrentUICulture 设置来将列过滤编辑器本地化。但是,你可以使用"语言"属性来推翻默认并指定当表格显示列过滤编辑器时应该使用的语言。
值过滤器 "值过滤器"概念上非常简单。它包含一个值的列表,并且只有该表中列出的值才会显示在表格上。如果列表设置为"无效的",那么该过滤为"没有活性的",且所有的值会被显示。这种类型的过滤器在过滤包含离散值,如名称或枚举,的列方面是被推荐的。
"值过滤"编辑器包括一个有复选框的值的列表。用户可以一次选中或取消选中所有值。采用先进的内置键盘导航来浏览长的列表是很容易的。编辑器中实现一个灵活的搜索缓冲区,可以使用户通过输入值的任何部分来找到值。例如,输入"希尔顿",将选择下一个其中包含"希尔顿"的值,包括"纽约希尔顿","王子爱德华希尔顿",或"巴黎希尔顿"。此外,敲 Ctrl+向上键或 Ctrl +向下键将会导航到下一个或前一个检查项目。使用目前分配给列的格式,值就会显示在列表上。
这个图像显示了"值过滤"编辑器。

条件过滤器
"条件过滤器"比其他的更加灵活。它不是选择特定的值,而是允许用户用运算符号来指定两个条件,如"大于" 、"开始"或"包含"。这种类型的过滤器在过滤包含"连续的"值的列,如数字,或日期/时间值,在这方面是被推荐的。 这个图像显示了"条件过滤"编辑器。

有过滤器适用于列来显示他们的标题过滤器的图标,甚至当鼠标不在他们上面的时候。在这个图像中你可以看到有"产品名称"和"数量"列标题的地方显示过滤器的图标。
内置的过滤器支持自动定位在以下语言:英语,西班牙语,法语,意大利语,葡萄牙语,德国,荷兰,俄罗斯,日本,希腊,丹麦,芬兰,挪威,瑞典文,阿拉伯文,波兰,中国,土耳其,波斯语,朝鲜语,希伯来语。该本地化资源是内置的,并不需要提供额外的 DLL 自定义过滤器
可以创建自定义过滤器来处理专门的值。例如,自定义过滤器在过滤颜色、地理或自定义数据类型方面值得推荐。
要创建一个自定义的过滤器,开发人员必须创建两个类:

  • 过滤器:这个类必须实现 IC1ColumnFilter 接口,它可以指定过滤器应用到一个特定的值,对过滤器进行复位,并返回一个用于查看和编辑过滤器的参数的编辑器。
  • 过滤编辑器:这个类必须继承自 Control,必须实现 IC1ColumnFilterEditor 的接口,该接口可以指定用于初始化编辑器和更改应用到过滤器的方法。

自定义过滤器的样本包含三个自定义过滤器,用于过滤类型的颜色日期/ 时间字符串的值。

 程序化地管理过滤器

正如我们前面提到的这个文件,设置表格的"允许过滤"属性为"",这足以让所有的列进行列过滤。然而,在许多情况下,你可能需要更精细的过滤控制。这可以通过修改个别列的"允许过滤"和"过滤"属性来实现。例如,下面的代码使能够启用过滤,但对过滤字符串类型的列进行了限制:
// 绑定和配置表格
flex.DataSource = dtProducts;

_flex.Cols["UnitPrice"].Format = "#,###.00";


// 启用过滤
_flex.AllowFiltering = true;
// 限制过滤 "字符串"类型的列
foreach (Column c in _flex.Cols)
{
c.AllowFiltering = c.DataType == typeof(string)
? AllowFiltering.Default
: AllowFiltering.None;
}
你可以通过创建过滤器并将他们分配到列,或通过检索现有的过滤器并修改其属性,来进一步自定义过滤过程。例如,下面的代码创建了一个"条件过滤器",配置它来选择所有以字母"C"开头的项目,然后分配这个新的过滤器给"产品名称"一列:


// 创建一个新的"条件过滤器"
var filter = new ConditionFilter(); // 配置过滤器来选择以"C"开始的项目
filter.Condition1.Operator = ConditionOperator.BeginsWith; filter.Condition1.Parameter = "C";
// 分配新的过滤器到"产品名称"列

_flexCustom.Cols["ProductName"].Filter = filter;



 程序化地应用过滤器

当用户编辑他们或当他们适用于一列时,过滤器是适用的。当数据发生变化时,它们不会自动应用。
要将过滤器应用到从表格加载的当前的数据,请调用表格的"应用过滤器"方法。
例如,当用户编辑表格上的数据时,下面的代码启用了一个"应用过滤器"按钮。点击这个"应用过滤器"按钮即可应用该过滤器,并可以直到下一次的变化前禁用该按钮。
public Form1()
{
InitializeComponent();
// 获取一些数据
var da = new OleDbDataAdapter("select * from products", GetConnectionString()); var dtProducts = new DataTable(); da.Fill(dtProducts);
// 将表格绑定到数据_
flex.DataSource = dtProducts;
// 启用过滤
_flex.AllowFiltering = true;
// 监测变化以便启用"应用过滤器"按钮
_flex.AfterEdit += _flex_AfterEdit;
}
上面的代码可以将一个表格绑定到数据源,可以通过将"允许过滤"属性
设置为"真"来启用过滤器,并可以连接一个事件处理程序到"编辑后"事件。事件处理程序的执行情况如下:

void _flex_AfterEdit(object sender, C1.Win.C1FlexGrid.RowColEventArgs e)
{
foreach (C1.Win.C1FlexGrid.Column c in _flex.Cols)
{
if (c.ActiveFilter != null)
{
_btnApplyFilters.Enabled = true; break;
}
}
}

此代码可以扫描所有列,以确定一个过滤器是否为任何列所定义。如果检测到有一个正在起作用的过滤器,该代码可以启用将过滤器应用于目前的数据的按钮。当单击该按钮时,下面的事件处理程序会执行如下:

private void _btnApplyFilters_Click(object sender, EventArgs e)
{
_flex.ApplyFilters();
_btnApplyFilters.Enabled = false;
}


该代码简单适用于所有活跃着的过滤器,并且直到下一次的变化前才禁用按钮。相反,如果你不需要一个按钮,而只是简单地想在每次编辑后应用该过滤器,你可以从"编辑后"事件处理器那里直接调用"应用过滤器",如下所示:
void _flex_AfterEdit(object sender, C1.Win.C1FlexGrid.RowColEventArgs e)
{
_flex.ApplyFilters();

 自定义过滤器的行为

当过滤器被应用后,表格会通过将"可见"属性设置为""来隐藏一些行。但表格也能够激发可以允许你自定义过滤行为的"过滤前"和"过滤后" 事件。例如,假设,你不是要显示和隐藏行,而是要应用不同的风格来表明这些行是否能通过过滤器。这可以使用此代码来轻松地实现:
public Form1()
{
InitializeComponent();
// 配置表格_
flex.AllowFiltering = true; _flex.DataSource = dtInvoices;
// 为过滤器排除的行创建样式
var cs = _flexStyles.Styles.Add("filteredOut"); cs.BackColor = Color.LightSalmon;
cs.ForeColor = Color.DarkOrange;
// 连接过滤前和过滤后事件的处理程序
_flex.BeforeFilter += _flex_BeforeFilter;
_flex.AfterFilter += _flex_AfterFilter;
}
该代码可以创建一个将被用于显示未通过过滤器的行(而不是使他们不可见)的自定义样式。其次,该代码可以将处理程序与"过滤前"和"过滤后" 事件联系起来。事件处理程序列举如下:
在我们完成将自定义样式应用于被过滤掉的行之前,"过滤前"事件处理程序可以调用"开始更新"方法来防止表格自己进行重新绘制。"开始更新" 和"结束更新"方法可以代替已被弃用的"重绘"属性。
"过滤后"事件处理程序可以通过检索我们创建的样式来开始显示已筛选

// 在应用过滤器之前暂停绘制
void _flex_BeforeFilter(object sender, CancelEventArgs e)
{
_flexStyles.BeginUpdate();
}
// 在应用过滤器之后应用样式
void _flexStyles_AfterFilter(object sender, EventArgs e)
{
// 获取用于显示筛选出的行的样式

var cs = _flex.Styles["filteredOut"];


// 将样式应用于所有行
for (int r = _flexStyles.Rows.Fixed; r < _flexStyles.Rows.Count; r++)
{

var row = _flexStyles.Rows[r]; if (row.Visible)


{
// normal row, reset style row.Style = null;
}
else
{
// 过滤行,使其可见并应用样式
row.Visible = true; row.Style = cs;
}
}
// 恢复更新_
flexStyles.EndUpdate();
}


出的行。然后它可以对表格中的行进行扫描,并且将新的样式应用于"可见" 属性设置为""的所有行。这些是被过滤器隐藏起来的行。一旦做到这一点,代码可以调用"结束更新"来恢复表格的更新。

 自定义 UI 过滤

我们相信,默认的过滤行为和 UI 可以解决牵涉到列过滤中的绝大多数情况。但是,你可以独立地使用这些列过滤器列类,来实现自己的自定义用户界面。

例如,下面的代码显示了你可以如何使用"条件过滤器"的类来实现

C1FlexGrid iTunes 风格的搜索框。
这种类型的搜索,可以允许用户键入一个值,并自动过滤表格的行来显示任何列中包含搜索字符串的行。为落实 iTunes 风格的搜索,我们以一个包含将作为一个过滤器参数的文本的文本框开始。我们还定义了一个定时器,它将在用户停止往文本框中输入的几毫秒后适用于过滤器:

public Form1()
{
InitializeComponent();
// 在用户停止在搜索框中键入的 1/2 秒后配置定时器来适用于过滤器
_timer.Interval = 500;
_timer.Tick += t_Tick; // 监测搜索框的变化
_txtSearch.TextChanged += _txtSearch_TextChanged;
}
// 当搜索框中的文字发生变化时重新启动定时器
void _txtSearch_TextChanged(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}

既然定时器已经被配置好了,所有我们需要做的就是在计时器滴答计时时来创建和应用过滤器:

// 在计时器滴答计时时应用过滤器
void t_Tick(object sender, EventArgs e)
{
// done for now...
_timer.Stop();
// 配置过滤器
var filter = new C1.Win.C1FlexGrid.ConditionFilter(); filter.Condition1.Operator =
C1.Win.C1FlexGrid.ConditionOperator.Contains;
filter.Condition1.Parameter = _txtSearch.Text;
// 应用过滤器
_flex.BeginUpdate(); for (int r = _flex.Rows.Fixed; r < _flex.Rows.Count; r++)
{
bool visible = false; for (int c = _flex.Cols.Fixed; c < _flex.Cols.Count; c++)
{

if (filter.Apply(_flex[r, c]))


{
visible = true; break;
}
}

_flex.Rows[r].Visible = visible;


}
_flex.EndUpdate();
}


 C1FlexGrid 的属性组

C1FlexGrid 控件拥有一套非常丰富的属性,方法和事件。但你却无需为了有效地使用该控件,而知道所有的这些。
下面的参考资料显示了其中最为重要的属性,方法,以及按使用类型来分组的事件。而且其中某些元素不只出现在一个分组中。想知道有关于此的更多详细信息,请选择下面的特定元素。表格布局
Rows, Cols, AutoSizeCols, ScrollBars
光标和选择
SelectionMode, Select, ShowCell, Row, Col, RowSel, ColSel, MouseRow, MouseCol, BeforeRowColChange, AfterRowColChange, BeforeSelChange, AfterSelChange, KeyActionTab, KeyActionEnter

编辑
AllowEditing, ComboList, EditMask, BeforeEdit, StartEdit, ValidateEdit, AfterEdit, StartEditing, FinishEditing, Editor, CellButtonClick, KeyDownEdit, KeyPressEdit, KeyUpEdit, ChangeEdit

获取和设置值
Item (indexer), GetData, GetDataDisplay, SetData, GetCellRange, GetCellImage, SetCellImage, Clip, FindRow, Aggregate, CellChanged

用户界面
AllowEditing, AllowMerging, AllowResizing, AllowDragging, AllowSorting, BeforeSort, AfterSort, AutoSearch, AutoSearchDelay, BeforeDragColumn, AfterDragColumn, BeforeDragRow, AfterDragRow, BeforeResizeColumn, AfterResizeColumn, BeforeResizeRow, AfterResizeRow, ShowScrollTip

概述和总结
Subtotal, Tree, IsNode, Level, Collapsed, BeforeCollapse, AfterCollapse

合并单元格
AllowMerging
数据绑定
DataSource, DataMember, AfterDataRefresh, AutoResize, GridError

保存、加载和打印表格
LoadGrid, SaveGrid, LoadExcel, SaveExcel, ClipSeparators, PrintGrid
OLE 拖放
DragMode, DropMode, BeforeMouseDown

  • No labels