Delphi中TFrame组件的使用

博主注:用C#做开发的时候,感觉它的自定义控件的那个功能非常好。然后就想,既然C#有这个功能,那Delphi应该也会有的吧,然后到网上找了一下。果然,Delphi在很早的版本中就有了这个功能了。而且我感觉比C#的那个还好用些~~

从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;

框架节省资源

在声明窗体中有两个框架。我们已经讨论了TwoButtonFrame框架。第二个框架显示公司标志,名为LogoFrame。

LogoFrame在FramDemo的多个窗体中出现。一种替代使用框架的做法是在每个窗体中设置一个Image对象来显示公司标志。但是,用框架可以显著减少需要编译到EXE文件中的资源,生成一个较小的可执行文件。

你可以在如下的DFM文件部分找到原因:

inline LogoFrame1: TLogoFrame
Left = 6
Top = 6
Width = 211
Height = 182
       inherited Image1: TImage
                        Width = 211
                        Height = 182
       end
end

如果把一个TImage放在窗体中,窗体的DFM文件将必须包含整个公司标志的二进制数表示。而且,每一个含有图象的窗体都重复保存这一资源。但用框架时,资源只定义一次。

object LogoFrame: TLogoFrame
Left = 0
Top = 0
Width = 239
Height = 178
TabOrder = 0
object Image1: TImage
              Left = 0
              Top = 0
              Width = 239
              Height = 178
              Align = alClient
              Picture.Data = {
                           07544269746D6170D6540000424DD654000000000000760000…

简化框架的使用

在一单个、小的工程中,使用组件面板Standard页上的框架组件是相当简单的。但是对更大的、或是要在多个程序中使用同一个框架的情况下,你需要使操作更简单。幸运的是,Delphi允许你将单个框架放到组件面板,使这些框架不需要另外的步骤而简单的重复使用。框架也可放到对象库(Object Repository)中,使它可以简单的拷贝。这两个技术都将在下面叙述。

把框架添加到组件面板

把一个框架添加到组件模板,你可以使放置这一框架变得与其它组件一样简单。与之相比,用组件模板的Standard页中的框架组件需要四步,而且限制你仅能放置在你的工程中已经定义的框架。将一特定框架添加到组件模板中,需要以下四步:

1> 保存框架。如果你要在多个程序中使用一个框架,我非常建议你将框架保存到升级Delphi时不会被删除的目录中。例如,建立一个名为c:\Program Files\Borland\DelphiFrames的目录来保存你的框架。
2> 选择框架并在上面点鼠标右键,选择Add to Palette,Delphi会显示Component Template Information对话框。
3> 在Component name栏定义框架组件的名称,在Palette page栏填写你想让框架在组件模板内出现的页。如果你已为框架建立了一个24 x 24象素、16色的图标,点击Change按钮来选择这个BMP文件。当你完成后,点击OK按钮。

从组件模板使用框架

要使用一个已添加到组件面板的框架,在组件面板中选择你保存框架的页,选择框架图标并在你要框架后代出现的窗体上拖出该框架。这个过程仅需这两步。

将框架添加到对象库

把框架添加到对象库,你可以使拷贝该框架到一个新工程的过程变得简单。尤其重要的是对象库提供放置继承的框架到新的工程的能力,它使框架和它的祖先之间的关系得以保持。要将框架添加到对象库,需要以下步骤:

1> 保存框架。除了保存框架到Delphi的OBJREPOS目录或是共享目录,你还可以使用你保存添加到组件模板中的框架的目录。如果你使用共享库,将框架保存到共享目录特别好,它允许多个开发者共享框架。
2> 在框架上面点鼠标右键,选择Add To Repository,Delphi会显示Add To Repository对话框。
3> 就像你添加其它模板到Object Repository一样填写Add To Repository对话框。完成后点击OK按钮。

从对象库中使用框架

按照下面步骤来从对象库中使用框架:

1> 选择 File | New.
2> 选择你在Object Repository保存框架模板的页。
3> 选择代表框架的图标,然后选择Inherit单选按钮。
4> 点击OK按钮,添加框架的继承版本到你的工程

如果你选择Copy单选按钮而不是Inherit单选按钮,添加进的框架将是原框架的一个拷贝。当你需要创建一个新的框架,但又不想保持它与原框架的关系时是很有用的。

总结

把需要重复使用的框架添加到组件模板与添加到对象库有不同的效果吗?答案是:”Yes!”。在大多数情况,你要把你频繁使用的框架添加到组件面板中。这样当你放置框架时,你得到的是框架的一个实例。你然后可以容易的修改这一实例的属性和事件处理函数,就象本文先前介绍的一样。与之相比,从对象库放置一个框架会产生一个新的类,而不是一个实例。这个类要么是原来的拷贝要么是原来的继承,取决于你在Object Repository对话框中选择了哪一个单选按钮。如果你要在工程中使用框架,添加一个框架实例比定义一个框架新类有意义的多。如果是这一目的,将框架保存到组件面板是最好的方法。

有一种情况你要使用对象库:当你需要专门创建一框架层次,并且每一个框架后代引进了附加的对象、方法或事件处理函数。这时,对象库提供的继承性使创建后代变得简单。但是,当你定义好了需要经常使用框架的后代,我建议你将它们添加到组件面板以简化它们的使用。