ection构件,它的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。编译并运行应用服务器。现在可以打开客户程序的项目,它的数据模块如图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参数的值。中间的栅格用于显示查询的结果。“Description”框用于显示Event_Description字段的值。“Photo”框用于显示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构件。
上一页 [1] [2] [3] [4] |