I]; If Result.Caption = PageName then Exit; End; Result := ProviderPage; End;
Procedure ProcessComponents(Components: array of TComponent); varI: Integer; Begin If Write then Begin For I := Low(Components) to High(Components) Do If Components[I] is TCustomEdit then With TEdit(Components[I]) do WriteStr(Name, Text) Else if Components[I] is TComboBox then With TDBComboBox(Components[I]) do WriteStr(Name, Text) Else if Components[I] is TCheckBox then With TCheckBox(Components[I]) do WriteBool(Name, Checked) Else if Components[I] is TAction then With TAction(Components[I]) do WriteBool(Name, Checked) Else if Components[I] is TPageControl then With TPageControl(Components[I]) doWriteStr(Name,ActivePage.Caption); End; Else Begin For I := Low(Components) to High(Components) do If Components[I] is TCustomEdit then With TEdit(Components[I]) do Text := ReadStr(Name) Else if Components[I] is TComboBox then With TComboBox(Components[I]) do Text := ReadStr(Name) Else if Components[I] is TCheckBox then With TCheckBox(Components[I]) do Checked := ReadBool(Name) Else if Components[I] is TAction then With TAction(Components[I]) do Checked := ReadBool(Name) Else if Components[I] is TPageControl then With TPageControl(Components[I]) doActivePage := FindPage(ReadStr(Name)); End; End; Begin GetConfigFile; If not Write and (ReadStr(''''AreaSelector'''') = '''''''') then Exit;
ProcessComponents([AreaSelector, DatabaseName, MasterTableName,DetailTableName, MasterSQL, DetailSQL, poCascadedDeletes, poCascadedUpdates,poDelayedDetails, poDelayedBlobs, poIncludeFieldProps, poReadOnly,DisableProvider, ObjectView, SparseArrays, MixedData, FetchOnDemand,DisableProvider, ResolveToDataSet, DataRows, CreateDataSetDesc,EnableBCD, RequestLiveQuery, ViewEvents, DisplayDetails, IncludeNestedObject]); End; StreamSettings用Write参数来区分现在是要读还是写。StreamSettings中又嵌套了几个过程和函数,其中,WriteStr、WriteBool、ReadStr、ReadBool分别用于在配置文件中存取字符串和布尔类型的信息,FindPage函数搜索并返回一个特定的对象,而ProcessComponents则用于存取与具体构件有关的信息。 GetConfigFile函数用于创建一个TIniFile对象的实例(如果还没有创建的话)。 Function TDBClientTest.GetConfigfile: TIniFile; Begin If FConfig = nil Then FConfig := TIniFile.Create(ChangeFileExt(ParamStr(0), ''''.INI'''')); Result := FConfig; End; 请读者注意StreamSettings是怎样调用ProcessComponents函数的。ProcessComponents需要传递一个数组,数组中的元素就是窗体上的一些控件的名称。 我们先翻到“Provider”页,看看怎样指定数据库和建立Master/Detail关系,如图14.7所示。 图14.7 “Provider”页 “Database”框用于指定要访问的数据库。当用户下拉此框时,将触发OnDropDown事件。如果此时“Database”框还是空的话,就调用TSession的GetDatabaseNames函数把所有已定义的BDE别名和专用的别名填到“Database”框中。 Procedure TDBClientTest.DatabaseNameDropDown(Sender: TObject); Begin If DatabaseName.Items.Count = 0 then Session.GetDatabaseNames(DatabaseName.Items); End; 当用户在“Database”框中选择一个别名,将触发OnClick事件。此时,就调用CheckDatabase连接另一个数据库。由于数据库已改变,“Master/DetailTables”框内的内容应当清掉。 Procedure TDBClientTest.DatabaseNameClick(Sender: TObject); Begin If (DatabaseName.Text <> '''''''') and not DatabaseName.DroppedDown then Begin CheckDatabase(True); MasterTableName.Items.Clear; MasterTableName.Text := ''''''''; DetailTableName.Text := ''''''''; ClientData.Close; End; End; 用户也可以直接在“Database”框键入一个数据库别名,然后按Enter键,此时将触发OnKeyPress事件。 Procedure TDBClientTest.DatabaseNameKeyPress(Sender: TObject; var Key: Char); Begin If Key = #13 then Begin If DatabaseName.DroppedDown then DatabaseName.DroppedDown := False; DatabaseNameClick(Sender);Key := #0; End; End; 好,现在让我们看看CheckDatabase是怎样定义的: Procedure TDBClientTest.CheckDatabase(CloseFirst: Boolean); var SPassword, SUserName: string; Begin If not CloseFirst and Database1.Connected and(Database1.AliasName = DatabaseName.Text) then Exit; Database1.Close; Database1.AliasName := DatabaseName.Text; Session.GetAliasParams(Database1.AliasName, Database1.Params); If Database1.Params.IndexOfName(''''PATH'''') = -1 then Begin SPassword := ConfigFile.ReadString(''''Passwords'''', Database1.AliasName, ''''''''); If SPassword = '''''''' then Begin SUserName := Database1.Params.Values[''''USER NAME'''']; If not LoginDialog(''''DatabaseName.Text'''', SUserName, SPassword) then Exit; Database1.Params.Values[''''USER NAME''''] := SUserName; End; Database1.Params.Values[''''PASSWORD''''] := SPassword; End; If EnableBCD.Checked then Database1.Params.Add(''''ENABLE BCD=TRUE''''); Database1.Open; If Database1.IsSQLBased and (SPassword <> '''''''') thenConfigFile.WriteString(''''Passwords'''', Database1.AliasName, SPassword); End; CheckDatabase用于连接一个用户指定的数据库。如果当前连接的就是用户指定的数据库,CheckDatabase就什么也不干。如果不是的话,首先要调用TDatabase构件的Close断开与数据库的连接,然后把TDatabase构件的AliasName 属性设为用户选择的别名,并调用BDE会话期对象的GetAliasParams取出这个别名的参数。 注意,对于本地数据库来说,只有一个PATH参数,而对于SQL数据库来说,参数就有好几个,因此,可以用有没有PATH参数来区分本地数据库和SQL数据库。如果是SQL数据库的话,就要设置USER NAME和PASSWORD参数给出用户名和口令。如果“Settings”菜单上的“EnableBCD”命令被选中的话,就增加一个ENABLE BCD参数,并把它的值设为TRUE。然后调用Open重新连接数据库。 这个程序还能够让客户选择“Master/Detail”关系中的Master表和Detail表,这是在“Master/Detail Tables”框中选择的,其中,上面一个组合框用于选择Master表,下面一个组合框用于选择Detail表。当用户在组合框中选择一个表,将触发OnClick事件。 Procedure TDBClientTest.MasterTableNameClick(Sender: TObject); Begin With Sender as TComboBox Do If not DroppedDown and (MasterTable.TableName <> Text) then OpenTable.Execute; End; 当用户下拉“Master/Detail Tables”框中的一个组合框,将触发OnDropDown事件。此时就调用BDE会话期对象的GetTableNames把当前数据库中的所有表格的名称填到组合框中,供用户选择。 Procedure TDBClientTest.MasterTableNameDropDown(Sender: TObject); Begin CheckDatabase(False); With Sender as TComboBox do If (Items.Count < 1) and (Database1.AliasName <> '''''''') then Session.GetTableNames(Database1.DatabaseName, '''''''', True, False, Items); End; 用户也可以直接在“Master/Detail Tables”框中的一个组合框内键入一个表格的名称,然后按Enter键,此时将触发OnKeyPress事件。 Procedure TDBClientTest.MasterTableNameKeyPress(Sender: TObject; var Key: Char); Begin If Key = #13 then Begin With Sender as TComboBox Do If DroppedDown then DroppedDown := False; OpenTable.Execute; Key := #0; End; End; 注意:上面都是以选择Master表的组合框为例的,实际上,选择Detail表的操作完全一样,代码如下。 Procedure TDBClientTest.DetailTableNameClick(Sender: TObject); Begin With Sender as TComboBox Do If not DroppedDown and (DetailTable.TableName <> Text) then OpenTable.Execute; End; 在上面几个事件句柄中,OpenTable是一个动作列表,这是Delphi 4新增加的功能。在窗体上双击TActionList构件,将打开一个如图14.8所示的编辑器。 图14.8 动作列表编辑器 在这个编辑器中找出OpenTable这个动作,然后在对象观察器中可以发现, 执行这个动作的代码是OpenTableExecute函数。 Procedure TDBClientTest.OpenTableExecute(Sender: TObject); Begin ClearEventLog.Execute; If MasterTableName.Text <> '''''''' then OpenDataSet(MasterTable); End; 而OpenDataSet是这样定义的: Procedure TDBClientTest.OpenDataSet(Source: TDBDataSet); Begin Screen.Cursor := crHourGlass; Try ClientData.Data := Null; Source.Close; If not DisableProvider.Checked then Begin BDEProvider.DataSet := Source; SetProviderOptions; ClientData.ProviderName := BDEProvider.Name; ActiveDataSet := ClientData; End Else ActiveDataSet := Source; MasterGrid.SetFocus; StatusMsg := ''''Dataset Opened''''; FinallyScreen.Cursor := crDefault; End; StreamSettings(True); End; OpenDataSet通过一个叫DisableProvider的复选框来决定是否使用TProvider构件。如果没有选中“Disable Provider”这个复选框,表示使用TProvider构件,此时就把TProvider构件的DataSet属性设为MasterTable,然后调用SetProviderOptions来设置TProvider构件的选项,接着设置TClientDataSet构件的ProviderName属性指定这个TProvider构件,最后把ActiveDataSet变量设为此TClientDataSet构件。如果用户选中“Disable Provider”复选框,表示不使用TProvider构件,此时就直接把ActiveDataSet设为MasterTable。 SetProviderOptions是这样定义的: Procedure TDBClientTest.SetProviderOptions; var Opts: TProviderOptions; Begin Opts := [];If poDelayedDetails.Checked then Include(Opts, poFetchDetailsOnDemand); if poDelayedBlobs.Checked then Include(Opts, poFetchBlobsOnDemand); if poCascadedDeletes.Checked then Include(Opts, poCascadeDeletes); if poCascadedUpdates.Checked then Include(Opts, poCascadeUpdates); if poReadOnly.Checked then Include(Opts, Provider.poReadOnly); if poIncludeFieldProps.Checked then Include(Opts, poIncFieldProps); BDEProvider.Options := Opts; End; SetProviderOptions实际上是根据“Settings”菜单上的“Provider Options”命令的一些子命令是否被选中来设置TProvider构件的Options属性。这个程序还可以让用户在“Master/Detail Queries”框中输入SQL语句。当用户输入了SQL语句并且按下Enter键,将触发OnKeyPress事件。 Procedure TDBClientTest.MasterSQLKeyPress(Sender: TObject; var Key: Char); Begin If Key = #13 then Begin OpenQuery.Execute; Key := #0; End; End; 其中,OpenQuery也是一个动作,执行它的是OpenQueryExecute函数。OpenQueryExecute是这样定义的: Procedure TDBClientTest.OpenQueryExecute(Sender: TObject); Begin If UpperCase(Copy(MasterSQL.Text, 1, 6)) = ''''SELECT'''' then OpenDataSet(MasterQuery) Else Begin CheckDatabase(False); MasterQuery.RequestLive := True; MasterQuery.SQL.Text := MasterSQL.Text; MasterQuery.ExecSQL; StatusMsg := Format(''''%d rows were affected'''', [MasterQuery.RowsAffected]); End; Events.Items. Begin Update; Try Events.Clear; Finally Events.Items.EndUpdate; End; End; OpenQueryExecute首先判断用户输入的SQL语句是否为SELECT。如果是的话,就调用OpenDataSet执行SELECT语句。如果不是的话,就调用ExecSQL执行SQL语句。 当用户翻到“Fields”页,将触发FieldsPage(TTabSheet对象)的OnShow事件,此时就把数据集中的字段和字段定义对象名称分别显示在两个多行文本编辑器中,如图14.9所示。 图14.9 “Fields”页 Procedure TDBClientTest.FieldsPageShow(Sender: TObject); Procedure WriteFullNames(Fields: TFields); var I: Integer; Begin For I := 0 to Fields.Count - 1 Do With Fields[I] Do Begin FieldList.Lines.Add(Format(''''%d) %s'''', [FieldNo, FullName]