by Ray Konopka
November/December 1999, Vol. 10, No. 4
Grouping Delphi UI elements together in true, object-oriented
containers has been tried several ways as Delphi has evolved. With Delphi
5, we now have the concept of component frames--and this time Ray thinks
they've got it right.
So, have you upgraded to Delphi 5 yet? No? Well you should. It has many
more features and capabilities than Delphi 3 & 4 and it does not
suffer from the stability problems that plagued Delphi 4 after its initial
release. In short, I highly recommend upgrading to Delphi 5.
In this article we are going to take a much closer look at the new
Frames technology introduced in Delphi 5. In my Delphi 5 Preview article
from the last issue, I briefly mentioned frames in the VCL Enhancements
section. Specifically, Delphi 5 introduces a new type of control called a
frame that can be nested within a form or another frame. The technique is
similar to visual form inheritance, but because frames can be embedded
within a form, they are often viewed as a way of creating a compound
component visually. Also, because frames are true objects, they are vastly
superior to component templates. For example, if you make a change to a
frame, the change is propagated to all instances of the frame, which is
not true for component templates.
On the surface, frames are certainly attractive and appear quite
powerful and flexible. However, this power and flexibility is not free. In
this article, we will discover just what we can and can't do with frames,
but before we do, I want to revisit a topic that I have discussed in
Action Resources Revisited
Way back in the March/April 1999 issue, I described how to create your
own custom action classes. In the process, I commented on how the standard
actions that come with Delphi have no constructors, but still get
initialized. In the July/August 1999 issue I described how Delphi
initializes custom actions using a registration resource, which as you may
recall is typically a custom data module.
At that time, I did not have an explanation as to why this approach was
taken, when simply overriding the custom action's constructors appears to
work quite well. Since then, I have investigated a little further into
this and discovered why Borland R&D took the approach they did for
initializing custom actions. It all has to do with images.
Specifically, take a look at Figure 1, which shows the RkListActionResource
data module being edited in Delphi 5. Note that the Action List Editor is
also open and shows the actions contained in ActionList1. More
importantly, notice how each action has an associated image. This is
reflected in the ImageIndex property for each custom action.
Figure 1: Creating a Custom Action Resource.
After a lot of testing, I discovered a significant advantage of the
resource approach versus overriding the constructor. In particular, when
you add a custom action to an action list in your application and that
action list is connected to an image list, the image associated with the
action is automatically copied into the application specific image list.
In addition, because the application's image list may already have images
on it, the Action List Editor is smart enough to alter the newly created
action's ImageIndex property to match the index of the image in the
application's image list. Very cool, indeed.
But be warned there is a sequence that must be followed. In order to
get the image associated with an action into your application's image
list; the image list must be assigned to the action list before you add
any custom actions to it.
By the way, the online help for Delphi 4.03 (and Delphi 5) has been
updated to describe the Resource parameter of RegisterActions, but
unfortunately it does not describe the significance of using an image list
component inside the resource along with the action list.
So, after this enlightenment, should you adopt the new resource
approach for all your custom actions? Well, it depends. If you want to
associate an image with your custom action, then yes, by all means, use a
custom resource to initialize the image to be used. However, if your
action does not need an associated image, then I recommend simply
overriding the custom action's constructor and initialize the appropriate
properties-it's much easier and does not require a two-step registration
So, what exactly are frames? Technically, a frame is any class that
descends from the TFrame base class. A more practical definition is
that a frame is a specialized component container that can be dropped onto
forms, components, and even other frames. Another way of looking at a
frame is Visual Form Inheritance (VFI) applied to a panel. However, the
best way to learn about frames is to see an example.
Figure 2 shows a form that will ultimately be used to capture
information for an invoice. Also in the figure is a new frame, Frame1.
This frame was created by selecting the File|New Frame menu item. After
creating a new frame, I recommend renaming it to something more
meaningful. In this example, I renamed the frame to AddressFrame.
Next, you place components on the frame just as you would with a form. You
can even write event handlers for the components on the frame. Figure 3
shows the completed address frame.
Figure 2: Frames look slightly different from forms at design-time.
Figure 3: Components are placed on a frame just like a form.
We are now ready to create an instance of our new address frame in the
group boxes of the main invoice form. This is accomplished by clicking on
the Frames icon located on the Standard page of the component palette
followed by clicking on the form or component that will be the parent of
the frame instance. This action results in the dialog box shown in Figure
4 being displayed. From here you can select which frame you would like to
Figure 4: Inserting a frame-also called creating a frame instance.
At this point I should clarify some of the terminology that is used
when talking about frames. First, the term frame is typically used to
refer to the base class definition and its associated DFM file that
describes the visual characteristics of the components that are on the
frame. The process of inserting a frame somewhere is a bit misleading. In
fact, the process of inserting a frame is equivalent to creating an
instance of a frame. Another term that you will see commonly used is
inline frame. An inline frame is simply another term for an instance of a
For example, Figure 5 shows the Delphi IDE from Figure 4 just after the
OK button was pressed. Using the terminology described above, the design
window with AddressFrame in the caption represents the TAddressFrame
frame. Inside the Bill To group box, AddressFrame1 represents an
instance of the TAddressFrame frame. AddressFrame1 can also
be called an inline frame.
Figure 5: The Bill To group box contains an inline frame.
Inheritance is Key
Frames are similar to component templates in that the components that
are contained within the frame instance can be customized using the Form
Designer and Object Inspector. However, as noted at the beginning of this
article, frames are much more powerful than templates.
I like to describe component templates as specialized IDE macros. That
is, when you create a component template you are essentially instructing
the IDE to record each of the selected components, their properties, and
their event handlers. The recorded template is stored in the Delphi.dct
file located in the Delphi Bin directory and an icon is created on the
component palette. This icon is used to play back the recorded macro.
What happens if you decide to change the template macro? In this case,
all future playbacks of the template will include the change. However,
playbacks that took place before the change are not altered. In fact,
there is absolutely no connection at all between the components that are
part of a template and the template itself.
This is the biggest difference between component templates and frames.
Unlike templates, frames are implemented as true objects. As a result,
frames take advantage of inheritance to propagate changes to all instances
of the frame. As an example, consider Figure 6, which shows the same
example described in Figure 5, but this time the edit field used for the
State field has been changed to a combo box. Notice how both instances of
the frame on the form automatically reflect the change.
Figure 6: Frames support visual inheritance.
Customizing an Inline Frame
As noted above, inline frames can be customized using the Form Designer
and the Object Inspector. In fact, you can customize any component's
properties within the frame instance. For example, Figure 6 shows how the
Name label's Caption has been customized in each instance. Any
changes made in an inline frame essentially override the default values
defined in the frame. To cancel your customizations and restore the
default, simply right-click on the specific property or the component and
select the Revert to Inherited menu item.
In addition to customizing properties, you can also customize event
handlers. For example, suppose that our address frame defines an OnChange
event handler for the EdtName component. If we then double-click on
the EdtName component on the AddressFrame1 instance, the
following event handler is created:
procedure TForm1.AddressFrame1EdtNameChange(Sender: TObject);
Notice how Delphi automatically adds a call to the default event
handler defined in the TAddressFrame class. However, this only
works when the event handler is already defined in the frame class. For
example, suppose you create a custom event handler for an event that is
not handled by the frame class. If you were to go back and create an event
handler in the base class for the same event, you would not get the
default event handler code inserted into your previously defined event
handler. You have to add this code manually.
It is quite common these days for dialogs to use a page control to
partition information-the Delphi IDE uses several. Creating these dialogs
is also very easy thanks to the TPageControl component. However,
for complex dialog boxes, it is often necessary to have more than one
developer working on it. This becomes quite a challenge when all of the
controls are dropped directly onto the tab sheets of the page control.
A better approach is to break up the multi-page dialog into separate
pieces. In previous versions of Delphi, this is accomplished by creating a
separate form for each page of the dialog box. Each page form has its BorderStyle
property set to bsNone and in the main dialog form, all you need to
do is to create an instance of each page form and set its Parent
property to the appropriate tab sheet. Note, after constructing the page
form, you need to set its Visible property to True to get it to
appear, and you will also need to set the Left and Top
properties to position the embedded form correctly.
Frames offer another way of handling complex multi-page dialogs.
Instead of creating a separate form for each page, you create a separate
frame. This lets multi-developers work on the page frames. In addition,
because frames can be dropped onto a form at design-time, it is possible
to see all of the pages in the main dialog at design-time.
However, if you want to delay creating the controls that are on a
particular page until the user selects that page, then you will have to
create the frame dynamically at runtime in the same way described above
with the form approach. In this case, there is not much benefit of using
frames over separate forms. In fact, the separate form approach does offer
some benefits over frames as described in the Limitation section later in
Frames and Compound Components
On the surface, a frame looks a lot like a compound component. (A
compound component is a component that contains one or more embedded
components.) In fact, frames and compound components do share many of the
same features. However, there are some distinct differences between the
For example, the embedded components that are in a (correctly
implemented) compound component cannot be selected at design-time. As a
result, it is not possible to manipulate the embedded control directly.
This does not mean that the embedded control cannot be modified, it simply
means that the compound component must surface additional properties to
manipulate the control. The point is that with a compound component, you
are only allowed to change what the component author provides access to.
The benefit of this approach is consistency.
With frames, however, a developer is allowed to change any property for
any control that is contained within the inline frame. This includes
layout, dimensions, anything. The frame approach is certainly more
flexible, but if you are trying to enforce consistency this flexibility is
counter-productive. It would be nice if it were possible to prevent a user
from modifying the components in an inline frame, but it's not-maybe for
Another difference between the two approaches is the way in which the
two are distributed. Compound components are placed inside a package and
can be distributed to any other developer who can then install the package
and see the component on the palette. Frames, on the other hand, are
shared among developers by either using the Object Repository or by
copying the *.pas and *.dfm files associated with the frame unit.
Yes, it is possible to add a frame to the component palette by
selecting the Add to Palette menu item that appears when right-clicking on
a frame. However, the frame is added to the palette in the same way a
component template is added to the palette. The icon that is created on
the palette simply provides an easy way to create an inline instance of
As flexible as frames are there are several limitations of the
technology that you should be aware of. First, unlike a descendant form,
you cannot add components to an inline frame at design-time. However, you
can add a component to a frame at runtime. Second, you cannot delete a
component from an inline frame at design-time. Of course, you can free a
component in a frame at runtime, but be warned that if the base frame code
tries to access the component you destroyed, you will get an access
Another way in which frames differ from forms is that TFrame
does not define an OnCreate event or an OnDestroy event. The
reason why these events are not available is quite complicated but suffice
it to say that the issue has been thoroughly investigated by Borland
R&D. In short, the problem is one of timing-there is no clear and
consistent point at which these events can be generated for a frame.
Fortunately, there is way to workaround this limitation. The trick is
to remember that a TFrame is just a class, and as such, it has a
constructor and destructor that we can override. Of course it's not as
easy as writing an event handler, but it does work.
Moving on, suppose you want to create a custom property or event for
your new frame. As an example, consider the address frame that we've been
using. Suppose we wanted to create a property that would rearrange the
layout of the controls on the frame so that the labels appear either to
the left of the corresponding data-entry control or on top of them.
Defining the actual property is fairly simple. However, the new
property will not appear in the Object Inspector even if it is declared as
a published property. Why this happens is the same reason custom
properties for forms do not appear in the Object Inspector. To get the
properties to show up you need to create a custom module and register it
with Delphi. Unfortunately, even though this capability has been around
since Delphi 3, there is still no easy way to create and register a custom
module in Delphi.
Component Writer Guidelines
For you component writers out there, there are a few things that you
need to be aware of with respect to the new frame technology. First, as
stated earlier, frames are implemented in much the same way as Visual Form
Inheritance (VFI). It is not exactly the same, but from a component
writer's perspective it is the same technology. Therefore, in order for
your custom component to be used in a frame, it must support being used
with VFI. That is, it must at least specify the csInheritable style
in its ControlStyle property. Most components should have no
trouble supporting VFI and frames. However, if your component performs any
custom streaming, then you will most certainly want to thoroughly test
your component with both VFI and frames.
The new frame technology also impacts custom property and component
editors. For instance, a design editor must NOT be allowed to create new
components when the corresponding component is being edited in an inline
frame. Here's an example, create a new application. Next, create a new
frame. Drop a page control on Frame2 and set Align to alClient.
Now select New Page from the context menu to create a new tab sheet. Next,
create an inline instance of Frame2 onto Form1. Next,
right-click on the instance of the frame on Form1 and notice how
the New Page menu item is disabled. If you invoked the popup menu from the
page control, then the Delete Page is also disabled. However, if you
invoke the menu from the tab sheet, then the Delphi Page is enabled.
However, if you try to delete the page, Delphi does not let you.
So, how do we know when to show the menu items? Fortunately, a new
method has been added to the TComponentEditor class. The IsInInlined
method returns True if the component is in an inline frame.
Another issue that custom design editors need to be aware of is that Designer.Form
is not necessarily the same as Designer.Root. The Designer property
is defined in both the TPropertyEditor and TComponentEditor
classes and provides an interface reference to the current form designer.
For forms, Designer.Form and Designer.Root are indeed the
same, but for frames and data modules, they are not. As a result, it is
crucial for custom editors that create new components to use Designer.Root
as the owner of the new components. For the same reasons, the common
technique of using GetParentForm to retrieve a reference to the
owner will not work for a component dropped onto a frame.
On the Drawing Board
So, what do I have planned for next time? Well, actually I haven't
quite decided yet. However, it is pretty safe to assume that I will cover
another new technology in Delphi 5.
By the way, the Palette menu item that you see in the screen shots is
not part of Delphi 5. This is a custom expert that I have written to make
it easier to switch to different pages on the component palette. Please
see the Raize Software web site (http://www.raize.com/Tools/Delphi/PaletteMenu.asp)
Copyright © 1999 The Coriolis Group, Inc. All rights reserved.