怎么样通过DB2表为Delphi产生动态的数据输入窗体?

怎么样通过DB2表为Delphi产生动态的数据输入窗体?

本文检查了 IBM DB2 Universal Database 的元数据,以便动态构建视图和窗体,其中包括如何动态生成新的 CLX 窗体,以及将窗体流化(stream)为 Delphi .pas 和 .xfm 文件,并准备将它们添加到 Kylix 和 Delphi CLX 项目中。

简介
在 上一篇文章中,我检查了 IBM® DB2® Universal Database™(UDB)的元数据,以便动态构建视图和窗体。我使用了 Linux 上的 Borland® Kylix™ 3 和 dbExpress 数据访问驱动程序,来分析 IBM DB2 UDB 数据库表、字段(名称和类型),允许用户选择特定的表,来回切换应该显示的字段,以及在数据网格(datagrid)和单个数据感知控件中动态查看输出。

这次,我们将扩展该方法,以允许用户指定更加复杂的查询(例如 JOIN)。作为附加步骤,我们将产生一个单独的窗体,并以 .pas 和 .xfm 源格式保存它,因此,可以将结果窗体添加至 Borland Delphi™ 或 Kylix 项目中。这类似于原来的基于 BDE(且仅用于 Windows)的数据库-窗体向导,但这次产生的是一个跨平台的 CLX 窗体。

从 Linux 进行迁移
上次,我完成了一个在 Linux 上运行的 Kylix 3 项目。这次,我首先需要将该项目迁移到 Windows 上,并在那里进行扩展(用以说明该项目和由此生成的 CLX 窗体是跨平台的,既可以用于 Linux 上的 Kylix,也可用于 Windows 上的 Delphi)。

将该项目从 Kylix 迁移到 Delphi 仅涉及一项更改:LibraryName 和 VendorLib 的 TSQLConnection 属性在 Linux 上被设置为 libsqldb2.so 和 libdb2.so,但在 Windows 上,该属性必须设为 dbexpdb2.dll 和 db2cli.dll。关于如何创建跨平台项目的指导,请参阅 大转变:利用 Kylix 3 从 Windows 迁移到 Linux。

添加 SQL 功能
一旦可以在 Delphi 7 中重新打开该项目,您就可以对页面控制的 Meta Data 选项卡进行一些增强。具体地说,您需要允许用户输入 SQL 语句,而不是只能从可用表的列表中选择表名。图 1 展示了新的用户界面(显示了 EMP_PHOTO 表的字段),您可以使用左下方的 memo 字段输入 SQL 语句。

图 1. 用于 DB2 的新动态窗体
怎么样通过DB2表为Delphi产生动态的数据输入窗体?

就在 TMemo 控件的正上方,我放置了文本 SELECT * FROM,作为小小的一个提示:用户只需指定表名,以及下面可选的 where子句。可以用代码来构造 SQL 语句,检索元数据(metadata)来获取字段名,并将这些字段名置于 TCheckListBox 中,该代码如下所示:

procedure TDBWizForm.QCheckBoxClick(Sender: TObject);

var i: Integer;

begin

if QCheckBox.Checked then begin ClientDataSet1.Active := False;

SQLDataSet1.CommandType := ctQuery;

SQLDataSet1.CommandText := 'select * from' + QMemo.Text;

SQLDataSet1.FieldDefs.Update; // get meta information

CheckListBox1.Clear;

for i:=0 to Pred(SQLDataSet1.FieldDefs.Count) do

CheckListBox1.Items.Add(SQLDataSet1.FieldDefs[i].Name)

end

end;

作为一个实际示例,我输入了一条 SQL 语句,以连接 EMPLOYEE 和 EMP_PHOTO 表,该操作是基于 EMPNO 字段的。因为已经给定了 SELECT * FROM,所以我只需要在查询 memo 字段中输入 EMP_PHOTO, EMPLOYEE WHERE EMP_PHOTO.EMPNO = EMPLOYEE.EMPNO,然后启用 Select fields from query复选框,这将执行以上代码,产生 TCheckListBox 中的字段列表。

图 2. 从查询中选择字段
怎么样通过DB2表为Delphi产生动态的数据输入窗体?

请注意,EMPNO 字段出现了两次:一次是在 EMP_PHOTO 表中(称作 EMPNO),一次是在 EMPLOYEE 表中(然后自动称作 EMPNO_1)。

在上面的实例中,通过左下方所指定的 where子句,我从查询中选择了 PICTURE、FIRSTNME、LASTNAME、PHONENO、HIREDATE 和 SEX 字段。

为了给 DBGrid 和其他数据感知控件提供真正的数据,您需要添加一些代码,以获取表名(从 TListBox 中)或 where子句(从 TMemo 控件中)。这项决策是基于复选框的启用情况的,其编码如下:

SQLDataSet1.CommandText := 'SELECT ';

comma := False;

for i:=0 to Pred(CheckListBox1.Items.Count) do

begin

if CheckListBox1.Checked[i] then

begin

if not comma then comma := True

else

SQLDataSet1.CommandText :=SQLDataSet1.CommandText + ', ';

SQLDataSet1.CommandText := SQLDataSet1.CommandText + CheckListBox1.Items[i]

end

end;

if QCheckBox.Checked then

SQLDataSet1.CommandText := SQLDataSet1.CommandText + ' FROM ' + QMemo.Text

else if

ListBox1.ItemIndex >= 0 then SQLDataSet1.CommandText := SQLDataSet1.CommandText

+ ' FROM ' + ListBox1.Items[ListBox1.ItemIndex];

这确保您可以使用带有表名的 TListBox,或使用带有 SQL where 子句的 TMemo ,来选择字段并为视图产生数据。

添加更多的控件
您在图 2 中选择的字段是 PICTURE 字段。上次,我在由 TDBMemo 表示的 Memo 字段之间进行了区分,虽然其他所有字段都是由 TDBEdit 表示的。对于可以是 ftGraphic 或 ftBlob(本实例中)的 image 字段,您可以使用 TDBImage 控件。请注意,ftBlob 的情况并不是百分之百确定的:大多数 BLOB 字段只包含二进制数据,而不是总包含图像。但是对于本例,ftBlob PICTURE 字段包含图像(以不同的格式,如 bmp 或 gif)。

必须添加一些附加代码,以检测字段类型,并通过动态创建 TDBImage 对此作出响应,这些附加代码片断如下所示:

if (ClientDataSet1.FieldDefs[i].DataType = ftGraphic) or (ClientDataSet1.FieldDefs[i].DataType = ftBlob) then

begin

with TDBImage.Create(Self) do

begin

Parent := TabSheet3;

Left := 126;

Top := Y - 4;

Y := Y + 204;

Height := 200;

Width := 200;

DataSource := DataSource1;

DataField := ClientDataSet1.FieldDefs[i].Name;

end

end ;

在您选择了 PICTURE、FIRSTNME、LASTNAME、PHONENO、HIREDATE 和 SEX 字段的示例中,该代码的结果如图 3 所示。

图 3. 动态数据控件预览
怎么样通过DB2表为Delphi产生动态的数据输入窗体?

请注意,这里还有一个附加的按钮,在这里,将用它在新的 CLX 窗体上从该 tabsheet 重新创建这些控件。该窗体将被流化为 Delphi .pas 源文件和 .xfm 窗体文件;并准备将它们添加至 Kylix 或 Delphi CLX 项目中。

设计新的窗体框架
虽然在 tabsheet 中查看数据效果极佳(如图 3 中),但是在新的窗体(不同于原先那个您指定了表名或查询,并选择了要使用的字段名的窗体)中查看控件将具有更强大的功能。您必须重新创建 TLabel、TDBEdit、TDBMemo 和 TDBImage 控件,这意味着您可以从一个几乎为空的框架窗体开始,准备创建到 DB2 UDB 数据库新的 dbExpress 连接,输入 SQL 语句,并放置数据感知控件,以显示数据。

通过 Delphi 7,我设计了一个新的 CLX 窗体,该窗体包含 TSQLConnection、TSQLDataSet、TDataSetProvider、TClientDataSet、TdataSource 和 TDBNavigator 控件;其布局如图 4 中所示。像您以前多次做过的那样,将它们都关联起来,但是此时还没有任何数据感知控件。

图 4. 设计时的新窗体模板
怎么样通过DB2表为Delphi产生动态的数据输入窗体?

现在,应该为图 3 中首次出现的 Generate Code 按钮实现 OnClick 事件处理程序了。

生成新的窗体内容
Generate Code 按钮将创建新 CLX 框架窗体的一个实例,并动态添加 TLabel、TDBEdit、TDBMemo 和 TDBImage 控件(使用与原来窗体相同的属性值)。用来创建新的 CLX 窗体并克隆将放置在新 CLX 窗体上的控件的代码如下所示:

procedure TDBWizForm.btnWizardClick(Sender: TObject);

var

ThisForm: TDBWizForm;

NewForm: TNewForm;

LabelNr,EditNr,MemoNr,ImageNr,i: Integer;

begin

ThisForm := Self;

NewForm := TNewForm.Create(Self);

try

NewForm.Caption := 'Dr.Bob's Database Form Wizard';

NewForm.SQLConnection1.Params.Clear;

NewForm.SQLConnection1.Params.Assign(ThisForm.SQLConnection1.Params);

NewForm.SQLDataSet1.CommandText := ThisForm.SQLDataSet1.CommandText;

LabelNr := 0;

EditNr := 0;

MemoNr := 0;

ImageNr := 0;

for i:=0 to Pred(ThisForm.ComponentCount) do

begin

if (ThisForm.Components[i] is TLabel) then

begin

with TLabel.Create(NewForm) do

begin

Inc(LabelNr);

Name := Format('Label%d',[LabelNr]);

Parent := NewForm;

Left := (ThisForm.Components[i] as TLabel).Left;

Top := (ThisForm.Components[i] as TLabel).Top;

Width := (ThisForm.Components[i] as TLabel).Width;

Alignment := taRightJustify;

Caption := (ThisForm.Components[i] as TLabel).Caption

end

end

else if (Components[i] is TDBEdit) then

begin

with TDBEdit.Create(NewForm) do

begin Inc(EditNr);

Name := Format('Edit%d',[EditNr]);

Parent := NewForm;

Left := (ThisForm.Components[i] as TDBEdit).Left;

Top := (ThisForm.Components[i] as TDBEdit).Top;

Width := 200;

DataSource := NewForm.DataSource1;

DataField := (ThisForm.Components[i] as TDBEdit).DataField

end

end

else if (Components[i] is TDBMemo) then

begin

with TDBMemo.Create(NewForm) do

begin

Inc(MemoNr);

Name := Format('Memo%d',[MemoNr]);

Parent := NewForm;

Left := (ThisForm.Components[i] as TDBMemo).Left;

Top := (ThisForm.Components[i] as TDBMemo).Top;

Height := 200;

Width := 200;

DataSource := NewForm.DataSource1;

DataField := (ThisForm.Components[i] as TDBMemo).DataField

end

end

else if (Components[i] is TDBImage) then

begin

with TDBImage.Create(NewForm) do

begin Inc(ImageNr);

Name := Format('Image%d',[ImageNr]);

Parent := NewForm;

Left := (ThisForm.Components[i] as TDBImage).Left;

Top := (ThisForm.Components[i] as TDBImage).Top;

Height := 200;

Width := 200;

DataSource := NewForm.DataSource1;

DataField := (ThisForm.Components[i] as TDBImage).DataField

end

end

end;

try

NewForm.ClientDataSet1.Active := True;

except

end;

NewForm.ShowModal; // See Figure 5. NewForm.ClientDataSet1.Active := False; NewForm.SQLConnection1.Connected := False; WriteComponentResFile(UnitName+'.xfm', NewForm);

finally NewForm.Free

end

end;

在该事件处理程序的结尾,通过调用 ShowModal 显示了 NewForm,其结果如图 5 所示。因为就在显示该窗体之前激活了 TClientDataSet(关闭该窗体之时再取消激活状态),所以该图将显示带有数据的新窗体。

图 5. 运行时的新窗体
怎么样通过DB2表为Delphi产生动态的数据输入窗体?

在显示了该窗体之后,您可以创建一个 Delphi .xfm 文件,其中包含窗体的流化(streaming)信息、其上所有的组件及其属性信息。对 WriteComponentResFile 的调用将为该窗体创建一个二进制的源文件。该文件可用于 Kylix 或 Delphi CLX 项目,但是还需要相应的 .pas 文件,其中包含窗体上每个组件的源代码声明。

生成相关的源代码
最后一步(也集成在“Generate Code”OnClick 事件处理程序中)是创建包含组件声明的相应的 .pas 文件。这是一个由三个阶段组成的过程。首先,您要编写该 .pas 文件的第一部分,然后动态添加单个 TLabel、TDBEdit、TDBMemo 和 TDBImage 控件,最后,添加该 .pas 文件的最后一部分。

procedure TDBWizForm.btnWizardClick(Sender: TObject); var f: System.Text; LabelNr,EditNr,MemoNr,ImageNr,i: Integer; begin System.Assign(f,UnitName+'.pas'); Rewrite(f); writeln(f,'unit ',UnitName,';'); writeln(f,'interface'); writeln(f,'uses'); writeln(f,' SysUtils, Types, Classes, Variants, QTypes, QGraphics, QControls, QForms,'); writeln(f,' QDialogs, QStdCtrls, DBXpress, QCheckLst, DB, SqlExpr, QComCtrls, FMTBcd,'); writeln(f,' DBClient, Provider, QGrids, QDBGrids, QDBCtrls, QExtCtrls, QMask;'); writeln(f); writeln(f,'type'); writeln(f,' TNewForm = class(TForm)'); writeln(f,' SQLConnection1: TSQLConnection;'); writeln(f,' SQLDataSet1: TSQLDataSet;'); writeln(f,' DataSetProvider1: TDataSetProvider;'); writeln(f,' ClientDataSet1: TClientDataSet;'); writeln(f,' DataSource1: TDataSource;'); writeln(f,' DBNavigator1: TDBNavigator;'); LabelNr := 0; EditNr := 0; MemoNr := 0; ImageNr := 0; for i:=0 to Pred(ThisForm.ComponentCount) do begin if (ThisForm.Components[i] is TLabel) then begin Inc(LabelNr); Name := Format('Label%d',[LabelNr]); writeln(f,' ',Name,': TLabel;'); end else if (Components[i] is TDBEdit) then begin Inc(EditNr); Name := Format('Edit%d',[EditNr]); writeln(f,' ',Name,': TDBEdit;'); end else if (Components[i] is TDBMemo) then begin Inc(MemoNr); Name := Format('Memo%d',[MemoNr]); writeln(f,' ',Name,': TDBMemo;'); end else if (Components[i] is TDBImage) then begin Inc(ImageNr); Name := Format('Image%d',[ImageNr]); writeln(f,' ',Name,': TDBImage;'); end end; writeln(f,' procedure FormCreate(Sender: TObject);'); writeln(f,' private'); writeln(f,' { Private declarations }'); writeln(f,' public'); writeln(f,' { Public declarations }'); writeln(f,' end;'); writeln(f); writeln(f,'var'); writeln(f,' NewForm1: TNewForm;'); writeln(f); writeln(f,'implementation'); writeln(f); writeln(f,'{$R *.xfm}'); writeln(f); writeln(f,'procedure TNewForm.FormCreate(Sender: TObject);'); writeln(f,'begin'); writeln(f,' try'); writeln(f,' ClientDataSet1.Active := True'); writeln(f,' except'); writeln(f,' end'); writeln(f,'end;'); writeln(f); writeln(f,'end.'); System.Close(f); end;

完整的源代码集成位于事件处理程序之中,可通过下载获得。

使用结果
其结果就是一个 .pas 以及相应的 .xfm 文件,可以将它们用于 Linux 上的 Kylix 以及 Windows 上的 Delphi CLX 项目中。例如,目前给出了给定演示的结果窗体,如图 6 中所示,该图中显示了 Delphi 7 在设计时显示的结果窗体。该窗体与初始的框架窗体一样,只是添加了 TLabel、TDBImage 和 TDBEdit 控件,事实上,Height 已经被修改,以显示所有控件。

图 6. 设计时生成的新窗体
怎么样通过DB2表为Delphi产生动态的数据输入窗体?

正如我上次提到的,所生成的窗体不包含调用 TClientDataSet 的 ApplyUpdates 方法(用以将更新送回数据库)或 UndoLastChange,以及其他 Undo 方法的按钮。这将作为练习留给读者来完成。作为一点小小的一个提示,我建议在 NewForm 中将这些按钮添加至框架窗体,因此,您所做的修改将用于所有新生成的窗体中。那样的话,就可以将 NewForm 视作一个基类,用于所有动态创建的数据库窗体。

结束语
在这篇以及上一篇文章中,都检查了 IBM DB2 Universal Database 元数据,以便动态构建视图和窗体。您已经使用了带有 dbExpress 的 Linux 上的 Borland Kylix 3,以及 Windows 上的 Delphi 7,来分析 DB2 UDB 数据表、字段(名称和类型),允许用户选择特定的表或指定 SQL 语句的 join/where 子句,来回切换应该显示的字段,以及在数据网格(datagrid)和单个数据感知控件中动态查看输出。

最后一步包括动态生成新的 CLX 窗体,以及将该窗体流化为 Delphi .pas 和 .xfm 文件,并准备将它们添加到 Kylix 和 Delphi CLX 项目中(为 DB2 UDB 数据库表产生我们自己定制的数据库窗体向导)。