前言
很多人都在问,ClientDataSet如何才能在不连接数据库得情况下,用程序创建起来,并打开数据集。
在研究了一下TClientDataSet数据集后,发现如果要让ClientDataSet打开的话(Open),必须满足三个条件中的一个:
ProviderName属性赋值,即有数据源提供者。
Data属性赋值。即从其它已经打开的数据集中获得表结构和数据。
FileName赋值,即从本地文件获取数据和MetaData。
这三个条件是TClientDataSet的Active属性的帮助中说的。思考一下,第一和第二基本被排除,我如果有了现成的数据集,还要创建干吗?第三个又不是那么容易,你哪知道文件的格式是什么呢?
那到底该怎么做呢?
失败的尝试
突然想起TClientDataSet中支持InternalCalc(内部计算)字段。内部计算字段可以在设计期加进去,或者也可以在运行期动态加入。我们为了方便,就在设计期加入。见下图:
可以通过双击控件(TClientDataSet)进入字段编辑器,然后右键选择“New Field”命令,得到如图所示的界面。填写Name,Type和FieldType。
记住:FieldType一定要是“InternalCalc”。如此反复,你可以选择添加多个字段。
打开!报错:“ProviderName”或Data没有赋值。
这个方法不正确,既然要Data,我尝试着,从其它ClientDataSet的Data属性赋值上。得到结果是:自己新建的字段和数据源的字段都显示。
我又进行了进一步的尝试:那就是让这个有着新建的字段和原有字段的ClientDataSet的Data赋值到另一个新的ClientDataSet上,却发现新建的字段没有进入。
总得来说,这次尝试是失败的,不过有如下总结:
光靠新建内部计算字段是不可行的。
新建的计算字段不可以进入Data属性而被传递给第二个ClientDataSet。
XML
又是XML!ClientDataSet本就是断开连接的数据集控件,应此提供了将数据缓存到磁盘的功能SaveToFile(),其详细声明如下:
procedure SaveToFile(const FileName: string = ''; Format: TDataPacketFormat = dfBinary);
注意到其中的TDataPacketFormat。它是个枚举类型,全部的枚举值如下:
TDataPacketFormat = (dfBinary, dfXML, dfXMLUTF8);
大家可能注意到了,dfBinary和dfXMLUTF8都不是我们需要关心的,关心一下我们的dfXML格式。这是个让人兴奋的消息。有了XML,我们就可以查看ClientDataSet的MetaData的定义,而且更有了修改的可能!
调用一下SaveToFile功能,注意保存格式为dfXML。打开文件,如下显示:
<?xml version="1.0" standalone="yes" ?>
- <DATAPACKET Version="2.0">
- <METADATA>
- <FIELDS>
- <FIELD attrname="NormInfoID" fieldtype="i4">
<PARAM Name="PROVFLAGS" Value="7" Type="i4" Roundtrip="True" />
</FIELD>
<FIELD attrname="Description" fieldtype="string.uni" WIDTH="160" />
<FIELD attrname="NormInfoVal" fieldtype="string.uni" WIDTH="510" />
<FIELD attrname="NewField" fieldtype="i4" />
<FIELD attrname="Boolean" fieldtype="Boolean" />
<FIELD attrname="Date" fieldtype="date" />
<FIELD attrname="Time" fieldtype="time" />
</FIELDS>
<PARAMS DEFAULT_ORDER="1" PRIMARY_KEY="1" />
</METADATA>
<ROWDATA />
</DATAPACKET>
看这个全部的,比较混乱,先看一下结构:
<?xml version="1.0" standalone="yes" ?>
- <DATAPACKET Version="2.0">
+ <METADATA>
<ROWDATA />
</DATAPACKET>
这下比较清楚了:
整个XML定义为一个DataPacket,DataPacket包括两个部分:MetaData和RowData。显然对我们来说,RowData可以忽视,到时候将RowData的节点清空就是。而MetaData呢?
看看全部的XML格式,可以看出:MetaData包括Fields和Params。即字段和数据集参数。
字段
先重点考虑Fields。看一下Field节点的定义,大概可以看出一些:
attrname是指FieldName
fieldtype是指字段类型
width是需要宽度的字段类型的参数
如果需要定义字段的属性,再加上<Param />节点
下面我们来研究一下字段类型,我想这也是最主要的!其它属于高级用法。
MSSQL Type fieldtype WIDHT SUBTYPE DECIMALS READONLY
Binary bin.hex 50
Bit boolean
Char string 10 FixedChar
DateTime dateTime
Decimal fixed 18
Float r8
Image bin.hex Binary
Int i4
Money fixed 19 4
nChar string.uni 20
nText bin.hex Text
Numeric fixed 18
nVarChar string.uni 100
SmallDateTime dateTime
Real r8
SmallInt i2
SmallMoney fixed 10 4
Text bin.hex Text
TimeStamp bin.hex 8 true
tinyInt i2
UniqueIdentifier string 38 Guid
VarBinary bin.hex 50 Binary
VarChar string 50
见上表,这是我在SQLServer2000中,将所有类型的字段都用上,然后在ClientDataSet中得到的,类型对应关系。注意到有SubType属性,来帮助决定类型。其中没有值的单元格,表示该属性没有用到。宽度值中,除了UniqueIdentifier是38外,其余都是采用系统默认宽度。
分析一下这一张表,可以得到如下一些特点:
后缀uni表示unicode,没有后缀,则表示是AnsiCode
后缀hex表示16进制
i后的数字表示整数所占用的位数,默认为4位
r8表示8位浮点数
字段参数
注意到某些字段的定义下有节点:
<PARAM Name="PROVFLAGS" Value="7" Type="i4" Roundtrip="True" />
先来解释一下“PROVFLAGS”,在Delphi的源码里查找可以发现其实就是对应着TField的属性ProviderFlags,那么7也就是好理解的了。
看TProviderFlag和TProviderFlags的定义
TProviderFlag = (pfInUpdate, pfInWhere, pfInKey, pfHidden);
TProviderFlags = set of TProviderFlag;
既然是集合,那么7很显然就是一个集合的整数表示:按顺序排的话,7其实就是表示结合[pfInUpdate, pfInWhere, pfInKey]。
不过不知道为什么,查看TField的属性,pfInKey不在集合中。
MetaData属性
看看
<PARAMS DEFAULT_ORDER="1" PRIMARY_KEY="1" />
可以看出DEFAULT_ORDER和PRIMARY_KEY的意思,但是后面的值呢?研究之后,发现,它是主键或索引字段的Index。如果同时两个字段都是索引,那么它的格式是:"1 2",中间采用空格隔开。
如何使用XML格式?
好了,格式大概都知道了,现在问题是如何转载到ClientDataSet呢?
文件是可以,但是显然不满足我们当初的需求,在查看一下接口定义,发现除了LoadFromFile还有LoadFromStream。查看一下源码,其实LoadFromFile就是调用了LoadFromStream。
好了,只要我们创建了这样的XML流,就可以动态创建ClientDataSet了!
至于如何创建XML流,不在此讨论了