?? core14.htm
字號:
<html><head><title>第十四章 剖析幾個MIDAS示范程序 http://jjlzg.yeah.net</title><meta http-equiv=Content-Type content="text/html; charset=gb2312"></head><body bgcolor="#00000" text="#00cc66"><b>第十四章 剖析幾個MIDAS示范程序</b><br> MIDAS是Multi-Tier Distributed Application Services Suite的簡稱,為Delphi 4的一個關(guān)鍵技術(shù)。對于初學(xué)者來說,MIDAS具有相當(dāng)?shù)碾y度,因此,這一章詳細(xì)剖析幾個MIDAS示范程序,以幫助讀者理解和掌握MIDAS技術(shù)。<br> 與一般的數(shù)據(jù)庫應(yīng)用程序不同的是,只有當(dāng)應(yīng)用服務(wù)器正在運(yùn)行的情況下,才能打開、編譯和運(yùn)行“瘦”客戶程序的項目。<br> <b>14.1 一個ActiveForm的例子</b><br> Delphi 4可以把分布式的數(shù)據(jù)庫結(jié)構(gòu)引申到Internet/Intranet上,把“瘦”客戶程序作為ActiveForm嵌入到網(wǎng)頁中讓人們下載,然后在當(dāng)?shù)貓?zhí)行。<br> 這一節(jié)剖析一個ActiveForm的示范程序,項目名稱叫empeditx,它可以在C:\Program Files\Borland\Delphi4\Demos\Midas\Activefm目錄中找到。它的主窗體如圖14.1所示。 <br> 在打開這個項目之前,先要編譯、運(yùn)行位于C:\ProgramFiles\Borland\Delphi4\Demos\ Midas\Empedit目錄中的Server項目,這是個應(yīng)用服務(wù)器,如圖14.2所示。<br> 這里還要交代一下,在應(yīng)用服務(wù)器上,用一個TQuery構(gòu)件引入數(shù)據(jù)集,它的SQL語句如下:<br> Select * From Employee<br> 在這個ActiveForm上,有一個TDCOMConnection構(gòu)件,用于以DCOM方式連接應(yīng)用服務(wù)器,它的ServerName屬性設(shè)為Serv.EmpServer,它的ServerGUID設(shè)為{53BC6562-5B3E-11D0-9FFC-00A0248E4B9A}。 <br> ActiveForm用TClientDataSet構(gòu)件從應(yīng)用服務(wù)器引入數(shù)據(jù)集,它的RemoteServer屬性設(shè)為MidasConnection即TDCOMConnection構(gòu)件的名稱,它的ProviderName屬性設(shè)為EmpQuery即應(yīng)用服務(wù)器上的TQuery構(gòu)件,由它來提供IProvider接口。<br> ActiveForm上有幾個數(shù)據(jù)控件,用于顯示數(shù)據(jù),它們都通過一個TDataSource構(gòu)件獲得數(shù)據(jù)。此外,ActiveForm上還有一個TDBNavigator構(gòu)件,用于瀏覽數(shù)據(jù)集。<br> 由于本節(jié)要介紹的示范程序是一個ActiveForm,它的大部分代碼與類型庫有關(guān),我們只把其中涉及到MIDAS技術(shù)的部分“拎”出來。<br> 當(dāng)用戶單擊ActiveForm上的“Get Employees”按鈕時,就從應(yīng)用服務(wù)器檢索數(shù)據(jù)。每次檢索到的記錄數(shù)取決于TClientDataSet構(gòu)件的PacketRecords屬性。<br> Procedure TEmpEditForm.QueryButtonClick(Sender: TObject); <br> Begin<br> Employees.Close;<br> { Employees是TClientDataSet構(gòu)件的名稱}E<br> mployees.Open;<br> End;<br> 當(dāng)用戶單擊ActiveForm上的“Update Employees”按鈕時,把用戶對數(shù)據(jù)的修改寫到數(shù)據(jù)集中。<br> Procedure TEmpEditForm.UpdateButtonClick(Sender: TObject);<br> Begin<br> Employees.ApplyUpdates(-1);<br> End;<br> 當(dāng)用戶單擊ActiveForm上的“Undo Last Change”按鈕時,取消用戶對數(shù)據(jù)的修改。<br> Procedure TEmpEditForm.UndoButtonClick(Sender: TObject);<br> Begin<br> Employees.UndoLastChange(True);<br> End;<br> 為了測試這個ActiveForm,首先需要把它發(fā)布到Web服務(wù)器上,供下載用。為此,要使用“Project”菜單上的“Web Deployment Options”命令設(shè)置有關(guān)Web發(fā)布的選項,主要是指定ActiveForm在Web服務(wù)器上的URL。然后使用“Project”菜單上的“WebDeploy”命令把ActiveForm發(fā)布到Web服務(wù)器上。 <br> <b>14.2 一個動態(tài)傳遞SQL語句的示范程序</b><br> 這一節(jié)要剖析一個動態(tài)傳遞SQL語句的示范程序,可以在C:\Program Files\Borland\Delphi4\Demos\Midas\Adhoc目錄中找到。<br> 這個程序分為應(yīng)用服務(wù)器和客戶程序兩個部分。當(dāng)客戶程序通過IProvider接口調(diào)用DataRequest請求數(shù)據(jù)時,把用戶輸入的SQL語句傳遞給應(yīng)用服務(wù)器,這樣,應(yīng)用服務(wù)器上的TQuery構(gòu)件就能夠根據(jù)用戶的要求來查詢數(shù)據(jù)庫,這就是本示范程序的基本思路。<br> 先來剖析應(yīng)用服務(wù)器,看它的數(shù)據(jù)模塊,如圖14.3所示。<br> 圖14.3 數(shù)據(jù)模塊<br> 數(shù)據(jù)模塊上有這么幾個構(gòu)件:<br> 一個TSession構(gòu)件,它的SessionName屬性設(shè)為Session1_2。<br> 一個TDatabase構(gòu)件,它的SessionName屬性設(shè)為Session1_2,并且定義了一個專用的別名叫ADHOC。<br> 一個TQuery構(gòu)件,它的DatabaseName屬性設(shè)為ADHOC,它的SessionName屬性也設(shè)為Session1_2,而它的SQL屬性為空,因為SQL語句由客戶程序動態(tài)地傳遞過來。<br> 一個TProvider構(gòu)件,它的DataSet屬性設(shè)為AdHocQuery即TQuery構(gòu)件的名稱。<br> 現(xiàn)在我們暫時不管數(shù)據(jù)模塊,再來看看應(yīng)用服務(wù)器的主窗體,如圖14.4所示。<br> 圖14.4 應(yīng)用服務(wù)器的主窗體<br> 主窗體上顯示兩個計數(shù),一個是當(dāng)前連接應(yīng)用服務(wù)器的客戶數(shù)(Clients),另一個是已經(jīng)執(zhí)行的查詢次數(shù)(Queries)。<br> 用什么來判斷當(dāng)前的客戶數(shù),這與數(shù)據(jù)模塊的實例方式有關(guān)。我們可以回到數(shù)據(jù)模塊的單元,看看它的初始化代碼: <br> Initialization<br> TComponentFactory.Create(ComServer, TAdHocQueryDemo,<br> Class_AdHocQueryDemo, ciMultiInstance);<br> End.<br> 可以看出,這個數(shù)據(jù)模塊的實例方式設(shè)為ciMultiInstance,表示每當(dāng)有一個客戶連接應(yīng)用服務(wù)器,就會創(chuàng)建數(shù)據(jù)模塊的一個新的實例。因此,數(shù)據(jù)模塊的實例數(shù)就是當(dāng)前的客戶數(shù)。怎樣統(tǒng)計數(shù)據(jù)模塊的實例數(shù)呢?很簡單,只要處理數(shù)據(jù)模塊的OnCreate事件。<br> Procedure TAdHocQueryDemo.AdHocQueryDemoCreate(Sender: TObject);<br> Begin<br> MainForm.UpdateClientCount(1);<br> End;<br> 當(dāng)一個客戶退出連接時,將刪除一個數(shù)據(jù)模塊的實例,此時將觸發(fā)數(shù)據(jù)模塊的OnDestroy事件:<br> Procedure TAdHocQueryDemo.AdHocQueryDemoDestroy(Sender: TObject);<br> Begin<br> MainForm.UpdateClientCount(-1);<br> End;<br> 其中,UpdateClientCount函數(shù)是在主窗體的單元中定義的:<br> Procedure TMainForm.UpdateClientCount(Incr: Integer);<br> Begin<br> FClientCount := FClientCount + Incr;<br> ClientCount.Caption := IntToStr(FClientCount);<br> End;<br> 請讀者注意Incr參數(shù)的作用。怎樣統(tǒng)計已經(jīng)執(zhí)行過的查詢數(shù)呢?很簡單,只要統(tǒng)計TQuery構(gòu)件被激活的次數(shù)就可以了。因此,程序處理了TQuery構(gòu)件的AfterOpen事件。Procedure TAdHocQueryDemo.AdHocQueryAfterOpen(DataSet: TDataSet);<br> Begin<br> MainForm.IncQueryCount;<br> End;<br> IncQueryCount是在主窗體的單元中定義的:<br> Procedure TMainForm.IncQueryCount;<br> Begin<br> Inc(FQueryCount);<br> QueryCount.Caption := IntToStr(FQueryCount);<br> End;<br> 在打開客戶程序的項目之前,必須先編譯和運(yùn)行應(yīng)用服務(wù)器的項目。好,現(xiàn)在我們打開客戶程序的項目,它的主窗體如圖14.5所示。<br> 這個客戶程序用一個TDCOMConnection構(gòu)件連接應(yīng)用服務(wù)器,它的ServerName屬性設(shè)為Serv.AdHocQueryDemo。客戶程序用TClientDataSet構(gòu)件從應(yīng)用服務(wù)器引入數(shù)據(jù)集,它的RemoteServer屬性設(shè)為TDCOMConnection構(gòu)件的名稱,它的ProviderName屬性設(shè)為AdHocQuery,這是應(yīng)用服務(wù)器輸出的 IProvider接口。<br> 客戶程序上有一個柵格,用于顯示數(shù)據(jù),柵格與數(shù)據(jù)集之間通過TDataSource構(gòu)件連接。此外,客戶程序上有一個多行文本編輯器,讓用戶輸入SQL語句。有一個組合框用于選擇要訪問的數(shù)據(jù)庫。我們還是先從處理OnCreate事件的句柄開始。<br> Procedure TForm1.FormCreate(Sender: TObject);<br> var I: Integer;DBNames: OleVariant;<br> Begin<br> RemoteServer.Connected := True;<br> DBNames := RemoteServer.AppServer.GetDatabaseNames;<br> If VarIsArray(DBNames) then<br> For I := 0 to VarArrayHighBound(DBNames, 1) Do<br> DatabaseName.Items.Add(DBNames[I]);<br> DatabaseNameClick(Self);<br> End;<br> 首先,把TDCOMConnection構(gòu)件的Connected屬性設(shè)為True,將連接應(yīng)用服務(wù)器。TDCOMConnection構(gòu)件的AppServer屬性將返回應(yīng)用服務(wù)器上數(shù)據(jù)模塊的接口,通過此接口就可以調(diào)用遠(yuǎn)程數(shù)據(jù)模塊的方法,例如GetDatabaseNames。GetDatabaseNames是在應(yīng)用服務(wù)器的數(shù)據(jù)模塊單元中定義的: <br> Function TAdHocQueryDemo.GetDatabaseNames: OleVariant;<br> var I: Integer;<br> DBNames: TStrings;<br> Begin<br> DBNames := TStringList.Create;<br> Try<br> Session1.GetDatabaseNames(DBNames);<br> Result := VarArrayCreate([0, DBNames.Count - 1], varOleStr);<br> For I := 0 to DBNames.Count - 1 DoResult[I] := DBNames[I];<br> FinallyDBNames.Free;<br> End;<br> End;<br> GetDatabaseNames函數(shù)的作用是返回一個數(shù)組,該數(shù)組由所有已定義的別名和BDE會話期對象專用的別名組成。現(xiàn)在我們回到客戶程序中,調(diào)用了數(shù)據(jù)模塊的GetDatabaseNames函數(shù)后,就把檢索到別名加到窗體右上角的組合框中,然后調(diào)用DatabaseNameClick函數(shù)。<br> Procedure TForm1.DatabaseNameClick(Sender: TObject);<br> var Password: string; <br> Begin<br> If DatabaseName.Text <> '' then<br> Begin<br> ClientData.Close;<br> Try<br> RemoteServer.AppServer.SetDatabaseName(DatabaseName.Text, '');<br> Except<br> On E: Exception DoIf E.Message = 'Password Required' then<br> Begin<br> If InputQuery(E.Message, 'Enter password', Password) then<br> RemoteServer.AppServer.SetDatabaseName(DatabaseName.Text, Password);<br> End <br> Else<br> Raise;<br> End;<br> End;<br> End;<br> 調(diào)用DatabaseNameClick的目的是使應(yīng)用服務(wù)器與另一個數(shù)據(jù)庫連接,這就需要通過AppServer屬性獲得數(shù)據(jù)模塊的接口,然后調(diào)用數(shù)據(jù)模塊單元的SetDatabaseName。SetDatabaseName是在應(yīng)用服務(wù)器的數(shù)據(jù)模塊單元中定義的: <br> Procedure TAdHocQueryDemo.SetDatabaseName(const DBName, Password: WideString);<br> Begin<br> Try<br> Database1.Close;<br> Database1.AliasName := DBName;<br> If Password <> '' then<br> Database1.Params.Values['PASSWORD'] := Password;<br> Database1.Open;<br> Except<br> {如果數(shù)據(jù)庫打開失敗,很可能是因為該數(shù)據(jù)庫需要口令}<br> On E: EDBEngineError DoIf (Password = '') then Raise Exception.Create('Password Required')Else<br> Raise;<br> End;<br> End;<br> SetDatabaseName的作用是修改TDatabase構(gòu)件的AliasName屬性,然后連接新的數(shù)據(jù)庫,如果失敗,就觸發(fā)一個異常。在客戶程序的DatabaseNameClick過程中,如果出現(xiàn)異常,就彈出一個輸入框,讓用戶輸入口令,然后再次調(diào)用數(shù)據(jù)模塊的SetDatabaseName。<br> 當(dāng)用戶在“Query”框中輸入了SQL語句,就可以單擊“Run Query”按鈕執(zhí)行這個查詢。問題是,只有應(yīng)用服務(wù)器才可以執(zhí)行查詢,那么客戶程序是怎樣把SQL語句傳遞給應(yīng)用服務(wù)器的呢?這就是本示范程序的關(guān)鍵之處。<br> Procedure TForm1.RunButtonClick(Sender: TObject);<br> Begin<br> ClientData.Close;<br> ClientData.Provider.DataRequest(SQL.Lines.Text);<br> ClientData.Open;<br> End;<br> 原來,客戶程序通過IProvider接口調(diào)用DataRequest把用戶輸入的SQL語句傳遞給應(yīng)用服務(wù)器。客戶程序通過IProvider接口調(diào)用DataRequest將在應(yīng)用服務(wù)器端觸發(fā)OnDataRequest事件,我們來看看應(yīng)用服務(wù)器是怎樣處理OnDataRequest事件的。<br> Function TAdHocQueryDemo.AdHocProviderDataRequest(Sender: TObject; Input: OleVariant): OleVariant;<br> Begin<br> AdHocQuery.SQL.Text := Input;<br> End;<br> 至此,一個動態(tài)傳遞SQL語句的示范程序剖析完畢,請讀者仔細(xì)琢磨其中的編程技巧。實際上,通過IProvider接口調(diào)用DataRequest可以傳遞任何信息。 <br> <b>14.4 一個全面演示TClientDataSet功能的示范程序</b><br> 這一節(jié)介紹一個演示TClientDataSet功能的示范程序,項目名稱叫Alchtest,它可以在C:\Program Files\Borland\Delphi4\Demos\Midas\Alchtest目錄中找到,主窗體如圖14.6所示。<br> 這個程序的總體思路是,用一個多頁控件讓用戶修改TClientDataSet的屬性或者調(diào)用它的方法,然后在下面的TAB控件中演示修改后的效果。<br> 程序首先在處理OnCreate事件的句柄中做了一些初始化的工作。<br> Procedure TDBClientTest.FormCreate(Sender: TObject);<br> var I: Integer;<br> Begin<br> Database1.Close;<br> FMaxErrors := -1;<br> FPacketRecs := -1;<br> SetCurrentDirectory(PChar(ExtractFilePath(ParamStr(0))));<br> For I := 0 to StatusBar.Panels.Count - 1 do <br> StatusBar.Panels[I].Text := '';<br> Application.OnIdle := ShowHeapStatus;<br> Application.OnHint := OnHint;<br> StreamSettings(False);<br> SetEventsVisible(ViewEvents.Checked); <br> End;<br> 其中,指定ShowHeapStatus作為處理應(yīng)用程序的OnIdle事件的句柄,指定OnHint作為處理應(yīng)用程序的OnHint事件的句柄。ShowHeapStatus是這樣定義的:<br> Procedure TDBClientTest.ShowHeapStatus(Sender: TObject; var Done: Boolean);<br> Begin<br> Caption := Format('Client DataSet Test Form - (Blocks=%d Bytes=%d)',[AllocMemCount, AllocMemSize]);<br> End;<br> ShowHeapStatus的作用是在應(yīng)用程序空閑的時候,在主窗口的標(biāo)題欄顯示堆的狀態(tài),其中,AllocMemCount是當(dāng)前分配的內(nèi)存塊數(shù),AllocMemSize是當(dāng)前分配的內(nèi)存總長度。<br> OnHint是這樣定義的:<br> Procedure TDBClientTest.OnHint(Sender: TObject);<br> Begin<br> StatusMsg := Application.Hint;<br> End;<br> StatusMsg是一個自定義的屬性,用于表達(dá)要在狀態(tài)欄上顯示的提示信息。<br> 在處理OnCreate事件的句柄中還調(diào)用了StreamSettings函數(shù)。StreamSettings是非常有用的,當(dāng)窗體關(guān)閉時,就調(diào)用StreamSettings把窗體上一些控件的狀態(tài)保存到一個配置文件中。當(dāng)窗體彈出時,就調(diào)用StreamSettings讀取配置文件以初始化窗體上的控件。<br> Procedure TDBClientTest.StreamSettings(Write: Boolean);<br> Procedure WriteStr(const OptName, Value: string);<br> Begin<br> FConfig.WriteString('Settings', OptName, Value);<br> End;<br> <br> Procedure WriteBool(const OptName: string; Value: Boolean);<br> Begin<br> FConfig.WriteBool('Settings', OptName, Value);<br> End;<br> <br> Function ReadStr(const OptName: string): string;<br> Begin<br> Result := FConfig.ReadString('Settings', OptName, '');<br> End;<br> <br> Function ReadBool(const OptName: string): Boolean;<br> Begin<br> Result := FConfig.ReadBool('Settings', OptName, False);<br> End; <br> <br> Function FindPage(const PageName: string): TTabSheet;<br> var I: Integer;<br> Begin<br> For I := AreaSelector.PageCount - 1 downto 0 do<br> Begin<br> Result := AreaSelector.Pages[I];<br> If Result.Caption = PageName then Exit;<br> End;<br> Result := ProviderPage;<br> End;<br> <br> Procedure ProcessComponents(Components: array of TComponent);<br> varI: Integer;<br> Begin<br> If Write then<br> Begin<br> For I := Low(Components) to High(Components) Do<br> If Components[I] is TCustomEdit then<br> With TEdit(Components[I]) do WriteStr(Name, Text)<br> Else if Components[I] is TComboBox then <br> With TDBComboBox(Components[I]) do WriteStr(Name, Text)<br> Else if Components[I] is TCheckBox then<br> With TCheckBox(Components[I]) do WriteBool(Name, Checked)<br> Else if Components[I] is TAction then<br> With TAction(Components[I]) do WriteBool(Name, Checked)<br> Else if Components[I] is TPageControl then<br> With TPageControl(Components[I]) doWriteStr(Name,ActivePage.Caption);<br> End;<br> Else<br> Begin<br> For I := Low(Components) to High(Components) do<br> If Components[I] is TCustomEdit then<br> With TEdit(Components[I]) do Text := ReadStr(Name)<br> Else if Components[I] is TComboBox then<br> With TComboBox(Components[I]) do Text := ReadStr(Name)<br> Else if Components[I] is TCheckBox then<br> With TCheckBox(Components[I]) do Checked := ReadBool(Name)<br> Else if Components[I] is TAction then<br> With TAction(Components[I]) do Checked := ReadBool(Name)<br> Else if Components[I] is TPageControl then <br> With TPageControl(Components[I]) doActivePage := FindPage(ReadStr(Name));<br> End;<br> End;<br> Begin<br> GetConfigFile;<br> If not Write and (ReadStr('AreaSelector') = '') then Exit;<br> <br> 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]);<br> End;<br> StreamSettings用Write參數(shù)來區(qū)分現(xiàn)在是要讀還是寫。StreamSettings中又嵌套了幾個過程和函數(shù),其中,WriteStr、WriteBool、ReadStr、ReadBool分別用于在配置文件中存取字符串和布爾類型的信息,F(xiàn)indPage函數(shù)搜索并返回一個特定的對象,而ProcessComponents則用于存取與具體構(gòu)件有關(guān)的信息。<br> GetConfigFile函數(shù)用于創(chuàng)建一個TIniFile對象的實例(如果還沒有創(chuàng)建的話)。<br> Function TDBClientTest.GetConfigFile: TIniFile;<br> Begin<br> If FConfig = nil Then<br> FConfig := TIniFile.Create(ChangeFileExt(ParamStr(0), '.INI'));<br> Result := FConfig;<br> End;<br> 請讀者注意StreamSettings是怎樣調(diào)用ProcessComponents函數(shù)的。ProcessComponents需要傳遞一個數(shù)組,數(shù)組中的元素就是窗體上的一些控件的名稱。<br> 我們先翻到“Provider”頁,看看怎樣指定數(shù)據(jù)庫和建立Master/Detail關(guān)系,如圖14.7所示。<br> 圖14.7 “Provider”頁<br> “Database”框用于指定要訪問的數(shù)據(jù)庫。當(dāng)用戶下拉此框時,將觸發(fā)OnDropDown事件。如果此時“Database”框還是空的話,就調(diào)用TSession的GetDatabaseNames函數(shù)把所有已定義的BDE別名和專用的別名填到“Database”框中。<br> Procedure TDBClientTest.DatabaseNameDropDown(Sender: TObject);<br> Begin<br> If DatabaseName.Items.Count = 0 then<br> Session.GetDatabaseNames(DatabaseName.Items);<br> End;<br> 當(dāng)用戶在“Database”框中選擇一個別名,將觸發(fā)OnClick事件。此時,就調(diào)用CheckDatabase連接另一個數(shù)據(jù)庫。由于數(shù)據(jù)庫已改變,“Master/DetailTables”框內(nèi)的內(nèi)容應(yīng)當(dāng)清掉。<br> Procedure TDBClientTest.DatabaseNameClick(Sender: TObject);<br> Begin<br> If (DatabaseName.Text <> '') and not DatabaseName.DroppedDown then<br> Begin<br> CheckDatabase(True);<br> MasterTableName.Items.Clear;<br> MasterTableName.Text := '';<br> DetailTableName.Text := '';<br> ClientData.Close;<br> End; <br> End;<br> 用戶也可以直接在“Database”框鍵入一個數(shù)據(jù)庫別名,然后按Enter鍵,此時將觸發(fā)OnKeyPress事件。<br> Procedure TDBClientTest.DatabaseNameKeyPress(Sender: TObject; var Key: Char);<br> Begin<br> If Key = #13 then<br> Begin<br> If DatabaseName.DroppedDown then<br> DatabaseName.DroppedDown := False;<br> DatabaseNameClick(Sender);Key := #0;<br> End;<br> End;<br> 好,現(xiàn)在讓我們看看CheckDatabase是怎樣定義的:<br> Procedure TDBClientTest.CheckDatabase(CloseFirst: Boolean);<br> var SPassword, SUserName: string;<br> Begin<br> If not CloseFirst and Database1.Connected and(Database1.AliasName = DatabaseName.Text) then Exit;<br> Database1.Close;<br> Database1.AliasName := DatabaseName.Text; <br> Session.GetAliasParams(Database1.AliasName, Database1.Params);<br> If Database1.Params.IndexOfName('PATH') = -1 then<br> Begin<br> SPassword := ConfigFile.ReadString('Passwords', Database1.AliasName, '');<br> If SPassword = '' then<br> Begin<br> SUserName := Database1.Params.Values['USER NAME'];<br> If not LoginDialog('DatabaseName.Text', SUserName, SPassword) then Exit;<br> Database1.Params.Values['USER NAME'] := SUserName;<br> End;<br> Database1.Params.Values['PASSWORD'] := SPassword;<br> End;<br> If EnableBCD.Checked then Database1.Params.Add('ENABLE BCD=TRUE');<br> Database1.Open;<br> If Database1.IsSQLBased and (SPassword <> '') thenConfigFile.WriteString('Passwords', Database1.AliasName, SPassword);<br> End;<br> CheckDatabase用于連接一個用戶指定的數(shù)據(jù)庫。如果當(dāng)前連接的就是用戶指定的數(shù)據(jù)庫,CheckDatabase就什么也不干。如果不是的話,首先要調(diào)用TDatabase構(gòu)件的Close斷開與數(shù)據(jù)庫的連接,然后把TDatabase構(gòu)件的AliasName 屬性設(shè)為用戶選擇的別名,并調(diào)用BDE會話期對象的GetAliasParams取出這個別名的參數(shù)。<br> 注意,對于本地數(shù)據(jù)庫來說,只有一個PATH參數(shù),而對于SQL數(shù)據(jù)庫來說,參數(shù)就有好幾個,因此,可以用有沒有PATH參數(shù)來區(qū)分本地數(shù)據(jù)庫和SQL數(shù)據(jù)庫。如果是SQL數(shù)據(jù)庫的話,就要設(shè)置USER NAME和PASSWORD參數(shù)給出用戶名和口令。如果“Settings”菜單上的“EnableBCD”命令被選中的話,就增加一個ENABLE BCD參數(shù),并把它的值設(shè)為TRUE。然后調(diào)用Open重新連接數(shù)據(jù)庫。<br> 這個程序還能夠讓客戶選擇“Master/Detail”關(guān)系中的Master表和Detail表,這是在“Master/Detail Tables”框中選擇的,其中,上面一個組合框用于選擇Master表,下面一個組合框用于選擇Detail表。當(dāng)用戶在組合框中選擇一個表,將觸發(fā)OnClick事件。<br> Procedure TDBClientTest.MasterTableNameClick(Sender: TObject);<br> Begin<br> With Sender as TComboBox Do<br> If not DroppedDown and (MasterTable.TableName <> Text) then OpenTable.Execute;<br> End;<br> 當(dāng)用戶下拉“Master/Detail Tables”框中的一個組合框,將觸發(fā)OnDropDown事件。此時就調(diào)用BDE會話期對象的GetTableNames把當(dāng)前數(shù)據(jù)庫中的所有表格的名稱填到組合框中,供用戶選擇。<br> Procedure TDBClientTest.MasterTableNameDropDown(Sender: TObject);<br> Begin<br> CheckDatabase(False);<br> With Sender as TComboBox do<br> If (Items.Count < 1) and (Database1.AliasName <> '') then<br> Session.GetTableNames(Database1.DatabaseName, '', True, False, Items);<br> End;<br> 用戶也可以直接在“Master/Detail Tables”框中的一個組合框內(nèi)鍵入一個表格的名稱,然后按Enter鍵,此時將觸發(fā)OnKeyPress事件。<br> Procedure TDBClientTest.MasterTableNameKeyPress(Sender: TObject; var Key: Char);<br> Begin<br> If Key = #13 then<br> Begin<br> With Sender as TComboBox Do<br> If DroppedDown then DroppedDown := False;<br> OpenTable.Execute;<br> Key := #0;<br> End;<br> End;<br> 注意:上面都是以選擇Master表的組合框為例的,實際上,選擇Detail表的操作完全一樣,代碼如下。 <br> Procedure TDBClientTest.DetailTableNameClick(Sender: TObject);<br> Begin<br> With Sender as TComboBox Do<br> If not DroppedDown and (DetailTable.TableName <> Text) then<br> OpenTable.Execute;<br> End;<br> 在上面幾個事件句柄中,OpenTable是一個動作列表,這是Delphi 4新增加的功能。在窗體上雙擊TActionList構(gòu)件,將打開一個如圖14.8所示的編輯器。<br> 圖14.8 動作列表編輯器<br> 在這個編輯器中找出OpenTable這個動作,然后在對象觀察器中可以發(fā)現(xiàn), 執(zhí)行這個動作的代碼是OpenTableExecute函數(shù)。<br> Procedure TDBClientTest.OpenTableExecute(Sender: TObject);<br> Begin<br> ClearEventLog.Execute;<br> If MasterTableName.Text <> '' then OpenDataSet(MasterTable);<br> End;<br> 而OpenDataSet是這樣定義的:<br> Procedure TDBClientTest.OpenDataSet(Source: TDBDataSet);<br> Begin<br> Screen.Cursor := crHourGlass;<br> Try<br> ClientData.Data := Null;<br> Source.Close;<br> If not DisableProvider.Checked then<br> Begin<br> BDEProvider.DataSet := Source;<br> SetProviderOptions;<br> ClientData.ProviderName := BDEProvider.Name;<br> ActiveDataSet := ClientData;<br> End<br> Else<br> ActiveDataSet := Source; <br> MasterGrid.SetFocus;<br> StatusMsg := 'Dataset Opened';<br> FinallyScreen.Cursor := crDefault;<br> End;<br> StreamSettings(True);<br> End;<br> OpenDataSet通過一個叫DisableProvider的復(fù)選框來決定是否使用TProvider構(gòu)件。如果沒有選中“Disable Provider”這個復(fù)選框,表示使用TProvider構(gòu)件,此時就把TProvider構(gòu)件的DataSet屬性設(shè)為MasterTable,然后調(diào)用SetProviderOptions來設(shè)置TProvider構(gòu)件的選項,接著設(shè)置TClientDataSet構(gòu)件的ProviderName屬性指定這個TProvider構(gòu)件,最后把ActiveDataSet變量設(shè)為此TClientDataSet構(gòu)件。如果用戶選中“Disable Provider”復(fù)選框,表示不使用TProvider構(gòu)件,此時就直接把ActiveDataSet設(shè)為MasterTable。<br> SetProviderOptions是這樣定義的:<br> Procedure TDBClientTest.SetProviderOptions;<br> var Opts: TProviderOptions;<br> Begin<br> Opts := [];If poDelayedDetails.Checked then<br> Include(Opts, poFetchDetailsOnDemand); <br> if poDelayedBlobs.Checked then Include(Opts, poFetchBlobsOnDemand);<br> if poCascadedDeletes.Checked then Include(Opts, poCascadeDeletes);<br> if poCascadedUpdates.Checked then Include(Opts, poCascadeUpdates);<br> if poReadOnly.Checked then Include(Opts, Provider.poReadOnly);<br> if poIncludeFieldProps.Checked then Include(Opts, poIncFieldProps);<br> BDEProvider.Options := Opts;<br> End;<br> SetProviderOptions實際上是根據(jù)“Settings”菜單上的“Provider Options”命令的一些子命令是否被選中來設(shè)置TProvider構(gòu)件的Options屬性。這個程序還可以讓用戶在“Master/Detail Queries”框中輸入SQL語句。當(dāng)用戶輸入了SQL語句并且按下Enter鍵,將觸發(fā)OnKeyPress事件。<br> Procedure TDBClientTest.MasterSQLKeyPress(Sender: TObject; var Key: Char);<br> Begin<br> If Key = #13 then<br> Begin<br> OpenQuery.Execute; <br> Key := #0;<br> End;<br> End;<br> 其中,OpenQuery也是一個動作,執(zhí)行它的是OpenQueryExecute函數(shù)。OpenQueryExecute是這樣定義的:<br> Procedure TDBClientTest.OpenQueryExecute(Sender: TObject);<br> Begin<br> If UpperCase(Copy(MasterSQL.Text, 1, 6)) = 'SELECT' then<br> OpenDataSet(MasterQuery)<br> Else <br> Begin<br> CheckDatabase(False);<br> MasterQuery.RequestLive := True;<br> MasterQuery.SQL.Text := MasterSQL.Text;<br> MasterQuery.ExecSQL;<br> StatusMsg := Format('%d rows were affected', [MasterQuery.RowsAffected]);<br> End;<br> Events.Items.<br> Begin<br> Update;<br> Try<br> Events.Clear;<br> Finally<br> Events.Items.EndUpdate; <br> End;<br> End;<br> OpenQueryExecute首先判斷用戶輸入的SQL語句是否為SELECT。如果是的話,就調(diào)用OpenDataSet執(zhí)行SELECT語句。如果不是的話,就調(diào)用ExecSQL執(zhí)行SQL語句。<br> 當(dāng)用戶翻到“Fields”頁,將觸發(fā)FieldsPage(TTabSheet對象)的OnShow事件,此時就把數(shù)據(jù)集中的字段和字段定義對象名稱分別顯示在兩個多行文本編輯器中,如圖14.9所示。<br> 圖14.9 “Fields”頁<br> Procedure TDBClientTest.FieldsPageShow(Sender: TObject);<br> Procedure WriteFullNames(Fields: TFields);<br> var I: Integer;<br> Begin <br> For I := 0 to Fields.Count - 1 Do<br> With Fields[I] Do<br> Begin<br> FieldList.Lines.Add(Format('%d) %s', [FieldNo, FullName]));<br> If Fields[I].DataType in [ftADT, ftArray] then<br> WriteFullNames(TObjectField(Fields[I]).Fields);<br> End;<br> End;<br> <br> Procedure WriteLists(DataSet: TDataSet);<br> var I: Integer;<br> Begin<br> FieldList.Clear;<br> For I := 0 to DataSet.FieldList.Count - 1 Do<br> With DataSet.FieldList Do<br> FieldList.Lines.Add(Format('%d) %s', [Fields[I].FieldNo, Strings[I]]));<br> FieldDefList.Clear;<br> DataSet.FieldDefs.Updated := False;<br> DataSet.FieldDefList.Update;<br> For I := 0 to DataSet.FieldDefList.Count - 1 Do<br> With DataSet.FieldDefList Do<br> FieldDefList.Lines.Add(Format('%d) %s', [FieldDefs[I].FieldNo, Strings[I]]));<br> End;<br> var DataSet: TDataSet;<br> Begin<br> DataSet := DBNavigator1.DataSource.DataSet;<br> If Assigned(DataSet) and DataSet.Active then<br> Begin<br> WriteLists(DataSet)<br> End<br> Else<br> Begin<br> CheckDatabase(False);<br> MasterTable.TableName := MasterTableName.Text;<br> WriteLists(MasterTable);<br> End;<br> End;<br> 首先要說明的是,F(xiàn)ieldsPageShow中嵌套了WriteFullNames,其實WriteFullNames完全是多余的。FieldsPageShow先獲取當(dāng)前的數(shù)據(jù)集。如果當(dāng)前的數(shù)據(jù)集已打開的話,就調(diào)用WriteLists顯示字段對象和字段定義對象的列表。如果當(dāng)前數(shù)據(jù)集沒有打開,就顯示MasterTable中的字段對象和字段定義對象的列表。當(dāng)用戶翻到“Indexes”頁,將觸發(fā)IndexPage(TTabSheet對象)的OnShow事件,此時就把當(dāng)前數(shù)據(jù)集中的索引列出來,用戶也可以創(chuàng)建新的索引或者刪除一個索引。“Indexes”頁如圖14.10所示。<br> 圖14.10 “Indexes”頁<br> Procedure TDBClientTest.IndexPageShow(Sender: TObject);<br> Begin<br> If not Assigned(ActiveDataSet) or not ActiveDataSet.Active then<br> OpenTable.Execute;<br> RefreshIndexNames(0);<br> End;<br> IndexPageShow首先檢查當(dāng)前是否打開了一個數(shù)據(jù)集,如果沒有,就執(zhí)行OpenTable的代碼即打開數(shù)據(jù)集,然后調(diào)用RefreshIndexNames函數(shù)列出所有的索引名稱。<br> Procedure TDBClientTest.RefreshIndexNames(NewItemIndex: Integer);<br> var I: Integer;<br> IndexDefs: TIndexDefs;<br> Begin<br> IndexList.Clear;<br> If ActiveDataSet = MasterTable then<br> IndexDefs := MasterTable.IndexDefs<br> Else<br> IndexDefs := ClientData.IndexDefs;<br> IndexDefs.Update;<br> For I := 0 to IndexDefs.Count - 1 Do<br> If IndexDefs[I].Name = '' then IndexList.Items.Add('<primary>')<br> Else<br> IndexList.Items.Add(IndexDefs[I].Name);<br> If IndexList.Items.Count > 0 then<br> Begin<br> If NewItemIndex < IndexList.Items.Count then<br> IndexList.ItemIndex := NewItemIndex<br> ElseIndexList.ItemIndex := 0;<br> ShowIndexParams;<br> End;<br> End;<br> RefreshIndexNames又調(diào)用ShowIndexParams檢索索引的選項,用這些選項來初始化“Indexes”頁上的幾個編輯框和復(fù)選框。<br> Procedure TDBClientTest.ShowIndexParams;varIndexDef: TIndexDef;<br> Begin<br> If ActiveDataSet = MasterTable then<br> IndexDef := MasterTable.IndexDefs[IndexList.ItemIndex]<br> Else<br> IndexDef := ClientData.IndexDefs[IndexList.ItemIndex];<br> 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;<br> DescFields.Text := IndexDef.DescFields;<br> CaseInsFields.Text := IndexDef.CaseInsFields;<br> End;<br> 如果用戶在列表框中選擇了另一個索引,就應(yīng)當(dāng)相應(yīng)地刷新這些選項。Procedure TDBClientTest.IndexListClick(Sender: TObject);<br> Begin <br> If ActiveDataSet = MasterTable then<br> MasterTable.IndexName := MasterTable.IndexDefs[IndexList.ItemIndex].Name<br> Else<br> ClientData.IndexName := ClientData.IndexDefs[IndexList.ItemIndex].Name;<br> ShowIndexParams;<br> End;<br> 如果要創(chuàng)建一個新的索引,用戶必須事先設(shè)置索引的選項,然后單擊“CreateIndex”按鈕。<br> Procedure TDBClientTest.CreateIndexClick(Sender: TObject);<br> var IndexName: string;Options: TIndexOptions;<br> Begin<br> IndexName := Format('Index%d', [IndexList.Items.Count+1]);<br> If InputQuery('Create Index', 'Enter IndexName:', IndexName) then<br> Begin<br> Options := [];<br> If idxCaseInsensitive.Checked then Include(Options, ixCaseInsensitive);<br> If idxDescending.Checked then Include(Options, ixDescending);<br> If idxUnique.Checked then Include(Options, ixUnique);<br> If idxPrimary.Checked then Include(Options, ixPrimary);<br> If ActiveDataSet = MasterTable then<br> Begin <br> MasterTable.Close;<br> MasterTable.AddIndex(IndexName,IndexFields.Text,Options,DescFields.Text);<br> MasterTable.Open;<br> End<br> Else<br> ClientData.AddIndex(IndexName, IndexFields.Text, Options,DescFields.Text, CaseInsFields.Text);<br> StatusMsg := 'Index Created';<br> RefreshIndexNames(IndexList.Items.Count);<br> End;<br> End;<br> CreateIndexClick首先彈出一個輸入框,讓用戶輸入索引名稱,然后根據(jù)用戶設(shè)置的選項來設(shè)置索引的Options屬性。<br> 在調(diào)用AddIndex之前,首先要區(qū)分當(dāng)前的數(shù)據(jù)集是MasterTable還是ClientData,為什么要區(qū)分MasterTable和ClientData呢?因為對于一般的數(shù)據(jù)集構(gòu)件來說,在創(chuàng)建索引之前必須先關(guān)閉數(shù)據(jù)集,而對于TClientDataSet構(gòu)件來說,則不必先關(guān)閉數(shù)據(jù)集。<br> 用戶也可以先選擇一個索引,然后單擊“Delete Index”按鈕刪除這個索引。<br> Procedure TDBClientTest.DeleteIndexClick(Sender: TObject);<br> Begin<br> If IndexList.ItemIndex > -1 then <br> If ActiveDataSet = MasterTable then<br> Begin<br> MasterTable.Close;<br> MasterTable.DeleteIndex(MasterTable.IndexDefs[IndexList.ItemIndex].Name);<br> MasterTable.Open;<br> End<br> Else<br> ClientData.DeleteIndex(ClientData.IndexDefs[IndexList.ItemIndex].Name);<br> End;<br> 與調(diào)用AddIndex一樣,在調(diào)用DeleteIndex之前,首先要區(qū)分當(dāng)前的數(shù)據(jù)集是MasterTable還是ClientData。當(dāng)用戶翻到“Filters”頁,就可以設(shè)置過濾條件,如圖14.11所示。<br> 圖14.11 “Filters”頁<br> 當(dāng)“Filters”頁剛剛打開的時候,將觸發(fā)OnShow事件,這樣就可以初始化“Filter”框。這里運(yùn)用了一個編程技巧,先從下面的柵格中取出一個字段,然后判斷這個字段的數(shù)據(jù)類型是不是ftString、ftMemo或ftFixedChar中的一種,如果是的話,過濾條件表達(dá)式的運(yùn)算符后面的值要用引號括起來。<br> Procedure TDBClientTest.FilterPageShow(Sender: TObject);<br> var Field: TField;LocValue,QuoteChar: String;<br> Begin<br> If (Filter.Text = '') and Assigned(ActiveDataSet) and ActiveDataSet.Active then<br> Begin<br> Field := MasterGrid.SelectedField;If Field = nil then Exit;<br> With ActiveDataSet DoTryDisableControls;<br> MoveBy(3);<br> LocValue := Field.Value;<br> First;<br> Finally<br> EnableControls;<br> End;<br> If Field.DataType in [ftString, ftMemo, ftFixedChar] then <br> QuoteChar := '''' <br> Else QuoteChar := '';<br> Filter.Text := Format('%s=%s%s%1:s', [Field.FullName, QuoteChar, LocValue]);<br> End;<br> End;<br> 用戶可以在“Filter”框內(nèi)鍵入新的過濾條件,當(dāng)用戶按下Enter鍵或把輸入焦點(diǎn)移走,就會把用戶輸入的過濾條件表達(dá)式賦給當(dāng)前數(shù)據(jù)集的Filter屬性。當(dāng)用戶翻到“FindKey”頁,就可以輸入一個鍵值,然后在數(shù)據(jù)集中搜索特定的記錄,如圖14.12所示。<br> 圖14.12 “FindKey”頁<br> 當(dāng)用戶單擊“Find Key”或“Find Nearest”按鈕,就開始搜索特定的記錄。<br> Procedure TDBClientTest.FindKeyClick(Sender: TObject);<br> Begin<br> If ActiveDataSet = ClientData then <br> With ClientData Do<br> Begin<br> SetKey;IndexFields[0].AsString := FindValue.Text;<br> KeyExclusive := Self.KeyExclusive.Checked;If FindPartial.Checked then KeyFieldCount := 0;<br> If Sender = Self.FindNearest then GotoNearest else<br> If not GotoKey then StatusMsg := 'Not found';<br> End<br> Else <br> if ActiveDataSet = MasterTable then<br> With MasterTable Do<br> Begin<br> SetKey;<br> IndexFields[0].AsString := FindValue.Text;<br> KeyExclusive := Self.KeyExclusive.Checked;<br> If FindPartial.Checked then KeyFieldCount := 0;<br> If Sender = Self.FindNearest then GotoNearest<br> Else <br> if GotoKey thenStatusMsg := 'Record Found'<br> Else StatusMsg := 'Not found';<br> End; <br> End;<br> 首先,要區(qū)分當(dāng)前數(shù)據(jù)集是ClientData還是MasterTable,調(diào)用SetKey使數(shù)據(jù)集進(jìn)入dsSetKey狀態(tài),把用戶輸入的鍵值賦給索引中的第一個字段。然后根據(jù)Sender參數(shù)判斷用戶按下的是“Find Key”按鈕還是“Find Nearest”按鈕,如果是后者,就調(diào)用GotoNearest,如果是前者,就調(diào)用GotoKey,最后根據(jù)GotoKey的返回值顯示有關(guān)信息。<br> 當(dāng)用戶翻到“Locate”頁,將觸發(fā)LocatePage(TTabSheet對象)的OnShow事件,程序就把下面的柵格中選擇的字段作為關(guān)鍵字段。“Locate”頁如圖14.13所示。<br> 圖14.13 “Locate”頁<br> Procedure TDBClientTest.LocatePageShow(Sender: TObject);<br> var Field: TField;<br> Begin<br> If (ActiveDataSet <> nil) and ActiveDataSet.Active then<br> BeginField := MasterGrid.SelectedField;<br> If LocateField.Items.Count = 0 then<br> LocateFieldDropDown(LocateField);<br> If (LocateField.Text = '')or(LocateField.Items.IndexOf(Field.FieldName) < 1) then<br> LocateField.Text := Field.FieldName;<br> With ActiveDataSet Do<br> Try<br> DisableControls;<br> MoveBy(3);<br> LocateEdit.Text := Field.Value;<br> First;<br> Finally<br> EnableControls;<br> End;<br> End;<br> End;<br> 用戶也可以在“Field”框選擇一個關(guān)鍵字段。當(dāng)用戶下拉“Field”框時,觸發(fā)OnDropDown事件,這樣就可以把當(dāng)前數(shù)據(jù)集中的字段顯示到“Field”框中。 <br> Procedure TDBClientTest.LocateFieldDropDown(Sender: TObject);<br> Begin<br> ActiveDataSet.GetFieldNames(LocateField.Items);<br> End;<br> 當(dāng)用戶選擇了關(guān)鍵字段并且輸入了鍵值,就可以單擊“Locate”按鈕開始定位記錄。<br> Procedure TDBClientTest.LocateButtonClick(Sender: TObject);varOptions: TLocateOptions;LocateValue: Variant;<br> Begin<br> Options := [];<br> If locCaseInsensitive.Checked then Include(Options, loCaseInsensitive);<br> If locPartialKey.Checked then Include(Options, loPartialKey);<br> If LocateNull.Checked then LocateValue := Null<br> Else<br> LocateValue := LocateEdit.Text;<br> If ActiveDataSet.Locate(LocateField.Text, LocateValue, Options) then<br> StatusMsg := 'Record Found'<br> Else<br> StatusMsg := 'Not found';<br> End;<br> 前面幾行代碼主要是設(shè)置有關(guān)選項,其中,如果用戶選中“Null Value”復(fù)選框的話,就把鍵值設(shè)為Null。然后調(diào)用當(dāng)前數(shù)據(jù)集的Locate函數(shù)定位記錄,并根據(jù)Locate函數(shù)的返回值顯示相應(yīng)的信息。 <br> <b>14.6 一個登錄的示范程序</b><br> 這一節(jié)剖析一個登錄示范程序,它可以在C:\Program Files\Borland\Delphi4\Demos\Midas\Login目錄中找到。<br> 這個程序分為應(yīng)用服務(wù)器和客戶程序兩個部分。應(yīng)用服務(wù)器的主窗體上有一個列表框,用于記載曾經(jīng)登錄到應(yīng)用服務(wù)器上的用戶名,如圖14.16所示。 <br> 應(yīng)用服務(wù)器上的數(shù)據(jù)模塊如圖14.17所示。 <br> 數(shù)據(jù)模塊上只有一個TTable構(gòu)件,它的DatabaseName屬性設(shè)為DBDEMOS,TableName屬性設(shè)為COUNTRY。數(shù)據(jù)模塊上沒有TProvider構(gòu)件,由TTable構(gòu)件提供IProvider接口。<br> 這個數(shù)據(jù)模塊的實例方式設(shè)為ciMultiInstance,這意味著每當(dāng)一個客戶連接應(yīng)用服務(wù)器時,就會創(chuàng)建數(shù)據(jù)模塊的一個新的實例,當(dāng)客戶不再連接應(yīng)用服務(wù)器時,就刪除數(shù)據(jù)模塊的實例。因此,這個程序利用數(shù)據(jù)模塊的OnCreate事件做了一些初始化的工作,利用數(shù)據(jù)模塊的OnDestroy事件從列表框中刪除一個用戶名。<br> Procedure TLoginDemo.LoginDemoCreate(Sender: TObject);<br> Begin<br> FLoggedIn := False;<br> End;<br> 為什么要把FLoggedIn變量設(shè)為False呢?其原因后面將解釋。<br> Procedure TLoginDemo.LoginDemoDestroy(Sender: TObject);<br> Begin<br> With Form1.ListBox1.Items do Delete(IndexOf(FUserName));<br> End;<br> 編譯和運(yùn)行這個應(yīng)用服務(wù)器。打開客戶程序的項目,它的主窗體如圖14.18所示。<br> 窗體上的TDCOMConnection構(gòu)件用于連接應(yīng)用服務(wù)器,它的ServerName屬性設(shè)為Server.LoginDemo,它的LoginPrompt屬性設(shè)為True。窗體上的TClientDataSet構(gòu)件的RemoteServer屬性指定了TDCOMConnection構(gòu)件,它的ProviderName屬性設(shè)為Country。<br> 此外,窗體上有一個柵格用于顯示數(shù)據(jù)集中的數(shù)據(jù),還有一個“Open”按鈕用于打開數(shù)據(jù)集。 <br> 由于TDCOMConnection構(gòu)件的LoginPrompt屬性設(shè)為True,當(dāng)客戶程序試圖連接應(yīng)用服務(wù)器時就會彈出一個“Remote Login”對話框,要求用戶輸入用戶名和口令。登錄以后,就觸發(fā)OnLogin事件。在處理這個事件的句柄中,客戶程序通過AppServer屬性獲得數(shù)據(jù)模塊的接口,從而調(diào)用數(shù)據(jù)模塊的Login。<br> Procedure TForm1.DCOMConnection1Login(Sender: TObject; Username,Password: String);<br> Begin<br> DCOMConnection1.AppServer.Login(UserName, Password);<br> End;<br> 在應(yīng)用服務(wù)器的數(shù)據(jù)模塊單元中,Login是這樣定義的。<br> Procedure TLoginDemo.Login(const UserName, Password: WideString);<br> Begin<br> Form1.ListBox1.Items.Add(UserName);<br> FLoggedIn := True;<br> FUserName := UserName;<br> End;<br> Login把用戶名加到列表框中,然后把FLoggedIn變量設(shè)為True,表示用戶已登錄。當(dāng)用戶單擊“Open”按鈕,就調(diào)用TClientDataSet構(gòu)件的Open打開數(shù)據(jù)集。<br> Procedure TForm1.Button1Click(Sender: TObject);<br> Begin<br> ClientDataSet1.Open; <br> End;<br> <b>14.7 一個演示Master/Detail關(guān)系的示范程序</b><br> 這一節(jié)剖析一個演示Master/Detail關(guān)系的示范程序,它可以在C:\ProgramFiles\Borland\Delphi4\ Demos\Midas\Mstrdtl目錄中找到。<br> 這個程序分為應(yīng)用服務(wù)器和客戶程序兩個部分。應(yīng)用服務(wù)器有一個窗體,不過,這個窗體其實是多余的,如果不想顯示,可以打開應(yīng)用服務(wù)器的項目文件,加入這么一行:<br> Application.ShowMainForm := False;<br> 應(yīng)用服務(wù)器的數(shù)據(jù)模塊如圖14.19所示。 <br> 應(yīng)用服務(wù)器的數(shù)據(jù)模塊上有這么幾個構(gòu)件:<br> 名為Database的TDatabase構(gòu)件,其AliasName屬性設(shè)為IBLOCAL,并且定義了一個應(yīng)用程序?qū)S玫膭e名叫ProjectDB。其Params屬性提供了用戶名和口令。<br> 名為Project的TTable構(gòu)件,其DatabaseName屬性設(shè)為ProjectDB,它的TableName屬性設(shè)為PROJECT(注意:必須已運(yùn)行Interbase Server)。<br> 名為Employee的TQuery構(gòu)件,其DatabaseName屬性設(shè)為ProjectDB,它的SQL語句如下:Select * From EMPLOYEE_PROJECT E Where E.PROJ_ID= :PROJ_ID <br> 名為EmpProj的TQuery構(gòu)件,其DatabaseName屬性設(shè)為ProjectDB,它的SQL語句如下:Select EMP_NO,FULL_NAME From EMPLOYEE<br> 名為UpdateQuery的TQuery構(gòu)件,其DatabaseName屬性設(shè)為ProjectDB,它的SQL語句目前是空的。<br> 名為ProjectProvider的TProvider構(gòu)件,其DataSet屬性設(shè)為Project。<br> 名為ProjectSource的TDataSource構(gòu)件,其DataSet屬性設(shè)為Project。編譯并運(yùn)行應(yīng)用服務(wù)器。現(xiàn)在可以打開客戶程序的項目,它的數(shù)據(jù)模塊如圖14.20所示。<br> 圖14.20 數(shù)據(jù)模塊<br> 客戶程序的數(shù)據(jù)模塊上有這么幾個構(gòu)件:<br> 名為DCOMConnection的TDCOMConnection構(gòu)件,其ServerName屬性設(shè)為Serv.ProjectData。<br> 名為Project的TClientDataSet構(gòu)件,其RemoteServer屬性設(shè)為DCOMConnection它的ProviderName屬性設(shè)為ProjectProvider。并且建立了一個叫ProjectEmpProj的永久字段對象,它的類型是TDataSetField。與Project對應(yīng)的TDataSource構(gòu)件叫ProjectSource。<br> 名為Emp_Proj的TClientDataSet構(gòu)件,其RemoteServer屬性和ProviderName屬性都是空的,但它的DataSetField屬性設(shè)為叫ProjectEmpProj的字段對象,這就構(gòu)成了Master/Detail關(guān)系。與Emp_Proj對應(yīng)的TDataSource構(gòu)件叫EmpProjSource。<br> 名為Employee的TClientDataSet構(gòu)件,其RemoteServer屬性指定了TDCOMConnection構(gòu)件,但它的ProviderName屬性設(shè)為Employee。與Employee對應(yīng)的TDataSource構(gòu)件叫EmployeeSource。<br> 我們再來看客戶程序的主窗體,如圖14.21所示。<br> 左邊一個柵格只顯示Project數(shù)據(jù)集中的PROJ_NAME字段即項目名稱,“Product”框顯示Project數(shù)據(jù)集中的PRODUCT字段,“Description”框顯示Project數(shù)據(jù)集中的PROJ_DESC字段,并且用一個TDBNavigator構(gòu)件為Project數(shù)據(jù)集導(dǎo)航。 <br> 右下角的柵格顯示Emp_Proj數(shù)據(jù)集中一個叫EmployeeName的字段的值,這是個Lookup字段,它的LookupDataSet屬性設(shè)為Employee,它的LookupKeyField屬性設(shè)為EMP_NO,它的LookupResultField屬性設(shè)為FULL_NAME。當(dāng)用戶用導(dǎo)航器瀏覽Project數(shù)據(jù)集的記錄時,右下角的柵格就從Employee數(shù)據(jù)集中查找與EMP_NO字段匹配的記錄,并且顯示其中的FULL_NAME字段。<br> 由于右下角的柵格只建立了一個永久的列對象,因此,可以把這一列的寬度設(shè)為與柵格本身同寬,它是在處理窗體的OnCreate事件的句柄中進(jìn)行的。<br> Procedure TClientForm.FormCreate(Sender: TObject);<br> Begin<br> MemberGrid.Columns[0].Width :=MemberGrid.ClientWidth - GetSystemMetrics(SM_CXVSCROLL);<br> End;<br> 由于一個項目中不止一個雇員,為了醒目起見,可以把其中的負(fù)責(zé)人加粗顯示,這需要處理柵格的OnDrawColumnCell事件。<br> Procedure TClientForm.MemberGridDrawColumnCell(Sender: TObject; const Rect: TRect;DataCol: Integer;Column: TColumn;State: TGridDrawState);<br> Begin<br> If DM.ProjectTEAM_LEADER.Value = DM.Emp_ProjEMP_NO.Value then MemberGrid.Canvas.Font.Style := [fsBold];<br> MemberGrid.DefaultDrawColumnCell(Rect, DataCol, Column, State);<br> End;<br> 怎樣來判斷其中的負(fù)責(zé)人呢?在Project數(shù)據(jù)集中,有一個TEAM_LEADER 字段,它存儲的是項目負(fù)責(zé)人的雇員編號。在Emp_Proj數(shù)據(jù)集中,有一個EMP_NO,它存儲的也是雇員編號,如果這兩者相等,即表示該雇員是項目負(fù)責(zé)人。當(dāng)用戶單擊“Add”按鈕,就可以在柵格中增加一條記錄,即在項目中增加一個雇員。<br> Procedure TClientForm.AddBtnClick(Sender: TObject);<br> Begin<br> MemberGrid.SetFocus;<br> DM.Emp_Proj.Append;<br> MemberGrid.EditorMode := True;<br> End;<br> 由于柵格事先建立了一個永久的列對象,而該列對象的FieldName屬性指定了一個Lookup字段,所以,用戶可以從一個組合框中選擇一個值。<br> 當(dāng)用戶單擊“Delete”按鈕,就刪除當(dāng)前記錄,即一個雇員。<br> Procedure TClientForm.DeleteBtnClick(Sender: TObject);<br> Begin<br> DM.Emp_Proj.Delete;<br> End;<br> 當(dāng)用戶先選擇其中一個雇員,然后單擊“Leader”按鈕,就把該雇員設(shè)為項目負(fù)責(zé)人。<br> Procedure TClientForm.LeaderBtnClick(Sender: TObject); <br> var NewLeader: Integer;<br> Begin<br> NewLeader := DM.Emp_ProjEMP_NO.Value;<br> If not (DM.Project.State in dsEditModes) then DM.Project.Edit;<br> DM.ProjectTEAM_LEADER.Value := NewLeader;<br> MemberGrid.Refresh;<br> End;<br> 增加、刪除或修改了記錄后,用戶應(yīng)當(dāng)單擊“Apply Update”按鈕更新數(shù)據(jù)庫。<br> Procedure TClientForm.ApplyUpdatesBtnClick(Sender: TObject);<br> Begin<br> DM.ApplyUpdates;<br> End;<br> 在數(shù)據(jù)模塊的單元中,ApplyUpdates是這樣定義的:<br> Procedure TDM.ApplyUpdates;<br> Begin<br> If Project.ApplyUpdates(0) = 0 then Project.Refresh;<br> End;<br> 可以看出,數(shù)據(jù)模塊的ApplyUpdates又調(diào)用了TClientDataSet構(gòu)件的ApplyUpdates,并且把MaxErrors參數(shù)設(shè)為0,這樣,只要應(yīng)用服務(wù)器發(fā)現(xiàn)有一個錯誤的記錄,更新就停止。<br> 當(dāng)用戶在左邊的柵格中試圖增加一個新的項目時,會觸發(fā)TClientDataSet構(gòu)件的OnNewRecord事件。由于這個柵格只顯示了PROJ_NAME字段,用戶不能直接輸入PROJ_ID字段的值,因此,程序在處理OnNewRecord事件的句柄中推出一個輸入框,讓用戶輸入PROJ_ID字段的值。如果用戶輸入的字符超過了該字段允許的長度,就觸發(fā)一個異常。<br> 如果用戶沒有輸入任何字符,也觸發(fā)一個異常。<br> Procedure TDM.ProjectNewRecord(DataSet: TDataSet);<br> va rValue: String;<br> Begin<br> If InputQuery('Project ID','Enter Project ID:',Value) then<br> Begin<br> If Length(Value) > ProjectPROJ_ID.Size then<br> Raise Exception.CreateFmt('Project ID can only be %d characters',[ProjectPROJ_ID.Size]);If Length(Value) = 0 then<br> Raise Exception.Create('Project ID is required');<br> End<br> Else<br> SysUtils.Abort;<br> ProjectPROJ_ID.Value := Value;<br> End;<br> 由于Project數(shù)據(jù)集與Employee數(shù)據(jù)集之間存在著Master/Detail關(guān)系,當(dāng)刪除Project數(shù)據(jù)集的一條記錄時,應(yīng)當(dāng)先刪除Employee數(shù)據(jù)集中關(guān)聯(lián)的記錄。應(yīng)用服務(wù)器利用TProvider構(gòu)件的BeforeUpdateRecord事件實現(xiàn)了這一點(diǎn)。<br> Procedure TProjectData.ProjectProviderBeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet;DeltaDS: TClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);<br> Const DeleteQuery = 'Delete From EMPLOYEE_PROJECT where PROJ_ID = :ProjID';<br> Begin<br> If (UpdateKind = ukDelete) and (SourceDS = Project) then<br> Begin<br> UpdateQuery.SQL.Text := DeleteQuery;<br> UpdateQuery.Params[0].AsString := DeltaDS.FieldByName('PROJ_ID').AsString;<br> UpdateQuery.ExecSQL;<br> End;<br> End; <br> <b>14.9 一個動態(tài)設(shè)置查詢參數(shù)的示范程序</b><br> 這一節(jié)剖析一個動態(tài)設(shè)置查詢參數(shù)的示范程序,它可以在C:\ProgramFiles\Borland\Delphi4\ Demos\ Midas\Setparam目錄中找到。<br> 這個程序分為應(yīng)用服務(wù)器和客戶程序兩個部分。當(dāng)客戶程序通過TClientDataSet構(gòu)件的Params屬性設(shè)置參數(shù)時,這些參數(shù)會自動地傳遞給應(yīng)用服務(wù)器上的TQuery構(gòu)件,這樣就能夠根據(jù)用戶的要求來查詢數(shù)據(jù)庫,這就是本示范程序的基本思路。<br> 我們來剖析應(yīng)用服務(wù)器,先看它的數(shù)據(jù)模塊,如圖14.24所示。圖14.24 數(shù)據(jù)模塊數(shù)據(jù)模塊上只有一個TQuery構(gòu)件,它的DatabaseName屬性設(shè)為DBDEMOS,它的SQL語句如下:<br> Select * From EventsWhere Event_Date >= :Start_Date and Event_Date <= :End_Date Order by Event_Date<br> 可以看出,這個SQL語句中有兩個參數(shù),一個是:Start_Date,另一個是:End_Date。<br> 現(xiàn)在我們暫時不管數(shù)據(jù)模塊,再來看看應(yīng)用服務(wù)器的主窗體,如圖14.25所示。<br> 圖14.25 應(yīng)用服務(wù)器的主窗體<br> 主窗體上顯示兩個計數(shù),一個是當(dāng)前連接應(yīng)用服務(wù)器的客戶數(shù)(Clients),另一個是已經(jīng)執(zhí)行的查詢次數(shù)(Queries)。用什么來判斷當(dāng)前的客戶數(shù),這與數(shù)據(jù)模塊的實例方式有關(guān)。我們可以回到數(shù)據(jù)模塊的單元,看看它的初始化代碼:<br> Initialization<br> TComponentFactory.Create(ComServer, TSetParamDemo,<br> Class_SetParamDemo, ciMultiInstance);<br> End.<br> 可以看出,這個數(shù)據(jù)模塊的實例方式設(shè)為ciMultiInstance,表示每當(dāng)有一個客戶連接應(yīng)用服務(wù)器,就會創(chuàng)建數(shù)據(jù)模塊的一個新的實例。因此,數(shù)據(jù)模塊的實例數(shù)就是當(dāng)前的客戶數(shù)。怎樣統(tǒng)計數(shù)據(jù)模塊的實例數(shù)呢?很簡單,只要處理數(shù)據(jù)模塊的OnCreate事件。<br> Procedure TSetParamDemo.SetParamDemoCreate(Sender: TObject);<br> Begin<br> MainForm.UpdateClientCount(1);<br> End;<br> 當(dāng)一個客戶退出連接,將刪除一個數(shù)據(jù)模塊的實例,此時將觸發(fā)數(shù)據(jù)模塊的OnDestroy事件:<br> Procedure TSetParamDemo.SetParamDemoCreate(Sender: TObject);<br> Begin<br> MainForm.UpdateClientCount(1);<br> End;<br> 其中,UpdateClientCount是在主窗體的單元中定義的:<br> Procedure TMainForm.UpdateClientCount(Incr: Integer);<br> Begin<br> FClientCount := FClientCount + Incr;<br> ClientCount.Caption := IntToStr(FClientCount); <br> End;<br> 請注意Incr參數(shù)的作用。怎樣統(tǒng)計已經(jīng)執(zhí)行過的查詢數(shù)呢?也很簡單,只要統(tǒng)計TQuery構(gòu)件被激活的次數(shù)就可以了。因此,程序處理了TQuery構(gòu)件的AfterOpen事件。<br> Procedure TSetParamDemo.EventsAfterOpen(DataSet: TDataSet);<br> Begin<br> MainForm.IncQueryCount;<br> End;<br> IncQueryCount是在主窗體的單元中定義的:<br> Procedure TMainForm.IncQueryCount;<br> Begin<br> Inc(FQueryCount);<br> QueryCount.Caption := IntToStr(FQueryCount);<br> End;<br> 編譯和運(yùn)行這個應(yīng)用服務(wù)器。打開客戶程序的項目,它的主窗體如圖14.26所示。 <br> 窗體上有一個TDCOMConnection構(gòu)件用于連接應(yīng)用服務(wù)器,有一個叫Events的TClientDataSet構(gòu)件,用于引入數(shù)據(jù)集。<br> “Starting Date”框用于輸入:Start_Date參數(shù)的值,<br> “Ending Date”框用于輸入:End_Date參數(shù)的值。中間的柵格用于顯示查詢的結(jié)果。“Description”框用于顯示Event_Description字段的值。“Photo”框用于顯示Event_Photo字段的值。<br> 客戶程序在處理窗體的OnCreate事件的句柄中對“Starting Date”框和“EndingDate”框進(jìn)行初始化。<br> Procedure TForm1.FormCreate(Sender: TObject); <br> Begin<br> StartDate.Text := DateToStr(EncodeDate(96, 6, 19));<br> EndDate.Text := DateToStr(EncodeDate(96, 6, 21));<br> End;<br> 用戶可以在這兩個框中重新輸入其他日期,然后單擊“Show Events”按鈕。<br> Procedure TForm1.ShowEventsClick(Sender: TObject);<br> Begin<br> Events.Close;<br> Events.Params.ParamByName('Start_Date').AsDateTime:=StrToDateTime(StartDate.Text);Events.Params.ParamByName('End_Date').AsDateTime :=StrToDateTime(EndDate.Text);<br> Events.Open;<br> End;<br> 首先,要調(diào)用TClientDataset構(gòu)件的Close關(guān)閉數(shù)據(jù)集,然后分別設(shè)置Start_Date參數(shù)和End_Date參數(shù)的值,最后,調(diào)用TClientDataset構(gòu)件的Open打開數(shù)據(jù)集,此時,這兩個參數(shù)就被自動傳遞給應(yīng)用服務(wù)器上的TQuery構(gòu)件。</body></html>
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -