亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

多層數據庫開發十四:剖析幾個MIDAS示范程序

2019-11-18 18:10:47
字體:
來源:轉載
供稿:網友
  第十四章 剖析幾個MIDAS示范程序
  MIDAS是Multi-Tier Distributed application Services Suite的簡稱,為Delphi 4的一個關鍵技術。對于初學者來說,MIDAS具有相當的難度,因此,這一章詳細剖析幾個MIDAS示范程序,以幫助讀者理解和掌握MIDAS技術。
  與一般的數據庫應用程序不同的是,只有當應用服務器正在運行的情況下,才能打開、編譯和運行“瘦”客戶程序的項目。
14.1 一個ActiveForm的例子
  Delphi 4可以把分布式的數據庫結構引申到Internet/Intranet上,把“瘦”客戶程序作為ActiveForm嵌入到網頁中讓人們下載,然后在當地執行。
  這一節剖析一個ActiveForm的示范程序,項目名稱叫empeditx,它可以在C:/PRogram Files/Borland/Delphi4/Demos/Midas/Activefm目錄中找到。它的主窗體如圖14.1所示。
  在打開這個項目之前,先要編譯、運行位于C:/ProgramFiles/Borland/Delphi4/Demos/ Midas/Empedit目錄中的Server項目,這是個應用服務器,如圖14.2所示。
  這里還要交代一下,在應用服務器上,用一個TQuery構件引入數據集,它的SQL語句如下:
  Select * From Employee
  在這個ActiveForm上,有一個TDCOMConnection構件,用于以DCOM方式連接應用服務器,它的ServerName屬性設為Serv.EmpServer,它的ServerGUID設為{53BC6562-5B3E-11D0-9FFC-00A0248E4B9A}。
  ActiveForm用TClientDataSet構件從應用服務器引入數據集,它的RemoteServer屬性設為MidasConnection即TDCOMConnection構件的名稱,它的ProviderName屬性設為EmpQuery即應用服務器上的TQuery構件,由它來提供iprovider接口。
  ActiveForm上有幾個數據控件,用于顯示數據,它們都通過一個TDataSource構件獲得數據。此外,ActiveForm上還有一個TDBNavigator構件,用于瀏覽數據集。
  由于本節要介紹的示范程序是一個ActiveForm,它的大部分代碼與類型庫有關,我們只把其中涉及到MIDAS技術的部分“拎”出來。
  當用戶單擊ActiveForm上的“Get Employees”按鈕時,就從應用服務器檢索數據。每次檢索到的記錄數取決于TClientDataSet構件的PacketRecords屬性。
Procedure TEmpEditForm.QueryButtonClick(Sender: TObject);
Begin
Employees.Close;
{ Employees是TClientDataSet構件的名稱}E
mployees.Open;
End;
  當用戶單擊ActiveForm上的“Update Employees”按鈕時,把用戶對數據的修改寫到數據集中。
Procedure TEmpEditForm.UpdateButtonClick(Sender: TObject);
Begin
Employees.ApplyUpdates(-1);
End;
  當用戶單擊ActiveForm上的“Undo Last Change”按鈕時,取消用戶對數據的修改。
Procedure TEmpEditForm.UndoButtonClick(Sender: TObject);
Begin
Employees.UndoLastChange(True);
End;
  為了測試這個ActiveForm,首先需要把它發布到Web服務器上,供下載用。為此,要使用“Project”菜單上的“Web Deployment Options”命令設置有關Web發布的選項,主要是指定ActiveForm在Web服務器上的URL。然后使用“Project”菜單上的“WebDeploy”命令把ActiveForm發布到Web服務器上。
14.2 一個動態傳遞SQL語句的示范程序
  這一節要剖析一個動態傳遞SQL語句的示范程序,可以在C:/Program Files/Borland/Delphi4/Demos/Midas/Adhoc目錄中找到。
  這個程序分為應用服務器和客戶程序兩個部分。當客戶程序通過IProvider接口調用DataRequest請求數據時,把用戶輸入的SQL語句傳遞給應用服務器,這樣,應用服務器上的TQuery構件就能夠根據用戶的要求來查詢數據庫,這就是本示范程序的基本思路。
  先來剖析應用服務器,看它的數據模塊,如圖14.3所示。
  圖14.3 數據模塊
  數據模塊上有這么幾個構件:
  一個Tsession構件,它的SessionName屬性設為Session1_2。
  一個TDatabase構件,它的SessionName屬性設為Session1_2,并且定義了一個專用的別名叫ADHOC。
  一個TQuery構件,它的DatabaseName屬性設為ADHOC,它的SessionName屬性也設為Session1_2,而它的SQL屬性為空,因為SQL語句由客戶程序動態地傳遞過來。
  一個TProvider構件,它的DataSet屬性設為AdHocQuery即TQuery構件的名稱。
  現在我們暫時不管數據模塊,再來看看應用服務器的主窗體,如圖14.4所示。
  圖14.4 應用服務器的主窗體
  主窗體上顯示兩個計數,一個是當前連接應用服務器的客戶數(Clients),另一個是已經執行的查詢次數(Queries)。
  用什么來判斷當前的客戶數,這與數據模塊的實例方式有關。我們可以回到數據模塊的單元,看看它的初始化代碼:
Initialization
TComponentFactory.Create(ComServer, TAdHocQueryDemo,
Class_AdHocQueryDemo, ciMultiInstance);
End.
  可以看出,這個數據模塊的實例方式設為ciMultiInstance,表示每當有一個客戶連接應用服務器,就會創建數據模塊的一個新的實例。因此,數據模塊的實例數就是當前的客戶數。怎樣統計數據模塊的實例數呢?很簡單,只要處理數據模塊的OnCreate事件。
Procedure TAdHocQueryDemo.AdHocQueryDemoCreate(Sender: TObject);
Begin
MainForm.UpdateClientCount(1);
End;
  當一個客戶退出連接時,將刪除一個數據模塊的實例,此時將觸發數據模塊的OnDestroy事件:
Procedure TAdHocQueryDemo.AdHocQueryDemoDestroy(Sender: TObject);
Begin
MainForm.UpdateClientCount(-1);
End;
  其中,UpdateClientCount函數是在主窗體的單元中定義的:
Procedure TMainForm.UpdateClientCount(Incr: Integer);
Begin
FClientCount := FClientCount + Incr;
ClientCount.Caption := IntToStr(FClientCount);
End;
  請讀者注意Incr參數的作用。怎樣統計已經執行過的查詢數呢?很簡單,只要統計TQuery構件被激活的次數就可以了。因此,程序處理了TQuery構件的AfterOpen事件。Procedure TAdHocQueryDemo.AdHocQueryAfterOpen(DataSet: TDataSet);
Begin
MainForm.IncQueryCount;
End;
  IncQueryCount是在主窗體的單元中定義的:
Procedure TMainForm.IncQueryCount;
Begin
Inc(FQueryCount);
QueryCount.Caption := IntToStr(FQueryCount);
End;
  在打開客戶程序的項目之前,必須先編譯和運行應用服務器的項目。好,現在我們打開客戶程序的項目,它的主窗體如圖14.5所示。
  這個客戶程序用一個TDCOMConnection構件連接應用服務器,它的ServerName屬性設為Serv.AdHocQueryDemo??蛻舫绦蛴肨ClientDataSet構件從應用服務器引入數據集,它的RemoteServer屬性設為TDCOMConnection構件的名稱,它的ProviderName屬性設為AdHocQuery,這是應用服務器輸出的 IProvider接口。
  客戶程序上有一個柵格,用于顯示數據,柵格與數據集之間通過TDataSource構件連接。此外,客戶程序上有一個多行文本編輯器,讓用戶輸入SQL語句。有一個組合框用于選擇要訪問的數據庫。我們還是先從處理OnCreate事件的句柄開始。
Procedure TForm1.FormCreate(Sender: TObject);
var I: Integer;DBNames: OleVariant;
Begin
RemoteServer.Connected := True;
DBNames := RemoteServer.AppServer.GetDatabaseNames;
If VarIsArray(DBNames) then
For I := 0 to VarArrayHighBound(DBNames, 1) Do
DatabaseName.Items.Add(DBNames[I]);
DatabaseNameClick(Self);
End;
  首先,把TDCOMConnection構件的Connected屬性設為True,將連接應用服務器。TDCOMConnection構件的AppServer屬性將返回應用服務器上數據模塊的接口,通過此接口就可以調用遠程數據模塊的方法,例如GetDatabaseNames。GetDatabaseNames是在應用服務器的數據模塊單元中定義的:
Function TAdHocQueryDemo.GetDatabaseNames: OleVariant;
var I: Integer;
DBNames: TStrings;
Begin
DBNames := TStringList.Create;
Try
Session1.GetDatabaseNames(DBNames);
Result := VarArrayCreate([0, DBNames.Count - 1], varOleStr);
For I := 0 to DBNames.Count - 1 DoResult[I] := DBNames[I];
FinallyDBNames.Free;
End;
End;
  GetDatabaseNames函數的作用是返回一個數組,該數組由所有已定義的別名和BDE會話期對象專用的別名組成?,F在我們回到客戶程序中,調用了數據模塊的GetDatabaseNames函數后,就把檢索到別名加到窗體右上角的組合框中,然后調用DatabaseNameClick函數。
Procedure TForm1.DatabaseNameClick(Sender: TObject);
var PassWord: string;
Begin
If DatabaseName.Text <> '' then
Begin
ClientData.Close;
Try
RemoteServer.AppServer.SetDatabaseName(DatabaseName.Text, '');
Except
On E: Exception DoIf E.Message = 'Password Required' then
Begin
If InputQuery(E.Message, 'Enter password', Password) then
RemoteServer.AppServer.SetDatabaseName(DatabaseName.Text, Password);
End
Else
Raise;
End;
End;
End;
  調用DatabaseNameClick的目的是使應用服務器與另一個數據庫連接,這就需要通過AppServer屬性獲得數據模塊的接口,然后調用數據模塊單元的SetDatabaseName。SetDatabaseName是在應用服務器的數據模塊單元中定義的:
Procedure TAdHocQueryDemo.SetDatabaseName(const DBName, Password: WideString);
Begin
Try
Database1.Close;
Database1.AliasName := DBName;
If Password <> '' then
  Database1.Params.Values['PASSWORD'] := Password;
Database1.Open;
Except
{如果數據庫打開失敗,很可能是因為該數據庫需要口令}
On E: EDBEngineError DoIf (Password = '') then Raise Exception.Create('Password Required')Else
Raise;
End;
End;
  SetDatabaseName的作用是修改TDatabase構件的AliasName屬性,然后連接新的數據庫,如果失敗,就觸發一個異常。在客戶程序的DatabaseNameClick過程中,如果出現異常,就彈出一個輸入框,讓用戶輸入口令,然后再次調用數據模塊的SetDatabaseName。
  當用戶在“Query”框中輸入了SQL語句,就可以單擊“Run Query”按鈕執行這個查詢。問題是,只有應用服務器才可以執行查詢,那么客戶程序是怎樣把SQL語句傳遞給應用服務器的呢?這就是本示范程序的關鍵之處。
Procedure TForm1.RunButtonClick(Sender: TObject);
Begin
ClientData.Close;
ClientData.Provider.DataRequest(SQL.Lines.Text);
ClientData.Open;
End;
  原來,客戶程序通過IProvider接口調用DataRequest把用戶輸入的SQL語句傳遞給應用服務器??蛻舫绦蛲ㄟ^IProvider接口調用DataRequest將在應用服務器端觸發OnDataRequest事件,我們來看看應用服務器是怎樣處理OnDataRequest事件的。
Function TAdHocQueryDemo.AdHocProviderDataRequest(Sender: TObject; Input: OleVariant): OleVariant;
Begin
AdHocQuery.SQL.Text := Input;
End;
  至此,一個動態傳遞SQL語句的示范程序剖析完畢,請讀者仔細琢磨其中的編程技巧。實際上,通過IProvider接口調用DataRequest可以傳遞任何信息。
14.4 一個全面演示TClientDataSet功能的示范程序
  這一節介紹一個演示TClientDataSet功能的示范程序,項目名稱叫Alchtest,它可以在C:/Program Files/Borland/Delphi4/Demos/Midas/Alchtest目錄中找到,主窗體如圖14.6所示。
  這個程序的總體思路是,用一個多頁控件讓用戶修改TClientDataSet的屬性或者調用它的方法,然后在下面的TAB控件中演示修改后的效果。
  程序首先在處理OnCreate事件的句柄中做了一些初始化的工作。
Procedure TDBClientTest.FormCreate(Sender: TObject);
var I: Integer;
Begin
Database1.Close;
FMaxErrors := -1;
FPacketRecs := -1;
SetCurrentDirectory(PChar(ExtractFilePath(ParamStr(0))));
For I := 0 to StatusBar.Panels.Count - 1 do
StatusBar.Panels[I].Text := '';
Application.OnIdle := ShowHeapStatus;
Application.OnHint := OnHint;
StreamSettings(False);
SetEventsVisible(ViewEvents.Checked);
End;
  其中,指定ShowHeapStatus作為處理應用程序的OnIdle事件的句柄,指定OnHint作為處理應用程序的OnHint事件的句柄。ShowHeapStatus是這樣定義的:
Procedure TDBClientTest.ShowHeapStatus(Sender: TObject; var Done: Boolean);
Begin
Caption := Format('Client DataSet Test Form - (Blocks=%d Bytes=%d)',[AllocMemCount, AllocMemSize]);
End;
  ShowHeapStatus的作用是在應用程序空閑的時候,在主窗口的標題欄顯示堆的狀態,其中,AllocMemCount是當前分配的內存塊數,AllocMemSize是當前分配的內存總長度。
  OnHint是這樣定義的:
Procedure TDBClientTest.OnHint(Sender: TObject);
Begin
StatusMsg := Application.Hint;
End;
  StatusMsg是一個自定義的屬性,用于表達要在狀態欄上顯示的提示信息。
  在處理OnCreate事件的句柄中還調用了StreamSettings函數。StreamSettings是非常有用的,當窗體關閉時,就調用StreamSettings把窗體上一些控件的狀態保存到一個配置文件中。當窗體彈出時,就調用StreamSettings讀取配置文件以初始化窗體上的控件。
Procedure TDBClientTest.StreamSettings(Write: Boolean);
Procedure WriteStr(const OptName, Value: string);
Begin
FConfig.WriteString('Settings', OptName, Value);
End;

Procedure WriteBool(const OptName: string; Value: Boolean);
Begin
FConfig.WriteBool('Settings', OptName, Value);
End;

Function ReadStr(const OptName: string): string;
Begin
Result := FConfig.ReadString('Settings', OptName, '');
End;

Function ReadBool(const OptName: string): Boolean;
Begin
Result := FConfig.ReadBool('Settings', OptName, False);
End;

Function FindPage(const PageName: string): TTabSheet;
var I: Integer;
Begin
For I := AreaSelector.PageCount - 1 downto 0 do
Begin
  Result := AreaSelector.Pages[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]));
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事件,程序就把下面的柵格中選擇的字段作為關鍵字段?!癓ocate”頁如圖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屬性指定了TDCOMConnection構件,它的ProviderName屬性設為Country。
  此外,窗體上有一個柵格用于顯示數據集中的數據,還有一個“Open”按鈕用于打開數據集。
  由于TDCOMConnection構件的LoginPrompt屬性設為True,當客戶程序試圖連接應用服務器時就會彈出一個“Remote Login”對話框,要求用戶輸入用戶名和口令。登錄以后,就觸發OnLogin事件。在處理這個事件的句柄中,客戶程序通過AppServer屬性獲得數據模塊的接口,從而調用數據模塊的Login。
  Procedure TForm1.DCOMConnection1Login(Sender: TObject; Username,Password: String);
Begin
DCOMConnection1.AppServer.Login(UserName, Password);
End;
  在應用服務器的數據模塊單元中,Login是這樣定義的。
Procedure TLoginDemo.Login(const UserName, Password: WideString);
Begin
Form1.ListBox1.Items.Add(UserName);
FLoggedIn := True;
FUserName := UserName;
End;
  Login把用戶名加到列表框中,然后把FLoggedIn變量設為True,表示用戶已登錄。當用戶單擊“Open”按鈕,就調用TClientDataSet構件的Open打開數據集。
Procedure TForm1.Button1Click(Sender: TObject);
Begin
ClientDataSet1.Open;
End;
14.7 一個演示Master/Detail關系的示范程序
  這一節剖析一個演示Master/Detail關系的示范程序,它可以在C:/ProgramFiles/Borland/Delphi4/ Demos/Midas/Mstrdtl目錄中找到。
  這個程序分為應用服務器和客戶程序兩個部分。應用服務器有一個窗體,不過,這個窗體其實是多余的,如果不想顯示,可以打開應用服務器的項目文件,加入這么一行:
  Application.ShowMainForm := False;
  應用服務器的數據模塊如圖14.19所示。
  應用服務器的數據模塊上有這么幾個構件:
  名為Database的TDatabase構件,其AliasName屬性設為IBLOCAL,并且定義了一個應用程序專用的別名叫ProjectDB。其Params屬性提供了用戶名和口令。
  名為Project的TTable構件,其DatabaseName屬性設為ProjectDB,它的TableName屬性設為PROJECT(注意:必須已運行Interbase Server)。
  名為Employee的TQuery構件,其DatabaseName屬性設為ProjectDB,它的SQL語句如下:Select * From EMPLOYEE_PROJECT E Where E.PROJ_ID= :PROJ_ID
  名為EmpProj的TQuery構件,其DatabaseName屬性設為ProjectDB,它的SQL語句如下:Select EMP_NO,FULL_NAME From EMPLOYEE
  名為UpdateQuery的TQuery構件,其DatabaseName屬性設為ProjectDB,它的SQL語句目前是空的。
  名為ProjectProvider的TProvider構件,其DataSet屬性設為Project。
  名為ProjectSource的TDataSource構件,其DataSet屬性設為Project。編譯并運行應用服務器?,F在可以打開客戶程序的項目,它的數據模塊如圖14.20所示。
  圖14.20 數據模塊
  客戶程序的數據模塊上有這么幾個構件:
  名為DCOMConnection的TDCOMConnection構件,其ServerName屬性設為Serv.ProjectData。
  名為Project的TClientDataSet構件,其RemoteServer屬性設為DCOMConnection它的ProviderName屬性設為ProjectProvider。并且建立了一個叫ProjectEmpProj的永久字段對象,它的類型是TDataSetField。與Project對應的TDataSource構件叫ProjectSource。
  名為Emp_Proj的TClientDataSet構件,其RemoteServer屬性和ProviderName屬性都是空的,但它的DataSetField屬性設為叫ProjectEmpProj的字段對象,這就構成了Master/Detail關系。與Emp_Proj對應的TDataSource構件叫EmpProjSource。
  名為Employee的TClientDataSet構件,其RemoteServer屬性指定了TDCOMConnection構件,但它的ProviderName屬性設為Employee。與Employee對應的TDataSource構件叫EmployeeSource。
  我們再來看客戶程序的主窗體,如圖14.21所示。
  左邊一個柵格只顯示Project數據集中的PROJ_NAME字段即項目名稱,“Product”框顯示Project數據集中的PRODUCT字段,“Description”框顯示Project數據集中的PROJ_DESC字段,并且用一個TDBNavigator構件為Project數據集導航。
  右下角的柵格顯示Emp_Proj數據集中一個叫EmployeeName的字段的值,這是個Lookup字段,它的LookupDataSet屬性設為Employee,它的LookupKeyField屬性設為EMP_NO,它的LookupResultField屬性設為FULL_NAME。當用戶用導航器瀏覽Project數據集的記錄時,右下角的柵格就從Employee數據集中查找與EMP_NO字段匹配的記錄,并且顯示其中的FULL_NAME字段。
  由于右下角的柵格只建立了一個永久的列對象,因此,可以把這一列的寬度設為與柵格本身同寬,它是在處理窗體的OnCreate事件的句柄中進行的。
Procedure TClientForm.FormCreate(Sender: TObject);
Begin
MemberGrid.Columns[0].Width :=MemberGrid.ClientWidth - GetSystemMetrics(SM_CXVSCROLL);
End;
  由于一個項目中不止一個雇員,為了醒目起見,可以把其中的負責人加粗顯示,這需要處理柵格的OnDrawColumnCell事件。
Procedure TClientForm.MemberGridDrawColumnCell(Sender: TObject; const Rect: TRect;DataCol: Integer;Column: TColumn;State: TGridDrawState);
Begin
If DM.ProjectTEAM_LEADER.Value = DM.Emp_ProjEMP_NO.Value then MemberGrid.Canvas.Font.Style := [fsBold];
MemberGrid.DefaultDrawColumnCell(Rect, DataCol, Column, State);
End;
  怎樣來判斷其中的負責人呢?在Project數據集中,有一個TEAM_LEADER 字段,它存儲的是項目負責人的雇員編號。在Emp_Proj數據集中,有一個EMP_NO,它存儲的也是雇員編號,如果這兩者相等,即表示該雇員是項目負責人。當用戶單擊“Add”按鈕,就可以在柵格中增加一條記錄,即在項目中增加一個雇員。
Procedure TClientForm.AddBtnClick(Sender: TObject);
Begin
MemberGrid.SetFocus;
DM.Emp_Proj.Append;
MemberGrid.EditorMode := True;
End;
  由于柵格事先建立了一個永久的列對象,而該列對象的FieldName屬性指定了一個Lookup字段,所以,用戶可以從一個組合框中選擇一個值。
  當用戶單擊“Delete”按鈕,就刪除當前記錄,即一個雇員。
Procedure TClientForm.DeleteBtnClick(Sender: TObject);
Begin
DM.Emp_Proj.Delete;
End;
  當用戶先選擇其中一個雇員,然后單擊“Leader”按鈕,就把該雇員設為項目負責人。
Procedure TClientForm.LeaderBtnClick(Sender: TObject);
var NewLeader: Integer;
Begin
NewLeader := DM.Emp_ProjEMP_NO.Value;
If not (DM.Project.State in dsEditModes) then DM.Project.Edit;
DM.ProjectTEAM_LEADER.Value := NewLeader;
MemberGrid.Refresh;
End;
  增加、刪除或修改了記錄后,用戶應當單擊“Apply Update”按鈕更新數據庫。
Procedure TClientForm.ApplyUpdatesBtnClick(Sender: TObject);
Begin
DM.ApplyUpdates;
End;
  在數據模塊的單元中,ApplyUpdates是這樣定義的:
Procedure TDM.ApplyUpdates;
Begin
If Project.ApplyUpdates(0) = 0 then Project.Refresh;
End;
  可以看出,數據模塊的ApplyUpdates又調用了TClientDataSet構件的ApplyUpdates,并且把MaxErrors參數設為0,這樣,只要應用服務器發現有一個錯誤的記錄,更新就停止。
  當用戶在左邊的柵格中試圖增加一個新的項目時,會觸發TClientDataSet構件的OnNewRecord事件。由于這個柵格只顯示了PROJ_NAME字段,用戶不能直接輸入PROJ_ID字段的值,因此,程序在處理OnNewRecord事件的句柄中推出一個輸入框,讓用戶輸入PROJ_ID字段的值。如果用戶輸入的字符超過了該字段允許的長度,就觸發一個異常。
  如果用戶沒有輸入任何字符,也觸發一個異常。
Procedure TDM.ProjectNewRecord(DataSet: TDataSet);
va rValue: String;
Begin
If InputQuery('Project ID','Enter Project ID:',Value) then
Begin
If Length(Value) > ProjectPROJ_ID.Size then
Raise Exception.CreateFmt('Project ID can only be %d characters',[ProjectPROJ_ID.Size]);If Length(Value) = 0 then
Raise Exception.Create('Project ID is required');
End
Else
SysUtils.Abort;
ProjectPROJ_ID.Value := Value;
End;
  由于Project數據集與Employee數據集之間存在著Master/Detail關系,當刪除Project數據集的一條記錄時,應當先刪除Employee數據集中關聯的記錄。應用服務器利用TProvider構件的BeforeUpdateRecord事件實現了這一點。
Procedure TProjectData.ProjectProviderBeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet;DeltaDS: TClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
Const DeleteQuery = 'Delete From EMPLOYEE_PROJECT where PROJ_ID = :ProjID';
Begin
If (UpdateKind = ukDelete) and (SourceDS = Project) then
Begin
UpdateQuery.SQL.Text := DeleteQuery;
UpdateQuery.Params[0].AsString := DeltaDS.FieldByName('PROJ_ID').AsString;
UpdateQuery.ExecSQL;
End;
End;
14.9 一個動態設置查詢參數的示范程序
  這一節剖析一個動態設置查詢參數的示范程序,它可以在C:/ProgramFiles/Borland/Delphi4/ Demos/ Midas/Setparam目錄中找到。
  這個程序分為應用服務器和客戶程序兩個部分。當客戶程序通過TClientDataSet構件的Params屬性設置參數時,這些參數會自動地傳遞給應用服務器上的TQuery構件,這樣就能夠根據用戶的要求來查詢數據庫,這就是本示范程序的基本思路。
  我們來剖析應用服務器,先看它的數據模塊,如圖14.24所示。圖14.24 數據模塊數據模塊上只有一個TQuery構件,它的DatabaseName屬性設為DBDEMOS,它的SQL語句如下:
  Select * From EventsWhere Event_Date >= :Start_Date and Event_Date <= :End_Date Order by Event_Date
  可以看出,這個SQL語句中有兩個參數,一個是:Start_Date,另一個是:End_Date。
  現在我們暫時不管數據模塊,再來看看應用服務器的主窗體,如圖14.25所示。
  圖14.25 應用服務器的主窗體
  主窗體上顯示兩個計數,一個是當前連接應用服務器的客戶數(Clients),另一個是已經執行的查詢次數(Queries)。用什么來判斷當前的客戶數,這與數據模塊的實例方式有關。我們可以回到數據模塊的單元,看看它的初始化代碼:
Initialization
TComponentFactory.Create(ComServer, TSetParamDemo,
Class_SetParamDemo, ciMultiInstance);
End.
  可以看出,這個數據模塊的實例方式設為ciMultiInstance,表示每當有一個客戶連接應用服務器,就會創建數據模塊的一個新的實例。因此,數據模塊的實例數就是當前的客戶數。怎樣統計數據模塊的實例數呢?很簡單,只要處理數據模塊的OnCreate事件。
Procedure TSetParamDemo.SetParamDemoCreate(Sender: TObject);
Begin
MainForm.UpdateClientCount(1);
End;
  當一個客戶退出連接,將刪除一個數據模塊的實例,此時將觸發數據模塊的OnDestroy事件:
Procedure TSetParamDemo.SetParamDemoCreate(Sender: TObject);
Begin
MainForm.UpdateClientCount(1);
End;
  其中,UpdateClientCount是在主窗體的單元中定義的:
Procedure TMainForm.UpdateClientCount(Incr: Integer);
Begin
FClientCount := FClientCount + Incr;
ClientCount.Caption := IntToStr(FClientCount);
End;
  請注意Incr參數的作用。怎樣統計已經執行過的查詢數呢?也很簡單,只要統計TQuery構件被激活的次數就可以了。因此,程序處理了TQuery構件的AfterOpen事件。
Procedure TSetParamDemo.EventsAfterOpen(DataSet: TDataSet);
Begin
MainForm.IncQueryCount;
End;
  IncQueryCount是在主窗體的單元中定義的:
Procedure TMainForm.IncQueryCount;
Begin
Inc(FQueryCount);
QueryCount.Caption := IntToStr(FQueryCount);
End;
  編譯和運行這個應用服務器。打開客戶程序的項目,它的主窗體如圖14.26所示。
  窗體上有一個TDCOMConnection構件用于連接應用服務器,有一個叫Events的TClientDataSet構件,用于引入數據集。
  “Starting Date”框用于輸入:Start_Date參數的值,
  “Ending Date”框用于輸入:End_Date參數的值。中間的柵格用于顯示查詢的結果?!癉escription”框用于顯示Event_Description字段的值?!癙hoto”框用于顯示Event_Photo字段的值。
  客戶程序在處理窗體的OnCreate事件的句柄中對“Starting Date”框和“EndingDate”框進行初始化。
Procedure TForm1.FormCreate(Sender: TObject);
Begin
StartDate.Text := DateToStr(EncodeDate(96, 6, 19));
EndDate.Text := DateToStr(EncodeDate(96, 6, 21));
End;
  用戶可以在這兩個框中重新輸入其他日期,然后單擊“Show Events”按鈕。
Procedure TForm1.ShowEventsClick(Sender: TObject);
Begin
Events.Close;
Events.Params.ParamByName('Start_Date').AsDateTime:=StrToDateTime(StartDate.Text);Events.Params.ParamByName('End_Date').AsDateTime :=StrToDateTime(EndDate.Text);
Events.Open;
End;
  首先,要調用TClientDataset構件的Close關閉數據集,然后分別設置Start_Date參數和End_Date參數的值,最后,調用TClientDataset構件的Open打開數據集,此時,這兩個參數就被自動傳遞給應用服務器上的TQuery構件。

上一篇:改變文件夾圖標

下一篇:多層數據庫開發十三:剖析幾個數據庫應用程序

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
學習交流
熱門圖片

新聞熱點

疑難解答

圖片精選

網友關注

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久久999精品视频| 欧美性理论片在线观看片免费| 国产午夜精品全部视频播放| 国外视频精品毛片| 中文字幕欧美日韩精品| 欧美在线性爱视频| 欧美wwwwww| 日韩精品免费在线| 久久久久久久97| 亚洲美女福利视频网站| 国产福利精品av综合导导航| 国产精品久久久久久久7电影| www.欧美视频| 另类色图亚洲色图| 久久精品视频播放| 懂色av中文一区二区三区天美| 欧美成人三级视频网站| 91在线观看欧美日韩| 亚洲第一精品久久忘忧草社区| 日韩中文字幕精品视频| 欧美丝袜一区二区| 精品亚洲男同gayvideo网站| 久久精品99久久久香蕉| 欧美伊久线香蕉线新在线| 亚洲三级 欧美三级| 国产精品视频精品视频| 国产丝袜视频一区| 国产欧美日韩精品在线观看| 欧美最猛黑人xxxx黑人猛叫黄| 日韩在线观看你懂的| 亚洲人成在线观看| 亚洲电影免费观看高清完整版在线| 成人在线激情视频| 国产999在线观看| 亚洲最大福利网| 中文字幕亚洲一区二区三区五十路| 91影视免费在线观看| 欧美与欧洲交xxxx免费观看| 亚洲毛片在线免费观看| 精品久久久香蕉免费精品视频| 久久亚洲国产成人| 91影院在线免费观看视频| 国产日韩欧美影视| 国产欧美一区二区三区视频| 欧美自拍视频在线| 亚洲tv在线观看| 亚洲精品视频在线播放| 久久视频免费在线播放| 国产精品久久久久7777婷婷| 日韩在线精品一区| 国产亚洲欧美日韩一区二区| www国产亚洲精品久久网站| 欧美巨猛xxxx猛交黑人97人| 国产视频在线观看一区二区| 国产一区二区三区中文| 欧美激情视频在线| 91久热免费在线视频| 亚洲成人久久久久| 亚洲精品按摩视频| 国产精品www网站| 国内精品一区二区三区| 亚洲精品久久久久久久久久久| 亚洲成人激情图| 欧美香蕉大胸在线视频观看| 日本精品免费一区二区三区| 亚洲japanese制服美女| 欧美一区二区.| 国产精品久久久久免费a∨| 日韩亚洲精品电影| 色琪琪综合男人的天堂aⅴ视频| 国产精品69久久久久| 中文字幕欧美精品日韩中文字幕| 欧美一区深夜视频| 日本aⅴ大伊香蕉精品视频| 亚洲xxx自由成熟| 欧美日韩中文字幕在线视频| 高清在线视频日韩欧美| 欧美日韩国产精品一区| 欧美激情成人在线视频| 91欧美精品午夜性色福利在线| 欧美成人剧情片在线观看| 亚洲一区二区三区sesese| 亚洲人成在线播放| 国产成人亚洲综合青青| 在线视频日韩精品| 狠狠色噜噜狠狠狠狠97| 美女啪啪无遮挡免费久久网站| 欧美高清电影在线看| 国产精品亚洲网站| 精品人伦一区二区三区蜜桃网站| 国产自产女人91一区在线观看| 国产精品免费久久久久影院| 国产精品观看在线亚洲人成网| 国产偷亚洲偷欧美偷精品| 午夜欧美不卡精品aaaaa| 久久国产精品久久久久久久久久| 色一情一乱一区二区| 国内精品久久久| 欧美性高跟鞋xxxxhd| 欧美性xxxx极品hd满灌| 美女视频黄免费的亚洲男人天堂| 日韩欧美999| 久久在精品线影院精品国产| 亚洲欧美自拍一区| 欧美日韩亚洲成人| 日韩中文字幕在线| 国产一区二区三区在线观看网站| 亚洲电影免费在线观看| 粉嫩av一区二区三区免费野| 欧美片一区二区三区| 91精品国产综合久久久久久蜜臀| 九九久久综合网站| 9.1国产丝袜在线观看| 亚洲精品国产综合区久久久久久久| 国产精品一区二区三区成人| 社区色欧美激情 | 久久91精品国产91久久跳| 日韩av在线免费观看| 91精品久久久久久久久久久| 亚洲一区二区三区乱码aⅴ蜜桃女| 免费99精品国产自在在线| 91精品国产亚洲| 91久久精品日日躁夜夜躁国产| 最新国产成人av网站网址麻豆| 成人福利视频网| 亚洲欧洲国产伦综合| 国产精品mp4| 日韩av毛片网| 成人自拍性视频| 91九色国产在线| www.精品av.com| 92国产精品视频| 久久久久久久亚洲精品| 亚洲精品国产福利| www国产91| 中文字幕日韩精品有码视频| 久久人体大胆视频| 久久夜色精品国产亚洲aⅴ| 亚洲二区中文字幕| 久久免费福利视频| 91九色蝌蚪国产| 91精品国产综合久久香蕉的用户体验| 啪一啪鲁一鲁2019在线视频| 亚洲欧美精品suv| 亚洲国产天堂久久综合网| 日韩av片免费在线观看| 在线观看国产精品淫| 欧美亚洲在线视频| 97精品一区二区三区| 俺去啦;欧美日韩| 欧美日韩中国免费专区在线看| 欧美国产日产韩国视频| 国产一区二区黄| 日韩女优人人人人射在线视频| 国产一区二区香蕉| 97国产在线观看| 国产精品一区=区| 久久精品电影网| 欧美激情视频三区| 国产成人在线视频| 日韩h在线观看| 亚洲图片欧美午夜| 国产91精品在线播放| 中文字幕日韩精品在线|