??xml version="1.0" encoding="utf-8" standalone="yes"?>
与TTable、TQuery一PTClientDataSet也是从TDataSetl承下来的,它通常用于多层体系l构的客L。TClientDataSet最大的特点是它不依赖于BDE(Borland Database Engine)Q但它需要一个动态链接库的支持,q个动态链接库叫DBCLIENT.DLL。在客户端,也不需要用TDatabase构gQ因为客Lq不直接q接数据库?br /> ׃TClientDataSet是从TDataSetl承下来的,所以,它支持诸如编辑、搜索、浏览、纠错、过滤等功能。由于TClientDataSet在内存中建立了数据的本地副本Q上q操作的执行速度很快。也正是׃TClientDataSetq不直接q接数据库,因此Q客L序必L供获取数据的机制。在Delphi 4中,TClientDataSet有三U途径获取数据Q?br />.从文件中存取数据?br />.从本地的另一个数据集中获取数据?br />.通过IProvider接口从远E数据库服务器获取数据?br /> 在一个客L序中Q可以同时运用上qCU机制获取数据?
11.1 览和编辑数?/b>
和其他数据集构g一P可以用标准的数据控g昄由TClientDataSet引入的数据集Q当Ӟq需要借助于TDataSource构g?br /> ׃TClientDataSet是从TDataSetl承下来的,所以,凡是其他数据集构件支持的功能QTClientDataSet构g也大致具备。不同的是,TClientDataSet能够在内存中建立数据的副本,因此QTClientDataSet比其他数据集构g增加了一些特D的功能?br />11.1.1 览数据
可以用标准的数据控g昄由TClientDataSet引入的数据集。在q行期,可以调用诸如First、GotoKey、Last、Next和Prior{函数来览数据?br /> TClientDataSet也支持书{֊能,可以用书{来标记某条记录Q以后就可以方便地找到这条记录?br /> 对于TTable、TQuery{数据集构g来说Q只能读RecNo属性来判断当前记录的序受对于TClientDataSet构g来说Q还可以写RecNo属性,使某一序号的记录成为当前记录?br />11.1.2 CanModify属?/b>
TDataSet的CanModify属性用于判断数据集中的数据是否可以修改。CanModify属性本w是只读的,也就是说Q数据是否能够修改不取决于应用程序?br /> 不过QTClientDataSet构g有其Ҏ性,因ؓTClientDataSet已经把数据在内存中徏立了副本Q因此,应用E序可以军_是否允许修改数据。如果不允许用户修改数据Q只要把ReadOnly属性设为TrueQ此ӞCanModify属性肯定返回False?br /> 与其他数据集构g不同Q修改TClientDataSet构g的ReadOnly属性时Q不需要事先把Active属性设为True?br />11.1.3 取消修改
TClientDataSet传输数据的基本单位称为数据包Q当前的数据包可以由Data属性来讉K。不q,用户Ҏ据的修改q不直接反映到Data属性中Q而是临时写到一个日志即Delta属性中Q这样做的好处是以后随时可以取消修改?br /> 不过Q这里要说明一点,管用户的修改ƈ没有反映到DataQ当用户在数据控件中看到的却是最C改的数据。如果一条记录被反复修改了多ơ,用户看到的只是最新的数据Q但日志中却记蝲了多ơ?br /> 要取消上一ơ的修改Q调用UndoLastChange函数。UndoLastChange需要传递一个布类型的参数叫FollowChangeQ如果FollowChange参数设ؓTrueQ光标就Ud被恢复的记录上,如果FollowChange参数设ؓFalseQ光标仍然在当前记录上?br /> ChangeCount属性返回日志中记蝲的修Ҏ数。如果一条记录被反复修改了多ơ,每调用一ơUndoLastChange能够逐取消上一ơ的修改?br /> UndoLastChange只能取消上一ơ的修改Q如果想一下子取消所有的修改Q首先要选择一个记录,然后调用RevertRecord。RevertRecord从日志中取消所有对当前记录的修攏V?br /> TClientDataSetq有一个SavePoint属性,它能把当前的~辑状态保存v来,以后随时可以q回当时的状态。例如,可以q样保存当前的状态:
BeforeChanges := ClientDataSet1.SavePoint;
以后Q可以这h恢复当时的状态:
ClientDataSet1.SavePoint := BeforeChanges;
应用E序可以保存多处状态,可以恢复其中一个状态,不过Q一旦某个状态被恢复Q在其之后的状态就无效?br /> 如果要一下子取消日志中记载的所有修改,可以调用CancelUpdates函数。CancelUpdates把日志清空Q取消所有的修改?br /> 如果LogChanges属性设为FalseQ用户对数据的修改就会直接反映到Data属性中?
11.1.4 合ƈ修改
要把日志中记载的修改合ƈ到Data属性中Q有两种方式Q具体用哪一U方式,取决于应用程序获取数据的机制。不q,不管是哪U机Ӟ合ƈ后,日志自动被清I?br /> 对于一个从文g中获取数据的E序来说Q只要调用MergeChangeLog函数Q就把日志中记蝲的修改合q到Data属性中。不用担心其他用户同时修改了数据?br /> 对于一个从应用服务器获取数据的E序来说Q就不能调用MergeChangeLog来合q数据,而要调用ApplyUpdates函数QApplyUpdates会把日志中记载的修改传递给应用服务器,待应用服务器成功地把数据更新了数据库服务器后Q才会合q到Data属性中?br />11.1.5 U错
TClientDataSet支持U错功能。一般情况下Q需要自己徏立纠错规则,以便对用戯入的数据q行U错?br /> 此外Q如果获得了IProvider接口的话Q还可以从远E服务器引入U错规则?br /> 有时候,客户端可能需要暂时禁止纠错,因ؓ客户端从应用服务器检索数据是分阶D进行的Q在所有的数据索完毕之前,有些U错规则很可能会报错?
要暂时禁止纠错,可以调用DisableConstraintsQ要重新允许U错Q可以调用EnableConstraints函数。DisableConstraints和EnableConstraints实际上都是作用于一个内部的计数?br />11.2 ??/b>
使用索引有这么几个好处:
.在数据集中定位记录比较快?br />.能够在两个数据集之间建立Lookup或Master/Detail关系?br />.可以对记录排序?br /> 在多层体pȝ构中Q当客户E序从应用服务器索数据时Q它同时获得了默认的索引。默认的索引叫DEFAULT_ORDERQ可以用这个烦引排序,但不能修Ҏ删除q个索引?br /> 除了默认的烦引外QTClientDataSetq对日志中记载的记录自动建立了一个副索引叫CHANGEINDEX。与DEFAULT_ORDER一P不能修改或删除这个副索引?br /> 另外Q还可以使用数据集中已徏立的其他索引Q或者自己徏立烦引?br />11.2.1 创徏一个新的烦?/b>
要创Z个新的烦引,可以调用AddIndex。AddIndex需要传递若q个参数Q?br /> 一是Name参数Q用于指定烦引名。在q行期切换烦引时需要用到烦引的名称?br /> 二是Fields参数Q它是一个字W串Q用于指定烦引中的字D名Q彼此之间用分号隔开?br /> 三是Options参数Q用于设|烦引的选项Q包含ixDescending元素表示按降序排列,包含ixCaseInsensitive元素表示大小写不敏感?br /> 四是DescFields参数Q它也是一个字W串Q用于指定若q个字段名,q些字段按照降序排列?br /> 五是CaseInsFields参数Q它的作用与DescFields参数cMQ包含在CaseInsFields参数中的字段对大小写不敏感?br /> 六是GroupingLevel参数Q用于指定分l别,其g能超q烦引中的字D|?br /> 下面的代码创Z一个烦引:
If Edit1.Text <> '' and ClientDataSet1.Fields.FindField(Edit1.Text) then
Begin
ClientDataSet1.AddIndex(Edit1.Text+'Index',Edit1.Text,
[ixCaseInsensitive],'','',0);
ClientDataSet1.IndexName := Edit1.Text + 'Index';
End;
Z避免创徏一个烦引,可以临时用IndexFieldNames属性来指定若干个字D,让数据集按这些字D|序?br />11.2.2 删除和切换烦?/b>
要删除一个先前创建的索引Q可以调用DeleteIndexq指定要删除的烦引名U。注意:DEFAULT_ORDER和CHANGEINDEX不能删除?br /> 如果建立了多个烦引,可以L选择其中的一个烦引,q就要用到IndexName属性?br />11.2.3 用烦引把数据分组
选择了一个烦引后Q数据集自动按其中的字D进行排序。这P临近的记录往往在关键字D上含有相同的倹{例如,假设有一个表是这LQ?br />SalesRep Customer OrderNo Amount
1 1 5 100
1 1 2 50
1 2 3 200
1 2 6 75
2 1 1 10
2 3 4 200
可以看出QSalesRep字段的值有重复的。对于SalesRep字段的gؓ1的来_Customer字段的g有重复的。这是_可以按SalesRep字段分组Q进而再按Customer字段分组。显Ӟq里的分l别是不同的,按SalesRep字段建立的分l属于第一U,按Customer字段建立的分l属于第二。实际上Q分l别取决于字段在烦引中的顺序?br /> TClientDataSet可以军_是否按照分组U别来显C录的倹{例如,也许想以下面q种形式昄数据Q?br />SalesRep Customer OrderNo Amount
1 1 5 100
2 50
2 3 200
6 75
2 1 1 10
2 3 4 200
要判断当前记录某一U的什么位|,可以调用GetGroupState函数。GetGroupState函数需要传递一个参敎ͼ用于指定分组U别?br />11.3 ???D?/b>
与其他数据集一P也可以在TClientDataSet建立的数据集中增加计字Dc计字D늚值是Z同一个记录中的其他字D计出来的?br /> 在其他数据集中,只要用户修改了数据或当前记录发生改变Q就会触发OnCalcFields事gQ换句话_计算字段的值就被计一ơ?br /> TClientDataSet引入了“内部计字D”的概念。与一般的计算字段不同的是Q内部计字D늚值将随其他字D늚g起存取,q样Q只有当用户修改了数据才会触发OnCalcFields事gQ如果仅仅改变了当前记录Q不会触发OnCalcFields事g。也是_内部计算字段的值需要重新计的Z大大减少?br /> 在处理OnCalcFields事g的句柄中Q首先要判断State属性。如果State属性返回dsInternalCalcQ此旉要计内部计字D늚倹{如果State属性返回dsCalcFieldsQ此旉要计一般的计算字段的倹{?br />11.4 l???/b>
TClientDataSet增加了统计的功能Q它可以Z分组自动计算d、^均、计数、最大、最倹{当用户~辑数据Ӟq些l计g自动跟着变化?br />11.4.1 指定l计方式
要指定怎样q行l计Q就要用到Aggregates属性。这个属性是一个TAggregates对象Q它用于理一lTAggregate对象?br /> 在设计期Q可以单击Aggregates属性边上的省略h钮打开如图11.1所C?br />的编辑器?br /> ?1.1 理一lTAggregate对象
单击按钮可以增加一个TAggregate对象Q单L钮可以删减一个TAggregate对象Q单L钮可以把TAggregate对象前移Q单L钮可以把TAggregate对象后移?br /> 可以用字D늼辑器专门创徏一个用于表辄计值的字段Q该字段的类型必L“Aggregate”。Delphi 4会自动创Z个TAggregate对象Qƈ加到Aggregates属性中。选择一个TAggregate对象QObject Inpector显C对象的属性?br /> 其中QExpression属性用于指定统计表辑ּQ例如:
Sum(Field1)
也可以是比较复杂的表辑ּQ?br />Sum(Qty * Price) - Sum(AmountPaid)
在表辑ּ中,可以使用下列l计q算W:
.Sum计算一l数据的d?br />.Avg计算一l数据的q_倹{?br />.Count计算一l数据中的非I值的个数?br />.Min计算一l数据的最倹{?br />.Max计算一l数据的最大倹{?br /> 除了上述几个l计q算W外Q还可以使用qo条g中所能用的q算W,但不能嵌套。在一个表辑ּ中,可以混合出现几个l计值或帔RQ但不能混合出现l计值和字段?br /> Sum(Qty * Price){合法}
Max(Field1) - Max(Field2){合法}
Avg(DiscountRate) * 100{合法}
Min(Sum(Field1)){非法Q不能嵌套}
Count(Field1) - Field2{非法Q统计值和字段不能混合出现在一个表辑ּ中}
11.4.2 指定分组
默认情况下,l计值是Z数据集中所有的记录计算出来的。不q,也可以针对一部分记录计算l计|q就需要事先徏立分l?br /> 前面在介l烦引时已经提到分组的概c可以通过IndexName属性和GroupingLevel属性来选择使用哪个索引以及最大的分组U别?br /> 例如Q假设有一个表是这LQ?br />SalesRep Customer OrderNo Amount
1 1 5 100
1 1 2 50
1 2 3 200
1 2 6 75
2 1 1 10
2 3 4 200
如果要按SalesRep字段分组Qƈ且指定其中的W一U,E序代码应当q样写:
Agg.Expression := 'Sum(Amount)';
Agg.IndexName := 'SalesCust';
Agg.GroupingLevel := 1;
Agg.AggregateName := 'Total for Rep';
11.4.3 怎样获取l计?/b>
要获取统计|可以调用TAggregate对象的Value函数。如果统计值是Z数据集中所有的记录计算出来的,随时可以调用Value函数。如果统计值是Z分组计算出来的,必须保证当前记录正好位于该分l内。因此,在调用Value之前Q最好先调用GetGroupState函数看看当前记录是否位于该分l内?br /> 要在数据控g中显C统计|必须事先在字D늼辑器中创Z个永久字D对象,该字D늚cd必须是Aggregate?br />11.5 ???/b>
通过Data属性可以访问客L序从应用服务器检索到的数据。程序示例如下:
Procedure TForm1.Button1Click(Sender: TObject);
Begin
ClientDataSet1.Data := ClientDataSet1.Provider.DataRequest(FilterEdit.Text);
End;
11.5.1 直接对Data属性赋?/b>
前面讲过Q客L序既可以通过IProvider接口获取数据Q也可以从另一个数据集获取数据Q后者就是通过Data属性赋值的。程序示例如下:
ClientDataSet1.Data := ClientDataSet2.Data;
一旦Data被赋|可以用标准的数据控件显C些数据?br /> 注意Q当从另一个数据集获取数据Ӟ另一个数据集的日志也被复制q来Q但不包括原来的范围和过滤条件?br /> 如果要从另一个基于BDE的数据集中获取数据,可以通过数据集构件的Provider属性,E序CZ如下Q?br /> ClientDataSet1.Data := Table1.Provider.Data;
如果要从一个自定义的数据集获取数据Q首先要创徏一个时的TProvider构gQ然后设|其DataSet属性指定这个自定义的数据集。程序示例如下:
TempProvider := TDataSetProvider.Create(Form1);
TempProvider.DataSet := SourceDataSet;
ClientDataSet1.Data := TempProvider.Data;
TempProvider.Free;
11.5.2 在数据包中加入自定义的信?/b>
可以把自定义的信息加到数据包中。当把数据保存到文g或流中时Q这些自定义的信息也保存到文g或流中。如果把数据包直接赋值给另一个数据集的话Q这些自定义的信息也被复制?br /> 要把自定义的信息加到数据包中Q可以调用SetOptionalParam函数。要从数据包中检索自定义的信息,可以调用GetOptionalParam。程序示例如下:
Procedure TAppServer.Provider1UpdateData(Sender: TObject; DataSet: TClientDataSet);
var
WhenProvided: TDateTime;
Begin
WhenProvided := DataSet.GetOptionalParam('TimeProvided');
...
End;
11.5.3 克隆另一个数据集
调用TClientDataSet的CloneCursor函数可以获得一个数据集的完全相同的副本。它与直接通过Data属性赋值是有区别的?br /> 区别之一Q数据在两个数据集之间是׃n的,修改其中一个将同时修改另一个?br /> 区别之二Q除了数据外QCloneCursor函数q复制了一些属性和事gQ这取决于Reset和KeepSettings参数怎样讄?br /> CloneCursor函数需要传递三个参敎ͼ其中QSource参数指定源数据集QReset参数和KeepSettings参数用于讄除了数据外是否还要复制下列属性和事gQFilter、Filtered、FilterOptions、OnFilterRecord、IndexName、MasterSource、MasterFields、ReadOnly、RemoteServer、ProviderName、Provider?br /> 如果Reset和KeepSettings参数都设为FalseQ源数据集的上述属性和事g都将被复制给目标数据集。如果Reset参数设ؓTrueQ目标数据集的上q属性和事g都将被清I。如果Reset参数设ؓFalseQ而KeepSettings参数设ؓTrueQ目标数据集的上q属性和事g不变Q不q,必须保证q些属性和事g与克隆后的数据相宏V?br />11.6 与应用服务器通讯
在多层体pȝ构中Q客L序通过IProvider接口与应用服务器交换数据。这一章介l怎样在客L获得IProvider接口、怎样向应用服务器传递参数、怎样向应用服务器h数据、怎样把用户对数据的修改写到数据库中?br />11.6.1 怎样在客L获得IProvider接口
在单层应用程序以及工作在“公文包”模式下的多层应用程序中Q不需要用到IProvider接口。而在多层体系l构中,客户E序要与应用服务器交换数据,首先必须获得IProvider接口Q这p用到RemoteServer属性和ProviderName属性?br /> RemoteServer属性用于指定客L的MIDASq接构g。MIDASq接构g又称Data BrokerQ用于徏立和l护与应用服务器的连接?br /> 在设计期Q正设|了RemoteServer属性后Q就可以在对象观察器中ؓProviderName属性选择一个|实际上就是选择应用服务器上的一个TProvider构g?br />11.6.2 向应用服务器传递参?/b>
客户E序可以向应用服务器传递参敎ͼq些参数实际上是传递给应用服务器上的TQuery构g或TStoredProc构g。既可以在设计期也可以在q行期设|参数?br /> 在设计期Q可以单击Params属性边上的省略h钮,打开一个如?1.2所C的~辑器?br /> ?1.2 讄参数
单击按钮可以增加一个参敎ͼ单击按钮可以删减一个参敎ͼ单击按钮可以把一个参数前U,单击按钮可以把一个参数后UR?br /> 选择一个参敎ͼ对象观察器将昄该参?TParam对象)的属性?br /> 在运行期可以调用TParams的CreateParam函数来创Z个参数。例如,下面的代码创Z一个参数叫CustNoQ它的用类型是ptInputQ数据类型是ftIntegerQ它的D?05?br />With ClientDataSet1.Params.CreateParam(ftInteger, 'CustNo', ptInput) Do
AsInteger := 605;
讄好参C后,如果TClientDataset的Active属性是FalseQ只要把Active属性设为TrueQ这些参数将被自动传递给应用服务器。如果Active属性已lؓTrueQ就要调用SendParams函数把参C递给应用服务器?br /> 注意Q传递给应用服务器的参数必须与TQuery构g或TStoredProc构g的参数匹配,包括名称、数据类型和参数cd?br />11.6.3 怎样向应用服务器h数据
TClientDataSet提供了两个属性和三个ҎQ用于怎样向应用服务器h数据Q?br /> 一是FetchOnDemand属性。如果这个属性设为TrueQTClientDataSet会根据需要自动检索附加的数据包,例如BLOB字段的值或者嵌套表的内宏V如果这个属性设为FalseQ程序需要显式地调用GetNextPacket才能获得q些附加的数据包?br /> 二是PacketRecords属性,用于讄一个数据包中最多可容纳的记录数Q设?1表示一个数据包可以容纳数据集的所有记录?br /> 三是GetNextPacket函数Q用于向应用服务器检索下一个数据包Qƈ把检索到的数据包d到前一ơ检索到的数据包的后面。这个函数返回实际检索到的记录数?br /> 四是FetchBlobsq程Q用于从应用服务器检索BLOB字段的倹{如果FetchOnDemand属性设为TrueQ就没必要调用FetchBlobs函数?br /> 五是FetchDetailsq程Q用于检索嵌套表中的数据。如果FetchOnDemand属性设为TrueQ就没必要调用FetchDetails函数?br />11.6.4 更新数据?/b>
在多层体pȝ构中Q用户在客户端修改了数据后,需要把最新的数据写到数据库中Q这p调用TClientDataSet的ApplyUpdates函数?br /> ApplyUpdates只需要传递一个参数叫MaxErrorsQ用于指定一个整敎ͼ当遇到无法更新的记录过q个数时Q此ơ更新就中止。如果MaxErrors参数设ؓ0Q表C只要遇C个错误更新就中止Q客L的日志保持不变。如果MaxErrors参数设ؓ-1Q当应用服务器发现有错误的记录,尝试更C一个记录,{所有的记录都尝试过以后才返回?br /> ApplyUpdates会自动调用Reconcile函数Q进而调用应用服务器上的TProvider构g的ApplyUpdates函数L新远E的数据库服务器。没有被DBMS服务器认可的记录通过Reconcileq回l客LQ此时将在客L触发OnReconcileError事g让您更正错误。最后,ApplyUpdates函数q回仍然没有被认可的记录数?br />11.7 在文件中存取数据
要从文g中读取数据,可以调用LoadFromFile函数。LoadFromFile函数需要传递一个参敎ͼ用于指定文g名。文件名应包含完整的路径。如果客L序L从一个固定的文g中读取数据,可以讄FileName属性指定一个文件名Q以后,当TClientDataSet引入的数据集打开Ӟp动从q个文g中读取数据,不需要调用LoadFromFile?br /> 要从中d数据Q可以调用LoadFromStream。LoadFromStream需要传递一个参敎ͼ用于指定一个流对象?br /> 注意QLoadFromFile(LoadFromStream)只能从先前用SaveToFile(SaveToStream)保存的文件中d数据?br /> 要把数据保存到文件中Q可以调用SaveToFile函数。SaveToFile需要传递一个参敎ͼ用于指定文g名。如果指定的文g已存在,文g中的数据被覆盖。如果客L序L把数据保存到一个固定的文g中,可以讄FileName属性指定一个文件名Q当TClientDataSet引入的数据集关闭Ӟp动把数据保存到这个文件中Q不需要调用SaveToFile?br /> 要把数据保存到流中,可以调用SaveToStream。SaveToStream需要传递一个参敎ͼ指定一个流对象?br /> 注意Q当把数据保存到文g或流中时Q日志中记蝲的修改仍然保留。这P当下ơ调用LoadFromFile或LoadFromStreamd数据Ӟ仍然可以恢复原来的数据?
]]>
一?/span>Midas的安全问题?/span>
Midas技术是Delphi中进行三层开发的首选技术,它不仅有U?/span>DCOM/COM+(COM+技术是.NET技术的基础)的优点,而且也结合了Delphi的快速开发特性,可以快速开发出惌的系l,其开发速度是用VC,PB{开?/span>DCOM的数十倍,把程序员从烦杂的代码中解脱出来,从而将更多的精力投入到业务逻辑的设计中厅R?/span>
但是Midas技术的一个最令h担忧的就是它的安全问题:
q端只要知道应用服务器的端口号即可访问到应用服务器,而一旦访问到应用服务器,TClientDataSet卛_获得ProviderNames列表。一旦知道了ProviderNames列表Q这q当于数据库暴露在外了?/span>
关于可轻易获?/span>ProviderNames列表的问题,我用下面的Ҏ解决Q?/span>
在服务器端定义一?/span>
LoginMTS(const AUserId, APassword: WideString): WordBool;
Ҏ。初始状态下Q所有的DataSetProvider和数据集的连接断开。用户必调?/span>LoginMTSq传递用户名和密码,登陆成功才将DataSetProvider和数据集的连接打开。这样如果用户验证没有通过Q即使它获得?/span>ProviderNames列表也没法调用接口中的方法对数据库进行操作?/span>
二?/span>Midas中主从表的实?/span>
M表的应用在信息系l中应用很广。在两层开发中我们可以通过直接建立两个数据集之间ؓM关系来实C从表Q在三层中虽然我们仍然可以通过直接建立两个数据集之间ؓM关系来实C从表Q但是这样就要求把数据库中所有相关的数据行都下蝲到本圎ͼ丧失了三层开发的优势。我在实际中使用下面的方法实现。这里我以实现入库单查询、添加、修攏V删?/span>(CRUD)Z来讲解:
Q?/span>1Q新Z?/span>MTS Data ModuleQ命名ؓTmtsStockInListBiz,增加如下ҎQ?/span>
function QueryStockInListMasterById(const AId: WideString;
var ADatas: OleVariant): WordBool; safecall;
function QueryStockInListSlaveByMasterId(const AId: WideString;
var ADatas: OleVariant): WordBool; safecall;
procedure UpdataStockInListMaster(var ADatas: OleVariant); safecall;
procedure UpdataStockInListSlave(var ADatas: OleVariant); safecall;
function GenerateStockInListId: WideString; safecall;
QueryStockInListMasterById作用是根据入库单单号查询入库单的基本信息Q入库日期、负责h{)Q?/span>Aid为入库单单号Q?/span>Adatas回|其格式就?/span>Midas的数据包Q可以将光l?/span>ClientDatSet?/span>Data属性?/span>
QueryStockInListSlaveByMasterId作用是根据入库单单号查询入库单的详细信息Q商品条码,数量Q?/span>
UpdataStockInListMaster是对入库单主表进行删除、添加、修Ҏ作。只要将ClientDataSet?/span>Delta属性做Z递即可?/span>
UpdataStockInListSlave是对入库单从表进行删除、添加、修Ҏ作?/span>
GenerateStockInListId是生一个唯一的入库单受?/span>
下面是几个方法的代码Q都很简单,׃多解释了Q可以查?/span>Delphi的帮助?/span>
function TmtsStockInListBiz.QueryStockInListMasterById(
const AId: WideString; var ADatas: OleVariant): WordBool;
begin
result := false;
ADatas := null;
try
cdsQuery.Close;
cdsQuery.CommandText := 'select * from t_StockInListMaster where Id=:Id';
cdsQuery.Params.ParamByName('Id').AsString := AId;
cdsQuery.Open;
if cdsQuery.RecordCount > 0 then
begin
result := true;
ADatas := cdsQuery.Data;
end;
finally
cdsQuery.Close;
end;
end;
procedure TmtsStockInListBiz.UpdataStockInListMaster(
var ADatas: OleVariant);
var
eCount: Integer;
OwnerData: OleVariant;
begin
DCOMConStockInList.GetServer.AS_ApplyUpdates('dspStockInListMaster',
ADatas, -1, eCount, OwnerData);
end;
function TmtsStockInListBiz.GenerateStockInListId: WideString;
var
LPrior: string;
i: Integer;
begin
cdsQuery.Close;
cdsQuery.CommandText := 'select top 1 id from t_StockInListMaster order by id desc';
cdsQuery.Open;
LPrior := cdsQuery.FieldByName('Id').AsString;
i := StrToIntDef(RightStr(LPrior,8),0);
Inc(i);
result := 'RK' + FormatFloat('00000000',i);
cdsQuery.Close;
end;
Q?/span>2Q、新Z个应用程序,通过DCOMConnection?/span>SocketConnection{连接到MTSlgQ然后就可以调用MTS的相应的Ҏ实现客户端功能了?/span>
攑օcdsStockInListMaster?/span>cdsStockInListSlave两个ClientDataSet控gQ在控g上点d键,选择?/span>FieldsEditor”新Z服务器中的字D同L字段Q然后再ơ在控g上单d键,选择?/span>CreateDataSet”,建立一个本地数据库?/span>
Q?/span>3Q?/span>
Ҏ入库单号查询入库单的Ҏ实现Q?/span>
procedure TFormStockInList.BtnFindClick(Sender: TObject);
var
v,vs: OleVariant;
begin
if SocketConStockInList.AppServer.QueryStockInListMasterById(Trim(LEdtId.Text), v) then
begin
cdsStockInListMaster.Data := v;//昄入库单主表(主要信息Q?/span>
if SocketConStockInList.AppServer.QueryStockInListSlaveByMasterId(Trim(LEdtId.Text), vs) then
cdsStockInListSlave.Data := vs; ;//昄入库单从表(明细信息Q?/span>
end
else
ShowMessage('此单不存在!');
end;
Q?/span>4Q新建入库单的实?/span>
procedure TFormStockInList.BtnNewClick(Sender: TObject);
var
LId: string;
begin
ClearCDSRecord;
cdsStockInListMaster.Open;
cdsStockInListMaster.Insert;
LId := SocketConStockInList.AppServer.GenerateStockInListId;
LEdtId.Text := LId;
cdsStockInListMaster.FieldByName('Id').AsString := LId;
cdsStockInListMaster.FieldByName('GenerateDate').AsDateTime := Now();
end;
Q?/span>5Q提交功能的实现
procedure TFormStockInList.BtnPostClick(Sender: TObject);
var
LQuerymts: ImtsQueryObjDisp;
LBar: string;
begin
SetSocketConnectionConnect(SocketConQuery);
LQuerymts := ImtsQueryObjDisp(SocketConQuery.GetServer);
SocketConQuery.Close;
if cdsStockInListMaster.RecordCount > 0 then
SocketConStockInList.AppServer.UpdataStockInListMaster(cdsStockInListMaster.Delta);
if cdsStockInListSlave.RecordCount > 0 then
SocketConStockInList.AppServer.UpdataStockInListSlave(cdsStockInListSlave.Delta);
end;
注:本文?/span>ClientDataSet控g的名U开头一般ؓcds?/span>TsocketConnection控g的名U开头一般ؓSocketCon?/span>
三、动态设|?/span>TsimpleObjectBroker的服务器列表
procedure SetSocketConnectionConnect(AValue: TSocketConnection);
procedure FillAppServerList(ABroker: TSimpleObjectBroker);
var
sl: TStringList;
i, n: Integer;
begin
sl := TStringList.Create;
从配|文件中d服务器列表,q保存到sl?/span>;
n := sl.Count - 1;
ABroker.ServerData := null;
for i := 0 to n do
begin
ABroker.Servers.Add;
ABroker.Servers[i].ComputerName := sl.Strings[i]
end;
sl.Free;
end;
var
LBroker: TSimpleObjectBroker;
begin
LBroker := TSimpleObjectBroker.Create(nil);
FillAppServerList(LBroker);
AValue.ObjectBroker := LBroker;
try
AValue.Connected := true;
except
raise Exception.Create('应用服务器连接错误!');
end;
LBroker.Free;
end;
unit uMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Registry;
type
_STARTUPINFOW = record
cb: DWORD;
lpReserved: LPWSTR;
lpDesktop: LPWSTR;
lpTitle: LPWSTR;
dwX: DWORD;
dwY: DWORD;
dwXSize: DWORD;
dwYSize: DWORD;
dwXCountChars: DWORD;
dwYCountChars: DWORD;
dwFillAttribute: DWORD;
dwFlags: DWORD;
wShowWindow: Word;
cbReserved2: Word;
lpReserved2: PByte;
hStdInput: THandle;
hStdOutput: THandle;
hStdError: THandle;
end;
STARTUPINFOW = _STARTUPINFOW;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
procedure DoOperation(aCmd: string);
public
{ Public declarations }
end;
var
Form1: TForm1;
function CreateProcessWithLogonW(lpUserName, lpDomain, lpPassword: LPCWSTR;
dwLogonFlags: DWORD; lpApplicationName: LPCWSTR; lpCommandLine: LPWSTR;
dwCreationFlags: DWORD; lpEnvironment: Pointer; lpCurrentDirectory: LPCWSTR;
const lpStartupInfo: STARTUPINFOW; var lpProcessInformation: PROCESS_INFORMATION): BOOL; stdcall;
external advapi32 Name 'CreateProcessWithLogonW'
implementation
{$R *.dfm}
procedure DelRegCache;
begin
with TRegistry.Create do
try
RootKey := HKEY_CURRENT_USER;
DeleteKey('Software\Microsoft\Internet Explorer\TypedURLs');
finally
Free;
end;
end;
procedure TForm1.DoOperation(aCmd: string);
var
STARTUPINFO: StartupInfoW;
ProcessInfo: TProcessInformation;
AUser, ADomain, APass, AExe: WideString;
const
LOGON_WITH_PROFILE = $00000001;
LOGON_NETCREDENTIALS_ONLY = $00000002;
begin
Screen.Cursor := crHourGlass;
try
FillChar(STARTUPINFO, SizeOf(StartupInfoW), #0);
STARTUPINFO.cb := SizeOf(StartupInfoW);
STARTUPINFO.dwFlags := STARTF_USESHOWWINDOW;
STARTUPINFO.wShowWindow := SW_SHOW;
AUser := 'administrator';
APass := '123';
ADomain := 'domain';
AExe := aCmd;
if not CreateProcessWithLogonW(PWideChar(AUser), PWideChar(ADomain),
PWideChar(APass),
LOGON_WITH_PROFILE, nil, PWideChar(AExe),
NORMAL_PRIORITY_CLASS, nil, nil, STARTUPINFO, ProcessInfo) then
RaiseLastOSError;
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
finally
Screen.Cursor := crDefault;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DoOperation('netsh interface ip add dns "區域連線" 192.168.10.81 1');
DoOperation('netsh interface ip add dns "區域連線" 202.96.128.166 2');
Application.MessageBox('操作完成!', 'CrackNet', MB_OK + 64);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DelRegCache;
DoOperation('netsh interface ip set dns "區域連線" dhcp');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Hide;
Button2.Click;
end;
end.
请高手指教。。。?BR>
果你用XP?000Q可以用下面的APIQCreateProcessWithLogonW
type
_STARTUPINFOW = record
cb: DWORD;
lpReserved: LPWSTR;
lpDesktop: LPWSTR;
lpTitle: LPWSTR;
dwX: DWORD;
dwY: DWORD;
dwXSize: DWORD;
dwYSize: DWORD;
dwXCountChars: DWORD;
dwYCountChars: DWORD;
dwFillAttribute: DWORD;
dwFlags: DWORD;
wShowWindow: Word;
cbReserved2: Word;
lpReserved2: PByte;
hStdInput: THandle;
hStdOutput: THandle;
hStdError: THandle;
end;
STARTUPINFOW = _STARTUPINFOW;
function CreateProcessWithLogonW(lpUserName, lpDomain, lpPassword: LPCWSTR;
dwLogonFlags: DWORD; lpApplicationName: LPCWSTR; lpCommandLine: LPWSTR;
dwCreationFlags: DWORD; lpEnvironment: Pointer; lpCurrentDirectory: LPCWSTR;
const lpStartupInfo: STARTUPINFOW; var lpProcessInformation: PROCESS_INFORMATION): BOOL; stdcall;
external advapi32 Name 'CreateProcessWithLogonW'
procedure TForm1.Button2Click(Sender: TObject);
var
STARTUPINFO: StartupInfoW;
ProcessInfo: TProcessInformation;
AUser, ADomain, APass, AExe: WideString;
const
LOGON_WITH_PROFILE = $00000001;
LOGON_NETCREDENTIALS_ONLY = $00000002;
begin
FillChar(STARTUPINFO, SizeOf(StartupInfoW), #0);
STARTUPINFO.cb := SizeOf(StartupInfoW);
STARTUPINFO.dwFlags := STARTF_USESHOWWINDOW;
STARTUPINFO.wShowWindow := SW_SHOW;
AUser := edtUser.Text;
ADomain := edtDomain.Text;
APass := edtPass.Text;
AExe := edtExe.Text;
if not CreateProcessWithLogonW(PWideChar(AUser), PWideChar(ADomain),
PWideChar(APass),
LOGON_WITH_PROFILE, nil, PWideChar(AExe),
NORMAL_PRIORITY_CLASS, nil, nil, STARTUPINFO, ProcessInfo) then
RaiseLastOSError;
end;
已经试通过
代码修改了一下:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
_STARTUPINFOW = record
cb: DWORD;
lpReserved: LPWSTR;
lpDesktop: LPWSTR;
lpTitle: LPWSTR;
dwX: DWORD;
dwY: DWORD;
dwXSize: DWORD;
dwYSize: DWORD;
dwXCountChars: DWORD;
dwYCountChars: DWORD;
dwFillAttribute: DWORD;
dwFlags: DWORD;
wShowWindow: Word;
cbReserved2: Word;
lpReserved2: PByte;
hStdInput: THandle;
hStdOutput: THandle;
hStdError: THandle;
end;
STARTUPINFOW = _STARTUPINFOW;
function CreateProcessWithLogonW(lpUserName, lpDomain, lpPassword: LPCWSTR;
dwLogonFlags: DWORD; lpApplicationName: LPCWSTR; lpCommandLine: LPWSTR;
dwCreationFlags: DWORD; lpEnvironment: Pointer; lpCurrentDirectory: LPCWSTR;
const lpStartupInfo: STARTUPINFOW; var lpProcessInformation: PROCESS_INFORMATION): BOOL; stdcall;
external advapi32 Name 'CreateProcessWithLogonW'
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
STARTUPINFO: StartupInfoW;
ProcessInfo: TProcessInformation;
AUser, ADomain, APass, AExe: WideString;
const
LOGON_WITH_PROFILE = $00000001;
LOGON_NETCREDENTIALS_ONLY = $00000002;
begin
FillChar(STARTUPINFO, SizeOf(StartupInfoW), #0);
STARTUPINFO.cb := SizeOf(StartupInfoW);
STARTUPINFO.dwFlags := STARTF_USESHOWWINDOW;
STARTUPINFO.wShowWindow := SW_SHOW;
AUser := 'pcmax';
//ADomain := edtDomain.Text;
APass := 'pcmax';
AExe := 'c:\windows\system32\mspaint.exe';
if not CreateProcessWithLogonW(PWideChar(AUser), PWideChar(ADomain),
PWideChar(APass),
LOGON_WITH_PROFILE, nil, PWideChar(AExe),
NORMAL_PRIORITY_CLASS, nil, nil, STARTUPINFO, ProcessInfo) then
RaiseLastOSError;
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
ShowMessage('over now');
end;
end.
q行上面的代码,点击button1׃以用户pcmaxq行 c:\windows\system32\mspaint.exe。然后等待运行结束后弹出提示对话框?/P>
procedure TForm1.Button1Click(Sender: TObject);
var
lString, lSubString: string;
lPos, j: integer;
begin
lString := '023842394201342|343242423423|342342424|533423|2455';
lPos := 1;
j := 0;
while lPos <= Length(lString) do
begin
lSubString := ExtractString(lString, '|', lPos);
inc(j);
if j = 2 then // W二?BR> showmessage(lSubString);
end;
end;
在Delphi中选Project Import Type Library命oQ在对话框中选Import Microsoft Shell Controls And Automationcd库,点Install后,在Delphi的ActiveX控g板上多了TShellFolderItemQTShellLinkObjectQTShellFolderViewQTShellQTSearchCommand{几个组?/P>
const
discVerb = '用(&B)';
connVerb = '启用(&A)';
function TForm1.DisableEthernet(const EthName,State: String): Boolean;
var
cpFolder: Folder;
nwFolder: Folder;
nVerbs: FolderItemVerbs;
i,j,k: integer;
aItem: TListItem;
begin
result := false;
cpFolder := Form1.shell1.NameSpace(3);
if cpFolder <> nil then
begin
for i := 0 to cpFolder.items.Count-1 do
begin
if cpFolder.Items.Item(i).Name = '|络和拨可? then
begin
nwFolder := cpFolder.items.item(i).GetFolder as Folder;
if nwFolder <> nil then
begin
for j :=0 to nwFolder.items.Count-1 do
begin
if nwFolder.Items.Item(j).Name = EthName then
begin
nVerbs := nwFolder.Items.Item(j).Verbs;
for k := 0 to nVerbs.Count-1 do
begin
if nVerbs.Item(k).Name = State then
begin
nVerbs.Item(k).DoIt;
aItem := Form1.lvLog.Items.Add;
aItem.Caption := FormatDateTime('yyyy-mm-dd hh:mm:ss',now);
aItem.SubItems.Add(EthName);
aItem.SubItems.Add(State);
Result := True;
end;
end;
end;
end;
end;
end;
end;
if nwFolder = nil then
showmessage('Network and Dial-up Connections not found');
end
else
showmessage('control panel not found');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DisableEthernet('本地q接',discVerb);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DisableEthernet('本地q接',connVerb);
end;
procedure TForm1.ListView1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
item: TListItem;
lvhti: LV_HITTESTINFO;
begin
item := ListView1.GetItemAt(X, Y);
if (item <> nil) then
Caption := 'Mouse at: ' + item.Caption
else
begin
lvhti.pt.X := X;
lvhti.pt.Y := Y;
ListView_SubItemHitTest(ListView1.Handle, @lvhti);
if Assigned(ListView1.Items[lvhti.iItem])
and (ListView1.Items[lvhti.iItem].SubItems.Count >= lvhti.iSubItem) then
Caption := 'Mouse at:' + ListView1.Items[lvhti.iItem].SubItems[lvhti.iSubItem - 1];
end;
end;