从Delphi 5.0开始,Borland 引进了一个新的可视化的容器类TFrame。
这个类,我们称之为TFrame框架组件,使程序开发人员能够可视化的设置一组组件,之后系统中对它进行重用。
TFrame框架概观
TFrame框架有两个主要的好处:
第一、框架可大幅减少需要存储在工程中的资源量。
第二、框架允许你可视化的创建能复制和扩展的对象。对可视化窗体继承(VFI),你可以享受同样的好处。
VFI允许你很简单的创建由继承得来的窗体。VFI的限制是你必须用“全部或全无”的方式来使用窗体。更具体的说,当你用VFI时,你总是创建一个新的窗体。框架,相反的,在这一方面更类似面板(TPanel组件),这就是说一个窗体可包含两个或更多框架。每一个框架保持它与父TFrame的关系,即对父类的改变将自动被实例继承。这一点是很重要的。虽然你可以用TPanel组件达到同样的效果,但它只局限于基于代码的操作,即你必须写代码来手工定义TPanel的子孙le。相反的,框架被可视化的设计,就像窗体一样。
TFrame框架也可认为与组件模板(含一个或更多组件并用Component | Create Component Template命令保存到组件面板的一个组)相似。但是,相似之处仅限于它们都是被可视化设计的(不象传统的完全基于代码的组件设计)。实际上,框架与组件模板的差异是巨大的。正如我们已学过的,框架是一定义类的实例,当定义类发生改变时框架也将发生变化。与之相比,组件模板是组件的集合。对组件模板的改变不影响以前从这一模板创建的对象。
创建框架
以下步骤演示如何建立一个框架:
1> 选择 File | New Application 建立一个新的工程。
2> 选择 File | New Frame 建立一个新的TFrame框架。在这一框架上,添加三个Label和三个DBEdit,还有一个DBNavigator和一个DataSource。将Label的Caption分别设为ID,First Name和Last Name。将DBEdit和DBNavigator的DataSource属性设为DataSource1。
3> 将框架的Name属性设为NameFrame。(相比其它对象,起一个有意义的名字对框架来说非常重要)最后,选择File | Save As 保存框架。在这里,用文件名NAMEFRAM.PAS保存框架。
这就是建立一个框架的所有步骤。
使用框架
以下步骤演示如何使用框架:
1> 在由上面步骤建立的工程中选择Form1。
2> 加两个GroupBox到窗体上,其中一个在另一个之上。设置第一个的Caption为Customers,第二个的Caption为Employees。
3> 现在添加框架。选择组件面板的Standard页,点击Frame组件并将其拖到名为Customers的GroupBox中。这时Delphi会显示一个Select frame to insert对话框。
4> 在对话框中选择NameFrame。现在框架将在名为Customers的GroupBox中显示。重复这一步骤,这一次将框架放在名为Employees的GroupBox中。你可能要调节框架的尺寸,这跟你最初是如何放置有关。
5> 将两个Table组件放到窗体中,将其DatabaseName属性都设为IBLocal。将Table1的TableName属性设为CUSTOMER,将Table2的TableName属性设为EMPLOYEE。将两个Table的Active属性都设为True,使它们有效。
6> 下面的步骤将把事情变得有趣。选择名为Customers的GroupBox中的DataSource,将其DataSet属性设为Table1。一般的你不能直接选择组件中的对象,但是框架是个例外。你可以选择框架中的任何对象,操作它们的属性。然后,选择名名为Customers的GroupBox中DataSource,其DataSet属性设为Table2。
7> 最后,设置好所有的DBEdits。将名为Customers的GroupBox中的三个DBEdits的DataField属性分别设为CUST_NO,CONTACT_FIRST和CONTACT_LAST。对Employees中的,设置DataField属性为EMP_NO,FIRST_NAME和LAST_NAME。
8> 保存工程并运行。
框架和继承
到此为止,使用框架似乎没有什么好处。但是,当你要在一些地方使用同一个框架,然后又要改变所有这些实例时,框架的威力就表现得很明显了。例如,假设你要使所有的NameFrame框架变为只读的,你只需要将初始的框架修改,所有的修改就会被框架实例立刻继承。
你依照如下步骤就可以验证这一点:
1> 在上面建立的工程中,按[Shift][F12]并在窗体列表中选择NameFrame。
2> 将DataSource的AutoEdit属性设为False 。
3> 然后,选择DBNavigator,展开它的VisibleButtons属性,并设置nbInsert,nbDelete,nbEdit,nbPost和nbCancel标志为False。
4> 现在看一下你的主窗体,注意两个NameFrame的后代都继承了你对框架做的修改。
重载包含组件的属性
框架的优点之一(与VFI一样)是你可以改变框架中对象的属性和事件处理函数。这些修改重载了继承的值。说得具体些,随后对初始框架的重载属性的修改将不改变后代的重载属性的值。以下步骤可以验证这一行为:
1> 在名为Customers的GroupBox中,选择标题为"ID"的label,在Object Inspector将其Caption属性改为“Customer No:”。现在选择名为Customers的GroupBox中的标题为"ID"的Label,其Caption属性改为“Employee ID:”。
2> 按[Shift][F12]并选择NameFrame。将标题为"ID"的Label的Caption属性改为Identifier。
3> 回到主窗体,注意Label的Caption属性没有变为Identifier。它们仍保持它们的重载值。
4> 这一效果是由保存在DFM文件中的信息实现的。
注意所有其属性改变过的在框架中的对象,都出现在DFM文件中的内置部分。但是,这一部分仅列出那些改变的值,所有其它属性值要么按初始框架的值设置(它们已经存储在框架的DFM文件中),要么按每一组件的默认类定义值。
包含组件的事件处理函数
框架中的对象也可以有事件处理函数。虽然事件只是一个方法指针型属性,但它们的默认行为被重载后的处理与其它属性不同。
让我们先考虑一下框架对象的事件处理函数是如何定义的。假设一个框架有两个按钮,一个标为Help,另一个标为Done(显然按钮的标题可以在框架后代中重载)。这两个按钮都有onClick的事件处理函数:
procedure TTwoButtonFrame.Button1Click(Sender: TObject);
begin
if (TComponent(Sender).Tag = 0) or (Application.HelpFile = '''') then
MessageBox(Application.Handle,''Help not available'', ''Help'',MB_OK)
else
Application.HelpContext(TComponent(Sender).Tag);
end;
---------------------------------------------------------------------------------------------------------
procedure TTwoButtonFrame.Button2Click(Sender: TObject);
var
AParent: TComponent;
begin
AParent := TComponent(Sender).GetParentComponent;
while not (AParent is TCustomForm) do
AParent := AParent.GetParentComponent;
TCustomForm(AParent).Close;
end;
就象窗体中的对象的事件处理函数是窗体类的公开方法,框架中的对象的事件处理函数也是框架类的公开方法。(代码段实际上并没有说框架的这些方法是公开的,而是它们在框架声明的默认可见部分被声明,其可见性默认是公开的。)
如果你查看与Done按钮相联系的Button2Click事件处理函数的代码,你会发现与框架相联系的事件处理函数引进了一个很有趣的技术。具体的说,由于Self是框架而不是包含框架的窗体,因此不能在函数中调用Close方法来关闭窗体。当你在代码中使用没有限定的方法时,编译器将认为你要使用Self的方法。因为TFrame对象没有Close方法,编译器将产生一个错误。
因为在本例中框架被设计为内置在一个窗体中,事件处理函数用GetParentComponent方法向上找TCustomForm实例(它要么是TForm的后代,要么是基于TCustomForm的定制窗体),如果找到了,就调用窗体的Close方法。
重载包含对象的事件处理函数
如果你对VFI中的事件重载比较熟悉,你会回忆起Delphi在后代窗体的重载的事件处理函数中内置继承的事件处理函数。你可以改变生成的代码,在调用继承函数之前或之后添加附加的代码,或是有条件的调用继承的事件处理函数,或是干脆省略对继承的事件处理函数的调用。
框架的后代当使用父框架中对象的事件处理函数时,不使用继承的函数,而直接调用祖先框架的方法。例如,你把TwoButtonFrame放到一个窗体中然后双击,Delphi将产生如下的代码:
procedure TForm1.TwoButtonFrame1Button2Click( Sender: Object);
begin
TwoButtonFrame1.Button2Click(Sender);
end;
在产生的代码中,TwoButtonFrame1是TTwoButtonFrame(最初的框架类)的后代。Button2Click,如你在先前的代码段中所看到的,是Done按钮的事件处理函数。结果是,这一代码调用最初的事件处理函数,将框架实例中传给按钮的Sender传给它。
这意味着事件处理函数引进了另一个有趣的特性。具体的说,在这种情况下,Sender一般不是Self对象的成员。实际上,Sender一般是窗体对象的成员,而Self是框架对象。
在这里,原来的行为被“注释掉了”,这样新行为就完全替代了原来对Done按钮定义的行为。
procedure TForm1.TwoButtonFrame1Button2Click( Sender: TObject);
begin
with TForm2.Create(Self) do begin
ShowModal;
Release;
end;
// 下面是原来自动生成的代码
// TwoButtonFrame1.Button2Click(Sender);
end;
这个类,我们称之为TFrame框架组件,使程序开发人员能够可视化的设置一组组件,之后系统中对它进行重用。
TFrame框架概观
TFrame框架有两个主要的好处:
第一、框架可大幅减少需要存储在工程中的资源量。
第二、框架允许你可视化的创建能复制和扩展的对象。对可视化窗体继承(VFI),你可以享受同样的好处。
VFI允许你很简单的创建由继承得来的窗体。VFI的限制是你必须用“全部或全无”的方式来使用窗体。更具体的说,当你用VFI时,你总是创建一个新的窗体。框架,相反的,在这一方面更类似面板(TPanel组件),这就是说一个窗体可包含两个或更多框架。每一个框架保持它与父TFrame的关系,即对父类的改变将自动被实例继承。这一点是很重要的。虽然你可以用TPanel组件达到同样的效果,但它只局限于基于代码的操作,即你必须写代码来手工定义TPanel的子孙le。相反的,框架被可视化的设计,就像窗体一样。
TFrame框架也可认为与组件模板(含一个或更多组件并用Component | Create Component Template命令保存到组件面板的一个组)相似。但是,相似之处仅限于它们都是被可视化设计的(不象传统的完全基于代码的组件设计)。实际上,框架与组件模板的差异是巨大的。正如我们已学过的,框架是一定义类的实例,当定义类发生改变时框架也将发生变化。与之相比,组件模板是组件的集合。对组件模板的改变不影响以前从这一模板创建的对象。
创建框架
以下步骤演示如何建立一个框架:
1> 选择 File | New Application 建立一个新的工程。
2> 选择 File | New Frame 建立一个新的TFrame框架。在这一框架上,添加三个Label和三个DBEdit,还有一个DBNavigator和一个DataSource。将Label的Caption分别设为ID,First Name和Last Name。将DBEdit和DBNavigator的DataSource属性设为DataSource1。
3> 将框架的Name属性设为NameFrame。(相比其它对象,起一个有意义的名字对框架来说非常重要)最后,选择File | Save As 保存框架。在这里,用文件名NAMEFRAM.PAS保存框架。
这就是建立一个框架的所有步骤。
使用框架
以下步骤演示如何使用框架:
1> 在由上面步骤建立的工程中选择Form1。
2> 加两个GroupBox到窗体上,其中一个在另一个之上。设置第一个的Caption为Customers,第二个的Caption为Employees。
3> 现在添加框架。选择组件面板的Standard页,点击Frame组件并将其拖到名为Customers的GroupBox中。这时Delphi会显示一个Select frame to insert对话框。
4> 在对话框中选择NameFrame。现在框架将在名为Customers的GroupBox中显示。重复这一步骤,这一次将框架放在名为Employees的GroupBox中。你可能要调节框架的尺寸,这跟你最初是如何放置有关。
5> 将两个Table组件放到窗体中,将其DatabaseName属性都设为IBLocal。将Table1的TableName属性设为CUSTOMER,将Table2的TableName属性设为EMPLOYEE。将两个Table的Active属性都设为True,使它们有效。
6> 下面的步骤将把事情变得有趣。选择名为Customers的GroupBox中的DataSource,将其DataSet属性设为Table1。一般的你不能直接选择组件中的对象,但是框架是个例外。你可以选择框架中的任何对象,操作它们的属性。然后,选择名名为Customers的GroupBox中DataSource,其DataSet属性设为Table2。
7> 最后,设置好所有的DBEdits。将名为Customers的GroupBox中的三个DBEdits的DataField属性分别设为CUST_NO,CONTACT_FIRST和CONTACT_LAST。对Employees中的,设置DataField属性为EMP_NO,FIRST_NAME和LAST_NAME。
8> 保存工程并运行。
框架和继承
到此为止,使用框架似乎没有什么好处。但是,当你要在一些地方使用同一个框架,然后又要改变所有这些实例时,框架的威力就表现得很明显了。例如,假设你要使所有的NameFrame框架变为只读的,你只需要将初始的框架修改,所有的修改就会被框架实例立刻继承。
你依照如下步骤就可以验证这一点:
1> 在上面建立的工程中,按[Shift][F12]并在窗体列表中选择NameFrame。
2> 将DataSource的AutoEdit属性设为False 。
3> 然后,选择DBNavigator,展开它的VisibleButtons属性,并设置nbInsert,nbDelete,nbEdit,nbPost和nbCancel标志为False。
4> 现在看一下你的主窗体,注意两个NameFrame的后代都继承了你对框架做的修改。
重载包含组件的属性
框架的优点之一(与VFI一样)是你可以改变框架中对象的属性和事件处理函数。这些修改重载了继承的值。说得具体些,随后对初始框架的重载属性的修改将不改变后代的重载属性的值。以下步骤可以验证这一行为:
1> 在名为Customers的GroupBox中,选择标题为"ID"的label,在Object Inspector将其Caption属性改为“Customer No:”。现在选择名为Customers的GroupBox中的标题为"ID"的Label,其Caption属性改为“Employee ID:”。
2> 按[Shift][F12]并选择NameFrame。将标题为"ID"的Label的Caption属性改为Identifier。
3> 回到主窗体,注意Label的Caption属性没有变为Identifier。它们仍保持它们的重载值。
4> 这一效果是由保存在DFM文件中的信息实现的。
注意所有其属性改变过的在框架中的对象,都出现在DFM文件中的内置部分。但是,这一部分仅列出那些改变的值,所有其它属性值要么按初始框架的值设置(它们已经存储在框架的DFM文件中),要么按每一组件的默认类定义值。
包含组件的事件处理函数
框架中的对象也可以有事件处理函数。虽然事件只是一个方法指针型属性,但它们的默认行为被重载后的处理与其它属性不同。
让我们先考虑一下框架对象的事件处理函数是如何定义的。假设一个框架有两个按钮,一个标为Help,另一个标为Done(显然按钮的标题可以在框架后代中重载)。这两个按钮都有onClick的事件处理函数:
procedure TTwoButtonFrame.Button1Click(Sender: TObject);
begin
if (TComponent(Sender).Tag = 0) or (Application.HelpFile = '''') then
MessageBox(Application.Handle,''Help not available'', ''Help'',MB_OK)
else
Application.HelpContext(TComponent(Sender).Tag);
end;
---------------------------------------------------------------------------------------------------------
procedure TTwoButtonFrame.Button2Click(Sender: TObject);
var
AParent: TComponent;
begin
AParent := TComponent(Sender).GetParentComponent;
while not (AParent is TCustomForm) do
AParent := AParent.GetParentComponent;
TCustomForm(AParent).Close;
end;
就象窗体中的对象的事件处理函数是窗体类的公开方法,框架中的对象的事件处理函数也是框架类的公开方法。(代码段实际上并没有说框架的这些方法是公开的,而是它们在框架声明的默认可见部分被声明,其可见性默认是公开的。)
如果你查看与Done按钮相联系的Button2Click事件处理函数的代码,你会发现与框架相联系的事件处理函数引进了一个很有趣的技术。具体的说,由于Self是框架而不是包含框架的窗体,因此不能在函数中调用Close方法来关闭窗体。当你在代码中使用没有限定的方法时,编译器将认为你要使用Self的方法。因为TFrame对象没有Close方法,编译器将产生一个错误。
因为在本例中框架被设计为内置在一个窗体中,事件处理函数用GetParentComponent方法向上找TCustomForm实例(它要么是TForm的后代,要么是基于TCustomForm的定制窗体),如果找到了,就调用窗体的Close方法。
重载包含对象的事件处理函数
如果你对VFI中的事件重载比较熟悉,你会回忆起Delphi在后代窗体的重载的事件处理函数中内置继承的事件处理函数。你可以改变生成的代码,在调用继承函数之前或之后添加附加的代码,或是有条件的调用继承的事件处理函数,或是干脆省略对继承的事件处理函数的调用。
框架的后代当使用父框架中对象的事件处理函数时,不使用继承的函数,而直接调用祖先框架的方法。例如,你把TwoButtonFrame放到一个窗体中然后双击,Delphi将产生如下的代码:
procedure TForm1.TwoButtonFrame1Button2Click( Sender: Object);
begin
TwoButtonFrame1.Button2Click(Sender);
end;
在产生的代码中,TwoButtonFrame1是TTwoButtonFrame(最初的框架类)的后代。Button2Click,如你在先前的代码段中所看到的,是Done按钮的事件处理函数。结果是,这一代码调用最初的事件处理函数,将框架实例中传给按钮的Sender传给它。
这意味着事件处理函数引进了另一个有趣的特性。具体的说,在这种情况下,Sender一般不是Self对象的成员。实际上,Sender一般是窗体对象的成员,而Self是框架对象。
在这里,原来的行为被“注释掉了”,这样新行为就完全替代了原来对Done按钮定义的行为。
procedure TForm1.TwoButtonFrame1Button2Click( Sender: TObject);
begin
with TForm2.Create(Self) do begin
ShowModal;
Release;
end;
// 下面是原来自动生成的代码
// TwoButtonFrame1.Button2Click(Sender);
end;