)); If Fields[I].DataType in [ftADT, ftArray] then WriteFullNames(TObjectField(Fields[I]).Fields); End; End;
Procedure WriteLists(DataSet: TDataSet); var I: Integer; Begin FieldList.Clear; For I := 0 to DataSet.FieldList.Count - 1 Do With DataSet.FieldList Do FieldList.Lines.Add(Format(''''%d) %s'''', [Fields[I].FieldNo, Strings[I]])); FieldDefList.Clear; DataSet.FieldDefs.Updated := False; DataSet.FieldDefList.Update; For I := 0 to DataSet.FieldDefList.Count - 1 Do With DataSet.FieldDefList Do FieldDefList.Lines.Add(Format(''''%d) %s'''', [FieldDefs[I].FieldNo, Strings[I]])); End; var DataSet: TDataSet; Begin DataSet := DBNavigator1.DataSource.DataSet; If Assigned(DataSet) and DataSet.Active then Begin WriteLists(DataSet) End Else Begin CheckDatabase(False); MasterTable.TableName := MasterTableName.Text; WriteLists(MasterTable); End; End; 首先要说明的是,FieldsPageShow中嵌套了WriteFullNames,其实WriteFullNames完全是多余的。FieldsPageShow先获取当前的数据集。如果当前的数据集已打开的话,就调用WriteLists显示字段对象和字段定义对象的列表。如果当前数据集没有打开,就显示MasterTable中的字段对象和字段定义对象的列表。当用户翻到“Indexes”页,将触发IndexPage(TTabSheet对象)的OnShow事件,此时就把当前数据集中的索引列出来,用户也可以创建新的索引或者删除一个索引。“Indexes”页如图14.10所示。 图14.10 “Indexes”页 Procedure TDBClientTest.IndexPageShow(Sender: TObject); Begin If not Assigned(ActiveDataSet) or not ActiveDataSet.Active then OpenTable.Execute; RefreshIndexNames(0); End; IndexPageShow首先检查当前是否打开了一个数据集,如果没有,就执行OpenTable的代码即打开数据集,然后调用RefreshIndexNames函数列出所有的索引名称。 Procedure TDBClientTest.RefreshIndexNames(NewItemIndex: Integer); var I: Integer; IndexDefs: TIndexDefs; Begin IndexList.Clear; If ActiveDataSet = MasterTable then IndexDefs := MasterTable.IndexDefs Else IndexDefs := ClientData.IndexDefs; IndexDefs.Update; For I := 0 to IndexDefs.Count - 1 Do If IndexDefs[I].Name = '''''''' then IndexList.Items.Add('''''''') Else IndexList.Items.Add(IndexDefs[I].Name); If IndexList.Items.Count > 0 then Begin If NewItemIndex < IndexList.Items.Count then IndexList.ItemIndex := NewItemIndex ElseIndexList.ItemIndex := 0; ShowIndexParams; End; End; RefreshIndexNames又调用ShowIndexParams检索索引的选项,用这些选项来初始化“Indexes”页上的几个编辑框和复选框。 Procedure TDBClientTest.ShowIndexParams;varIndexDef: TIndexDef; Begin If ActiveDataSet = MasterTable then IndexDef := MasterTable.IndexDefs[IndexList.ItemIndex] Else IndexDef := ClientData.IndexDefs[IndexList.ItemIndex]; idxCaseInsensitive.Checked := ixCaseInsensitive in IndexDef.Options;idxDescending.Checked := ixDescending in IndexDef.Options;idxUnique.Checked := ixUnique in IndexDef.Options;idxPrimary.Checked := ixPrimary in IndexDef.Options;IndexFields.Text := IndexDef.Fields; DescFields.Text := IndexDef.DescFields; CaseInsFields.Text := IndexDef.CaseInsFields; End; 如果用户在列表框中选择了另一个索引,就应当相应地刷新这些选项。Procedure TDBClientTest.IndexListClick(Sender: TObject); Begin If ActiveDataSet = MasterTable then MasterTable.IndexName := MasterTable.IndexDefs[IndexList.ItemIndex].Name Else ClientData.IndexName := ClientData.IndexDefs[IndexList.ItemIndex].Name; ShowIndexParams; End; 如果要创建一个新的索引,用户必须事先设置索引的选项,然后单击“CreateIndex”按钮。 Procedure TDBClientTest.CreateIndexClick(Sender: TObject); var IndexName: string;Options: TIndexOptions; Begin IndexName := Format(''''Index%d'''', [IndexList.Items.Count+1]); If InputQuery(''''Create Index'''', ''''Enter IndexName:'''', IndexName) then Begin Options := []; If idxCaseInsensitive.Checked then Include(Options, ixCaseInsensitive); If idxDescending.Checked then Include(Options, ixDescending); If idxUnique.Checked then Include(Options, ixUnique); If idxPrimary.Checked then Include(Options, ixPrimary); If ActiveDataSet = MasterTable then Begin MasterTable.Close; MasterTable.AddIndex(IndexName,IndexFields.Text,Options,DescFields.Text); MasterTable.Open; End Else ClientData.AddIndex(IndexName, IndexFields.Text, Options,DescFields.Text, CaseInsFields.Text); StatusMsg := ''''Index Created''''; RefreshIndexNames(IndexList.Items.Count); End; End; CreateIndexClick首先弹出一个输入框,让用户输入索引名称,然后根据用户设置的选项来设置索引的Options属性。 在调用AddIndex之前,首先要区分当前的数据集是MasterTable还是ClientData,为什么要区分MasterTable和ClientData呢?因为对于一般的数据集构件来说,在创建索引之前必须先关闭数据集,而对于TClientDataSet构件来说,则不必先关闭数据集。 用户也可以先选择一个索引,然后单击“Delete Index”按钮删除这个索引。 Procedure TDBClientTest.DeleteIndexClick(Sender: TObject); Begin If IndexList.ItemIndex > -1 then If ActiveDataSet = MasterTable then Begin MasterTable.Close; MasterTable.DeleteIndex(MasterTable.IndexDefs[IndexList.ItemIndex].Name); MasterTable.Open; End Else ClientData.DeleteIndex(ClientData.IndexDefs[IndexList.ItemIndex].Name); End; 与调用AddIndex一样,在调用DeleteIndex之前,首先要区分当前的数据集是MasterTable还是ClientData。当用户翻到“Filters”页,就可以设置过滤条件,如图14.11所示。 图14.11 “Filters”页 当“Filters”页刚刚打开的时候,将触发OnShow事件,这样就可以初始化“Filter”框。这里运用了一个编程技巧,先从下面的栅格中取出一个字段,然后判断这个字段的数据类型是不是ftString、ftMemo或ftFixedChar中的一种,如果是的话,过滤条件表达式的运算符后面的值要用引号括起来。 Procedure TDBClientTest.FilterPageShow(Sender: TObject); var Field: TField;LocValue,QuoteChar: String; Begin If (Filter.Text = '''''''') and Assigned(ActiveDataSet) and ActiveDataSet.Active then Begin Field := MasterGrid.SelectedField;If Field = nil then Exit; With ActiveDataSet DoTryDisableControls; MoveBy(3); LocValue := Field.Value; First; Finally EnableControls; End; If Field.DataType in [ftString, ftMemo, ftFixedChar] then QuoteChar := '''''''''''''''' Else QuoteChar := ''''''''; Filter.Text := Format(''''%s=%s%s%1:s'''', [Field.FullName, QuoteChar, LocValue]); End; End; 用户可以在“Filter”框内键入新的过滤条件,当用户按下Enter键或把输入焦点移走,就会把用户输入的过滤条件表达式赋给当前数据集的Filter属性。当用户翻到“FindKey”页,就可以输入一个键值,然后在数据集中搜索特定的记录,如图14.12所示。 图14.12 “FindKey”页 当用户单击“Find Key”或“Find Nearest”按钮,就开始搜索特定的记录。 Procedure TDBClientTest.FindKeyClick(Sender: TObject); Begin If ActiveDataSet = ClientData then With ClientData Do Begin SetKey;IndexFields[0].AsString := FindValue.Text; KeyExclusive := Self.KeyExclusive.Checked;If FindPartial.Checked then KeyFieldCount := 0; If Sender = Self.FindNearest then GotoNearest else If not GotoKey then StatusMsg := ''''Not found''''; End Else if ActiveDataSet = MasterTable then With MasterTable Do Begin SetKey; IndexFields[0].AsString := FindValue.Text; KeyExclusive := Self.KeyExclusive.Checked; If FindPartial.Checked then KeyFieldCount := 0; If Sender = Self.FindNearest then GotoNearest Else if GotoKey thenStatusMsg := ''''Record Found'''' Else StatusMsg := ''''Not found''''; End; End; 首先,要区分当前数据集是ClientData还是MasterTable,调用SetKey使数据集进入dsSetKey状态,把用户输入的键值赋给索引中的第一个字段。然后根据Sender参数判断用户按下的是“Find Key”按钮还是“Find Nearest”按钮,如果是后者,就调用GotoNearest,如果是前者,就调用GotoKey,最后根据GotoKey的返回值显示有关信息。 当用户翻到“Locate”页,将触发LocatePage(TTabSheet对象)的OnShow事件,程序就把下面的栅格中选择的字段作为关键字段。“Locate”页如图14.13所示。 图14.13 “Locate”页 Procedure TDBClientTest.LocatePageShow(Sender: TObject); var Field: TField; Begin If (ActiveDataSet <> nil) and ActiveDataSet.Active then BeginField := MasterGrid.SelectedField; If LocateField.Items.Count = 0 then LocateFieldDropDown(LocateField); If (LocateField.Text = '''''''')or(LocateField.Items.IndexOf(Field.FieldName) < 1) then LocateField.Text := Field.FieldName; With ActiveDataSet Do Try DisableControls; MoveBy(3); LocateEdit.Text := Field.Value; First; Finally EnableControls; End; End; End; 用户也可以在“Field”框选择一个关键字段。当用户下拉“Field”框时,触发OnDropDown事件,这样就可以把当前数据集中的字段显示到“Field”框中。 Procedure TDBClientTest.LocateFieldDropDown(Sender: TObject); Begin ActiveDataSet.GetFieldNames(LocateField.Items); End; 当用户选择了关键字段并且输入了键值,就可以单击“Locate”按钮开始定位记录。 Procedure TDBClientTest.LocateButtonClick(Sender: TObject);varOptions: TLocateOptions;LocateValue: Variant; Begin Options := []; If locCaseInsensitive.Checked then Include(Options, loCaseInsensitive); If locPartialKey.Checked then Include(Options, loPartialKey); If LocateNull.Checked then LocateValue := Null Else LocateValue := LocateEdit.Text; If ActiveDataSet.Locate(LocateField.Text, LocateValue, Options) then StatusMsg := ''''Record Found'''' Else StatusMsg := ''''Not found''''; End; 前面几行代码主要是设置有关选项,其中,如果用户选中“Null Value”复选框的话,就把键值设为Null。然后调用当前数据集的Locate函数定位记录,并根据Locate函数的返回值显示相应的信息。 14.6 一个登录的示范程序 这一节剖析一个登录示范程序,它可以在C:\Program Files\Borland\Delphi4\Demos\Midas\Login目录中找到。 这个程序分为应用服务器和客户程序两个部分。应用服务器的主窗体上有一个列表框,用于记载曾经登录到应用服务器上的用户名,如图14.16所示。 应用服务器上的数据模块如图14.17所示。 数据模块上只有一个TTable构件,它的DatabaseName属性设为DBDEMOS,TableName属性设为COUNTRY。数据模块上没有TProvider构件,由TTable构件提供IProvider接口。 这个数据模块的实例方式设为ciMultiInstance,这意味着每当一个客户连接应用服务器时,就会创建数据模块的一个新的实例,当客户不再连接应用服务器时,就删除数据模块的实例。因此,这个程序利用数据模块的OnCreate事件做了一些初始化的工作,利用数据模块的OnDestroy事件从列表框中删除一个用户名。 Procedure TLoginDemo.LoginDemoCreate(Sender: TObject); Begin FLoggedIn := False; End; 为什么要把FLoggedIn变量设为False呢?其原因后面将解释。 Procedure TLoginDemo.LoginDemoDestroy(Sender: TObject); Begin With Form1.ListBox1.Items do Delete(IndexOf(FUserName)); End; 编译和运行这个应用服务器。打开客户程序的项目,它的主窗体如图14.18所示。 窗体上的TDCOMConnection构件用于连接应用服务器,它的ServerName属性设为Server.LoginDemo,它的LoginPrompt属性设为True。窗体上的TClientDataSet构件的RemoteServer属性指定了TDCOMConn