亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频

? 歡迎來到蟲蟲下載站! | ?? 資源下載 ?? 資源專輯 ?? 關(guān)于我們
? 蟲蟲下載站

?? core14.htm

?? Delphi4核心編程技術(shù)
?? HTM
?? 第 1 頁 / 共 4 頁
字號:
<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 + -
亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频
综合久久综合久久| 亚洲国产美女搞黄色| 中文字幕一区二区在线观看 | 一区二区不卡在线播放 | 91理论电影在线观看| 欧美tickling网站挠脚心| 一二三区精品福利视频| 成人av电影免费在线播放| 日韩欧美一二区| 天天操天天干天天综合网| 91首页免费视频| 国产欧美一区二区三区在线看蜜臀| 五月开心婷婷久久| 在线观看www91| 亚洲精品一卡二卡| 成人污污视频在线观看| 精品国产乱码久久久久久老虎| 午夜视频一区二区三区| 在线一区二区观看| 樱花草国产18久久久久| thepron国产精品| 国产精品免费视频一区| 91福利视频网站| 亚洲毛片av在线| 色欧美乱欧美15图片| 亚洲天堂精品视频| 99re6这里只有精品视频在线观看| 久久综合久色欧美综合狠狠| 另类的小说在线视频另类成人小视频在线| 欧美日韩三级一区| 午夜电影一区二区| 欧美一卡二卡三卡| 青青青伊人色综合久久| 日韩精品资源二区在线| 久草热8精品视频在线观看| 精品少妇一区二区三区| 国产制服丝袜一区| 国产精品久久久久久久久免费相片| 国产精品123| 中文字幕一区二区三区四区不卡| 99九九99九九九视频精品| 亚洲人成精品久久久久| 色婷婷香蕉在线一区二区| 亚洲综合一区二区三区| 欧美老年两性高潮| 麻豆91在线播放免费| 国产亚洲人成网站| jlzzjlzz欧美大全| 亚洲成人综合视频| 日韩欧美一区中文| 国产成人免费高清| 亚洲欧美色综合| 欧美丰满美乳xxx高潮www| 久久国产精品一区二区| 国产亚洲欧美日韩日本| 91女人视频在线观看| 亚洲超碰97人人做人人爱| 欧美mv日韩mv国产| 99精品偷自拍| 日韩国产精品91| 国产色产综合产在线视频 | 国产伦精品一区二区三区在线观看| 国产午夜亚洲精品午夜鲁丝片 | 成人欧美一区二区三区小说| 精品视频1区2区| 国产一区二区剧情av在线| 中文字幕制服丝袜成人av| 在线不卡一区二区| 国产福利91精品一区二区三区| 一区二区三区四区在线| 精品国产髙清在线看国产毛片| a级精品国产片在线观看| 日韩精彩视频在线观看| 国产精品盗摄一区二区三区| 欧美日韩视频在线观看一区二区三区| 激情综合网最新| 亚洲一区二区在线免费观看视频| 精品少妇一区二区三区日产乱码| 91麻豆精品一区二区三区| 久久99国产精品免费| 亚洲激情欧美激情| 国产欧美中文在线| 777xxx欧美| 在线观看日韩电影| 99久久综合99久久综合网站| 青青青伊人色综合久久| 一级精品视频在线观看宜春院| 久久久五月婷婷| 欧美一区二区三区在线观看 | 日本一区二区三区免费乱视频| 欧美日韩另类一区| 99精品视频一区二区三区| 麻豆一区二区99久久久久| 亚洲观看高清完整版在线观看| 欧美国产综合一区二区| 欧美刺激脚交jootjob| 欧美体内she精高潮| 91在线国产福利| 成人一级视频在线观看| 国产一区二区网址| 精品一区精品二区高清| 日本欧美韩国一区三区| 五月天婷婷综合| 亚洲一线二线三线久久久| 国产精品久久久久精k8| 欧美国产一区在线| 国产色91在线| 日本一区二区在线不卡| 国产日产精品1区| 国产日韩欧美亚洲| 亚洲国产经典视频| 欧美激情在线看| 国产精品毛片高清在线完整版| 久久蜜桃av一区二区天堂| 精品国产成人系列| 精品动漫一区二区三区在线观看| 日韩精品中文字幕一区二区三区| 日韩一区二区视频在线观看| 欧美老肥妇做.爰bbww| 91精品中文字幕一区二区三区| 欧美精品亚洲二区| 日韩精品在线网站| 久久久精品tv| 国产精品高潮久久久久无| 亚洲卡通欧美制服中文| 亚洲一区在线免费观看| 亚洲成a天堂v人片| 蜜臀av一级做a爰片久久| 国产综合成人久久大片91| 国产99精品视频| 色香蕉久久蜜桃| 日韩一区二区三区视频| 久久人人97超碰com| 中文字幕一区二区三区四区 | 国产精品久久久久久久久免费相片 | 欧美一二区视频| 久久婷婷色综合| 亚洲私人黄色宅男| 亚洲国产美女搞黄色| 久久精品国产免费看久久精品| 国产999精品久久| 欧美偷拍一区二区| 精品国产百合女同互慰| 国产精品传媒视频| 日本大胆欧美人术艺术动态| 国产电影一区在线| 欧美在线免费观看亚洲| 精品国产免费一区二区三区香蕉| 欧美韩国日本不卡| 视频一区在线播放| 成人开心网精品视频| 欧美三级视频在线观看| 久久久91精品国产一区二区精品 | 国产麻豆精品一区二区| 久久男人中文字幕资源站| 亚洲天堂免费在线观看视频| 免费成人av在线播放| 91在线精品一区二区三区| 日韩欧美国产一区二区三区 | 18成人在线观看| 久久精品国产99国产| 色一区在线观看| 久久夜色精品国产噜噜av| 亚洲电影视频在线| 91亚洲男人天堂| 久久欧美一区二区| 日韩国产欧美一区二区三区| 91女厕偷拍女厕偷拍高清| 国产性色一区二区| 麻豆国产一区二区| 欧美人xxxx| 亚洲女人的天堂| 99在线热播精品免费| 久久久美女毛片| 经典三级一区二区| 欧美一区二区三区性视频| 亚洲午夜免费电影| 91色porny| 日韩伦理电影网| 不卡视频免费播放| 久久精品人人做人人综合| 麻豆成人久久精品二区三区红 | 91小视频在线观看| 国产精品久久久久久久久晋中 | 91亚洲永久精品| 国产精品成人免费| 成人avav在线| 国产欧美精品一区二区色综合| 久久精品久久久精品美女| 91精品欧美一区二区三区综合在| 亚洲国产视频直播| 色8久久精品久久久久久蜜| 中文字幕日本不卡| 成人黄色小视频| 国产精品久久久久影院色老大| 国产99久久精品| 国产精品久久毛片av大全日韩| 成人综合激情网| 亚洲少妇30p| 在线观看视频91|