再谈TableDataTable

有了前面的知识,我们可以更深入地理解TableDataTable的区别。

示例一

假定有一个订单表,这个表有1000行,假定其中100个订单是订购产品PD01的,我们在该表筛选出PD01的订单,现在你在订单表看到的行数是100行。

我们在窗口执行下面的代码:

For Each r As Row in Tables("订单").Rows
    r("
折扣"
) = 0.2
Next

上面的代码,将你看到的100(订购PD01的订单)的折扣设为0.2

我们再来测试下面的代码:

For Each dr As DataRow in DataTables("订单").DataRows
    dr("
折扣"
) = 0.2
Next

上面的代码将所有1000(全部订单)的折扣设为0.2

总之,如果你要对看得见的行进行操作,你就用Table,如果你要对所有行进行操作,你就用DataTable

示例二

DataTable中的行用DataRow表示,Table中的行用Row表示。
Table
的数据来自于DataTable,所以每一个Row都对应一个DataRowRow有一个名为DataRow的属性,用于返回该Row所对应的DataRow
例如判断订单表的选定行是否是新增行,可以用下面的代码:

If Tables("订单").Current.DataRow.RowState = DataRowState.Added Then
End If

提示:Row并没有RowState方法,所以要 判断其是否是新增行,必须使用DataRowRowState属性。

同样DataTable中的列用DataCol表示,Table中的列用Col表示,每一个Col都对应一个DataColCol有一个名为DataCol的属性,用于返回该Col对应的DataCol
例如要重置当前表选定列,但是Col并没有重置列的方法,我们只能调用DataColRaiseDataColChanged方法:

With CurrentTable
    .Cols(.ColSel).DataCol.RaiseDataColChanged()

End With

对应的,Table也有一个名为DataTable的属性,用于返回该Table所属的DataTable
例如要保存当前表,因为Table没有保存命令,只能调用DataTable的Save方法:

CurrentTable.DataTable.Save()

示例三

上面的例子提到,TableRowCol都有对应的属性,用于返回其所属的DataTableDataRowDataCol
但是却不可能反过来操作,因为一个DataTable,可能对应有多个Table
不过通过TableFindRow方法,我们可以获得某一个DataRowTable中的位置。
例如我们希望在订单表中,将光标定位到最近一次订购PD01的行上:

Dim dr As DataRow
Dim
po As Integer
dr = DataTables("
订单").Find("产品 = 'PD01'","日期 Desc") '找出最后一次订购PD01产品的DataRow
po = Tables("
订单").FindRow(dr)'找出该DataRowTable中的位置
If
po >= 0 Then '如果找到
    Tables(
"订单").Position = po '选定
End If

因为Table可能会进行筛选,所以一个DataRow可能不会出现在Table中,此时FindRow方法会返回-1,表示没有找到指定的DataRow

再例如正在订单表操作,希望在订单表中选定一行时,客户表的光标能够自动定位到该客户,这样我们一旦从订单表回到客户表,即可看到刚刚所选订单的客户资料。
为此我们在订单表的CurrentChanged事件中设置如下代码: 

Dim wz As Integer
Dim
dr As DataRow
dr = DataTables("
客户").Find("[客户ID] = '" & e.Table.Current("客户ID") & "'")
If
dr IsNot Nothing Then
    wz = Tables("
客户").FindRow(dr)
    If wz >= 0 Then
        Tables
("
客户").Position = wz
    End If
EndIf

这种在不同表之间联动的技巧应该掌握,也许你觉得通过关联,不就可以在订单表中以关联表的形式显示客户信息吗?
但是:
1
、有的时候我们并不希望建立太多的关联。
2
、即使建立了关联,我们也可能关闭了Foxtable的双向关联特性,这样就无法以关联表的形式查看父表数据了。

示例四

其实Table也有Find方法,不过TableFind方法是根据内容查找,而不是根据表达式来查询,返回的是行的位置。

例如:

With CurrentTable
    Dim
r As Integer
   
r = .Find("PD01", 0, "
产品", False, False, True)
    If
r > - 1 Then '如果找到符合条件的行

        .
Position = r
    End If
End With

TableFind方法功能较弱,但是使用简单,通常用于进行简单的查找和定位。
Table
还有一个FindRow方法,可以使用表达式进行查找,例如订单表的公司名称列中,查找包括“湛江”二字的单位:

With Tables("订单")
    Dim
r As Integer
    r = .FindRow("[
公司名称] Like '%湛江%'", .Position + 1, True ) '从当前行开始查找

    If
r >= 0 Then '如果找到的话
       
.Position = r'定位到找到的行。
   
End If
End With

显然TableFindFindRow方法的查找结果,会受筛选的影响,因为作为Table的方法,只是在可见的行中进行查找; 此外因为没有排序参数,这两个方法无法按照特定的顺序进行查找,只能从上往下地顺序查找。
相对来说,DataTableFind方法则要强大很多,不仅可以用表达式查询,有排序参数,还可以指定返回第几个符合条件的行,而且可以不受筛选的影响,只要有符合条件的行,始终都能找出来。
一些复杂的查找,用TableFindFindRow方法是无法实现的,我们可以先用DataTableFind方法,找出符合条件的DataRow, 然后利用TableFindRow方法 ,找出该DataRowTable中的位置。
例如我们希望定位到客户CS01第二次订购PD01产品的行:

Dim dr As DataRow
Dim
Index As Integer
dr = DataTables("
订单").Find("客户 = 'CS01' And 产品 = 'PD01'","日期",1)
Index = Tables("
订单").FindRow(dr)
If
Index > 0 Then
    Tables(
"
订单").Position = Index
End If

提示:TableFindRow有两个语法,一个是根据表达式查询,一个是查找指定的DataRow在表中的位置。

示例五

除非需要定位 到符合条件的行,否则是不需要使用TableFindFindRow方法的。

例如希望在订单表输入产品编号,能够从产品表提取该产品的名称、型号、规格,自动输入到订单表的相关列中。
为实现此目的,可在订单表的DataColChanged事件中输入如下代码:

If e.DataCol.Name ="产品编号" Then '发生变化的是产品名称吗?
    '
在产品表找出该产品

   
Dim dr As DataRow = DataTables("产品").Find("编号 = " & "'" & e.DataRow("产品编号") & "'" )
    If dr IsNot Nothing'
如果找到,则设置各列内容

        e.DataRow("
品名")= dr("品名")
        e.DataRow("
型号")= dr("型号")
        e.DataRow("
规格")= dr("规格"
)
    End If
EndIf

在上面的代码中,我们是在DataTable中查找所输入编号的产品,这是显然的, 首先我们只是引用数据,并不需要定位,其次如果在Table中查找,一旦Table进行了筛选,而你输入的产品编号恰好不符合筛选条件,那么就无法实现自动输入。

示例六

DataTable有一个Select方法,以集合的形式,返回所有符合指定条件的行。
例如对于199914日订购PD01的订单,希望将其折扣统一设置为0.12,代码为:

Dim drs As List(Of DataRow)
drs = DataTables("
订单").Select("[产品] = 'PD01' And [日期]= #1/4/1999#"
)
For Each
dr As Datarow In drs
    dr("
折扣") = 0.12
Next

Table也有Select方法,不过作用是完全不同的,该方法用于选定指定位置的单元格或区域。
例如选定当前表的第1行第1:

CurrentTable.Select(0,0)

我们经常需要选定整行、整列、甚至整个表,可以参考下面的代码:

'选定第二列
With Tables("订单")
    .Select(0,1, .Rows.Count - 1,1
)
End With

'选定数量列
With Tables("订单")
   
Dim c As Integer= .Cols("数量").Index

    .
Select(0, c, .Rows.Count - 1, c)
End With

'选定第三行
With Tables("订单")
    .Select(2,0,2, .Cols.Count - 1
)
End With

'选定第三行的数量单元格
With
Tables("订单")
   
Dim c As Integer = .Cols("数量").Index

    .
Select(2,c,2,c)
End With

'选定整个表
With Tables("订单")
    .
Select(0,0, .Rows.Count -1, .Cols.Count -1)
End With

示例七

DataTable侧重于数据管理,而Table侧重于数据展示,所以他们各自有自己独有的方法,基本上没有什么重复的。
例如DataTable的
DeleteFor方法,用于按条件批量删除行,Table就没有这样的方法。
DataTable的方法针对的是所有行,可是有的时候,我们只是希望对可见的行进行操作,可是Table却没有对应的办法,需要使用DataTable的方法才行,那么该怎么办呢?
其实很简单,DataTable的方法大多是可以设置条件的,我们可以将Table的Filter属性值传递给这些方法,即可让这些方法仅对可见的行生效。
例如进行筛选后,希望删除所有筛选出的行,只需:

CurrentTable.DataTable.DeleteFor(CurrentTable.Filter)

我们将Table的Filter属性传递给DataTable的DeleteFor方法,显然删除的就是我们筛选出来的行,也就是可见的行。
反过来,如果在筛选后,要删除不符合条件的行,也就是被隐藏的行,只需:

With CurrentTable
    .DataTable.DeleteFor("(" & .Filter &") = False")

End With

假定原来的筛选条件: [产品] = 'PD01',那么上面的代码传递给DeleteFor的条件就是:([产品] = 'PD01') = False。
删除的自然就是条件不成立的行,也就是被隐藏的行。

再例如,希望将筛选出来的订单的折扣,替换为0.15,代码为:

DataTables("订单").ReplaceFor("折扣", 0.15, Tables("订单").Filter)

示例八

前面提到,DataTable和Table各自有自己独有的方法,基本上没有什么重复的。
不过DataTable和Table都有Compute方法,用于统计数据;
DataTable的Compute方法统计所有行,而Table的Compute方法只统计Table中的行,也就是经过筛选后见到的行。
所以
如果没有进行筛选,那么两者的统计结果是一样的,如果进行了筛选,就需要根据具体情况选用了。

例如你在订单表中筛选出了客户CS01的所有行,那么:

Dim Sum1 As Double = Tables("订单").Compute("Sum(数量)")
Dim
Sum2 As Double = DataTables("订单").Compute("Sum(数量)"
)

Sum1将等于客户CS01的订购数量,而Sum2将等于所有客户的订购数量。

同样:

Dim Sum1 As Double = Tables("订单").Compute("Sum(数量)", "产品 = 'PD01'")
Dim
Sum2 As Double = DataTables("订单").Compute("Sum(数量)", "产品 = 'PD01'"
)

Sum1将等于客户CS01订购产品PD01的数量,Sum2将等于所有客户订购产品PD01的数量。

示例九

很多类型,例如常用的 CrossTableBuilderGroupTableBuilder ,只能对DataTable进行操作。
如果你希望对筛选后的数据,也就是Table中的数据进行统计,那么该怎么办呢?
其实一样,将这些类型的Filter属性的值,设为Table的Filter属性值即可。

例如:

Dim g As New GroupTableBuilder("统计表1",DataTables("订单"))
g.Groups.AddDef("产品")
g.Totals.AddDef("数量")
g.Filter =Tables("订单").Filter
g.Build()

MainTable
=Tables("统计表1")

示例十

Foxtable提供了四个增加行的命令,例如你要在订单表增加一行,下面四个方法都是有效的:

DataTables("订单").AddNew()
DataTables(
"订单").DataRows.AddNew()
Tables
("订单").AddNew()

Tables(
"订单").Rows.AddNew()

下面的代码是完全等效的,都是在DataTable中增加一行,前者不过是后者的简写:

DataTables("订单").AddNew()
DataTables(
"订单").DataRows.AddNew()

同样,下面的代码是也是完全等效的,都是在Table中增加一行,前者是后者的简写而已:

Tables("订单").AddNew()
Tables(
"订单").Rows.AddNew()

所以其实只有两种增加行的方法,分别是在DataTable和Table中增加行。 

那么在DataTable和Table中增加行,会有什么差别呢?差别有两个:

1、在Table中增加行,光标会自动移到新增加的行,使得新增行始终可以见。
2、在Table中增加行,而且该Table是一个关联子表,那么新增行会自动从父表中取得关联列的内容,并填入到新增行的关联列中。

例如订单表和订单明细表通过订单编号建立关联,如果要给当前选定订单增加一个订单明细,只能通过下面的代码:

Tables("订单.订单明细").AddNew()

示例十一

DataTable和Table有很多同名的属性,例如AllowEdit属性,在Table和DataTable都有,那么两者如何配合使用呢?
原则是:Table的同名属性优先。
如果Table没有设置过该属性的值,那么从DataTable继承,如果Table已经设置了该属性的值,那么以Table的设置为准。

例如在一个简单的订单管理系统中,产品表和订单表通过产品编号建立关联,客户表和订单表通过客户编号建立关联。
现在希望产品表内容只能在Tables("产品")修改,也就是只能在主表修改,不能在关联表修改,可以这样设置代码:

DataTables("产品").AllowEdit = False
Tables
("产品").AllowEdit = True

由于Tables("产品")的AllowEdit属性被设置为True,所以它是可以编辑的。
其它基于DataTables("产品")的Table,例如Tables("订单.产品")、Tables("客户.订单.产品"),因为他们没有设置AllowEdit属性,所以其AllowEdit属性的值从DataTables("产品")继承,而DataTables("产品")的AllowEdit属性被设为False,所以这些Table是不能被编辑的,也就是被锁定了。

显然,只有不同的Table需要不同的设置时,才需要使用Table的设置属性,否则直接用DataTable的设置属性即可。

实际上DataTable本身是不能编辑的,因为用户根本就看不到DataTable,用户能编辑的只能是Table,所以AllowEdit属性本应该只出现在Table中, 而DataTable之所以也提供这个属性,只是为了统一设置的方便。
例如,假定不管是作为主表还是关联表,都要禁止编辑订单表,只需:

DataTables("订单").AllowEdit = False

如果DataTable没有AllowEdit属性,那么就必须逐个Table设置,代码显得繁琐很多:

Tables("订单").AllowEdit = False
Tables("产品.订单").AllowEdit = False
Tables("客户.订单").AllowEdit = False

我们这里只是以AllowEdit属性为例说明,所有DataTable和Table同时具备的属性,都有和AllowEdit属性一样的作用原理。

示例十二

前面已经提到,DataTable和Table有各自不同的作用,所以他们基本上不会有重复的属性和方法,即使有同名的属性或方法,其作用也是有所区别,例如上面介绍的AddNew方法和AllowEdit属性。
不过有一个例外,DataRow和Row
有七个同名的方法和属性:Save、Reject、Load、Delete、RemoveLocked、IsNull, 它们的作用是完全相同的;调用Row上述成员时,Foxtable实际上调用的是DataRow的同名成员。

也就是说,下面的代码:

If Tables("表A").Current.IsNull("第一列") = False Then
    Tables(
"表A").Current.Locked = True
End If
Tables(
"表B").Rows(0).Delete()

在执行之前,会自动转换为下面的代码:

If Tables("表A").Current.DataRow.IsNull("第一列") = False Then
    Tables(
"表A").Current.DataRow.Locked = True
End If
Tables(
"表B").Rows(0).DataRow.Delete()

你也许会问,为什么单单这个成员被特殊处理,为什么不考虑通过Table、Col、Row直接调用DataTable、DataCol、DataRow的全部成员?
我也不能解释,也许Foxtable的策划人员感觉这七个成员使用的频率特别高,所以给了特别待遇,让我们编写代码的时候,能够更简洁一点。


本页地址:http://www.foxtable.com/webhelp/topics/1467.htm