Mega Code Archive

 
Categories / Delphi / Examples
 

Application design using frames

When Delphi 5 first shipped, it included a new, visual container class that represents an important advance in rapid application development (RAD) programming. This class, TFrame, provides you with the ability to visually configure a set of one or more components, and then to easily reuse this configuration throughout your application. This capability is so powerful that Delphi 5's integrated development environment (IDE) was re-designed, making extensive use of frames. Frames are now available in C++ Builder 5 and later, as well as Kylix. This paper provides you with a detailed overview of frames. It begins with a general discussion of what frames are and what benefits they provide. It continues with a demonstration of how to create frames, and how to modify the properties of objects that appear on frame instances. Next, you will learn how to create event handlers for frames, and how to override or extend these event handlers in frame instances. This paper concludes by showing you how to add frames to the Component Palette and the Object Repository, and what benefits you gain by doing so. Overview of Frames There are two primary benefits of frames. The first is that, under certain circumstances, frames can dramatically reduce the amount of resources that need to be stored in a project. The second, and generally more important benefit, is that frames permit you to visually create objects that can be duplicated from and extended. These happen to be the same two benefits that you enjoy with visual form inheritance (VFI). VFI permits you to create form objects that can be inherited from easily. The main limit to VFI is that you must use the form in an all-or-nothing fashion. Specifically, when you use VFI you always create an entirely new form. Frames, on the other hand, are more similar to panels in this respect. That is, a single form can contain two or more frames. Importantly, every frame maintains its relationship with the TFrame class that defines it, meaning that subsequent changes to the defining class are automatically inherited by the instances. While you could achieve a similar effect using TPanels, doing so would be a strictly code-based operation. That is, you would have to write the code to define the TPanel descendants manually. Frames, on the other hand, are designed visually, just like forms. Frames can also be thought of as sharing some similarities with component templates (a group of one or more components that are saved to the component palette by selecting Component | Create Component Template). But the similarities are limited to the fact that both component templates and frames are designed visually (unlike traditional component design, which is an exclusively code-based process). But the differences between component templates and frames are actually very great. As you have already learned, a frame is an instance of a defining class, and as such, is changed when the defining class is changed. By comparison, component templates are aggregates of components. A change to a component template has no effect on objects previously created from that template. Creating a Frame The following steps demonstrate how to create a frame. 1. Begin by selecting File | New Application to create a new project. 2. Select File | New Frame to create a new Frame. Place on this frame 3 Labels and DBEdits. Also place a DBNavigator and a DataSource. Set the Captions of the Labels to ID, First Name, Last Name. Set the DataSource property of each DBEdit as well as the DBNavigator to DataSource1. When you are done the frame may look something like that shown in Figure 1. Figure 1. A simple frame for displaying an ID number, as well as a first and last name. 3. With this frame still selected, set its Name property to NameFrame. More so than other objects, it is particularly important to give a frame a meaningful name. Finally, save the frame by selecting File | Save As to save this frame. In this case save the frame using the filename NAMEFRAM.PAS. That's all there is to creating a frame. The following section demonstrates how to use this frame. Using a Frame A frame is a component. Its typical use, however, differs from other components that appear on the component palette. The following steps demonstrate how to use a frame. 1. Select Form1 of the application that you started in the preceding steps. 2. Add two group boxes to the form, one above the other. Set the Caption of the first groupbox to Customers and the caption of the second to Employees. Your form may look something like that shown in Figure 2. Figure 2. A form ready for the placement of frames 3. Now add the frames. With the Standard page of the component palette selected, click on the Frame component and drop it within the Customer groupbox. The designer responds by displaying the Select frame to insert dialog box shown in Figure 3. Figure 3. The Select frame to insert dialog box 4. Select NameFrame. The frame now appears within the Customer groupbox. Repeat this process, this time placing the frame within the Employee groupbox. You may have to select each frame and correct its size, depending on how you placed it originally. When you are done your form may look something like the one you see in Figure 4. Figure 4. Two instances of the NameFrame appear on this form 5. Continue by placing two Table components onto the form. Set the DatabaseName property of both tables to IBLocal. Set the TableName property of Table1 to CUSTOMER and the TableName property of Table2 to EMPLOYEE. Make both tables active by setting their Active property to True. 6. Now here is where things get interesting. Select the data source in the Customer frame and set its DataSet property to Table1. Normally you cannot directly select objects that appear within a component. But frames are special. You can select any of the objects that appear within a frame and work with their properties. Next, repeat this operation by selecting the data source in the Employee frame and setting its DataSet property to Table2. 7. Finally, hook up all of the DBEdits. Assign the DataField property of the three DBEdits on the Customer frame to CUST_NO, CONTACT_FIRST, and CONTACT_LAST, respectively. For the Employee frame set the DataField properties of these same DBEdits to EMP_NO, FIRST_NAME, and LAST_NAME. 8. Save this project and then run it. The running project will look something like that shown in Figure 5. Figure 5. The running frame project Frames and Inheritance Up to this point there may seem to be only a little benefit to the use of frames. However, it is when you use the same frame in a number of different situations, and then want to change all instances, that the power of frames really becomes obvious. For example, imagine that you decided that you want your NameFrame to be a read-only frame. This can be accomplished easily by simply changing the original frame. Each frame instance immediately inherits all changes. You can demonstrate this yourself by following these steps: 1. With the project created in the preceding section open, press Shift-F12 and select NameFrame from the displayed list of forms. 2. Set the AutoEdit property of the DataSource to False. 3. Next, select the DBNavigator, expand its VisibleButtons property, and set the nbInsert, nbDelete, nbEdit, nbPost, and nbCancel flags to False. 4. Now look at your main form. Notice that both of the NameFrame descendants have inherited the changes that you made to the frame. Your frame may now look like the one shown in Figure 6. Figure 6. Updating the NameFrame automatically causes all instances to be updated as well. Overriding Contained Component Properties One of the advantages of frames, one shared with VFI, is that you can change the properties and event handlers associated with the objects inside the inherited frame. These changes override the values inherited. Specifically, subsequent changes to the overridden property in the original frame has no affect on the inherited value. The following steps demonstrate this behavior: 1. Select the label whose caption is ID in the Customer frame. Using the Object Inspector, change its Caption property to Customer No:. Now select the ID label for the Employee frame and change it to Employee ID:. 2. Press Shift-F12 and select the NameFrame. Change the Caption of this ID label to Identifier. 3. Return to the main form. Notice that the Caption properties of the labels have not changed to Identifier. They still use their overridden values. This effect is accomplished through information stored in the DFM file. Figure 7 displays a relevant part of the DFM file for this project: Figure 7. A DFM file containing property overrides for a frame instance Notice that information about all components contained within the frame whose property values have been changed appear in the frame's inline section of the DFM file. This section, however, only lists those values that have been changed. All other properties are assigned their values based either on the values set for the original frame (and which are stored in the frame's DFM file), or are designated as default values in the individual component's class declarations. Contained Object Event Handlers Objects contained within a frame may also have event handlers. While events are simply properties of a method pointer type, they are treated differently than other types of properties when it comes to overriding the default behavior defined for the frame. Let's begin by considering how an event handler is defined for a frame object. Consider the frame shown in Figure 8. (This code is found in the Frame2 project found in the download for this paper). This frame contains two buttons, one labeled Help and the other labeled Done. (Of course, these captions can be overridden in descendant frames). These buttons also have OnClick event handlers, shown in the following code segment: 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 begin if AParent = nil then Exit; AParent := AParent.GetParentComponent; end; TCustomForm(AParent).Close; end; Figure 8. A frame with components that have event handlers Just as the event handlers for objects on a form are published methods of that form's class, the event handlers of objects on a frame are published methods of that frame. (The code segment does not actually depict the fact that these methods are published, but they are declared in the default visibility section of the frame's class declaration, and the default visibility is published.) If you inspect the code associated with the Button2Click event handler, which is associated with the Done button, you will notice that the fact that the event handlers are associated with the frame introduces an interesting artifact. Specifically, Self is the frame, and not the form in which the frame is contained. Consequently, it is not possible to simply invoke the Close method from within this event handler to close the form. When an unqualified method invocation appears in code, the compiler assumes that you want it to apply to Self. Since a TFrame object does not have a Close method, the compiler generates an error if you simply use an unqualified call to Close. Since the frame in this example is designed to be embedded within a form, the event handler uses the GetParentComponent method of the frame to climb the containership hierarchy within which the frame is nested. Once a TCustomForm instance is found (which will either be a TForm descendant or a custom form based upon TCustomForm), that reference is used to invoke to containing form's Close method. Overridding Contained Object Event Handlers If you are familiar with event overriding in VFI you will recall that the IDE embeds a call to inherited from within an overridden event handler on a descendant form. You can then alter the generated code to either add additional behavior prior to, or following the call to inherited, conditionally invoke inherited, or omit the call altogether. Frame descendants do not use inherited when invoking the event handler for an object embedded on the parent frame. Instead, the ancestor frame's method is called directly. For example, if you place the TwoButtonFrame frame (shown Figure 8) onto a form and then double-click it, Delphi will generate the following code: procedure TForm1.TwoButtonFrame1Button2Click(Sender: Object); begin TwoButtonFrame1.Button2Click(Sender); end; In this generated code TwoButtonFrame1 is an instance of the TTwoButtonFrame (the defining frame class). Button2Click, as you saw in the earlier code segment, is the event handler for the Done button on that frame. As a result, what this code does is to invoke the defining classes riginal event handler, passing to it the Sender that was passed to the button on the frame instance. This means of event handling introduces another interesting feature. Specifically, in these situations, Sender is generally not a member of the Self object. Indeed, Sender is usually a member of the form object, while Self is the frame object. The following is an overridden event handler for a TwoButtonFrame instance that was placed on a form. Notice that in this case the original behavior is comment out, meaning that the new behavior completely replaces the originally defined behavior for the Done button. procedure TForm1.TwoButtonFrame1Button2Click(Sender: TObject);< begin with TForm2.Create(Self) do begin ShowModal; Release; end; // The following is the original, auto-generated code // TwoButtonFrame1.Button2Click(Sender); end; The Caption of this button was also overridden, so that it displays the text Start. Figure 9 shows the form on which this TwoButtonFrame descendant appears. Figure 9. This TwoButtonFrame instances overrides both the Caption and the OnClick event handler. Frames that Save Resources The form shown in Figure 9 actually contains two frames. We have already discussed the TwoButtonFrame frame. The second frame is the one that shows the company logo. This frame is named LogoFrame. The LogoFrame appears on more than one form in the FramDemo project. The alternative to using a frame to display the logo is to place an Image object on each form upon which you want the logo to appear. However, the use of a frame for this purpose significantly reduces the amount of resources that must be compiled into the .EXE, and therefore results in a smaller executable. The reason for this can be seen if you consider this segment of the DFM file for the form shown in the preceding figure: inline LogoFrame1: TLogoFrame Left = 6 Top = 6 Width = 211 Height = 182 inherited Image1: TImage Width = 211 Height = 182 end end If a TImage instance had been placed onto the form instead, the DFM file for the form would have had to contain the entire binary representation of the logo. Furthermore, each and every form containing one of these images would have repeated this resource. However, when a frame is used that resource is defined only once, as shown from this segment of the LogoFrame's DFM file: 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 = { 07544269746D6170D6540000424DD6540000000000007600000028000000F000 0000B40000000100040000000000605400000000000000000000100000000000 0000000000000000800000800000008080008000000080008000808000007F7F 7F00BFBFBF000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFF FF00000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000044446C6C767677776767 Note that this segment includes less than 1% of the entire hexadecimal representation of the binary resource. Simplifying Frame Usage Within a single, small project it is fairly easy to use the Frame component on the Standard page of the component palette. But for larger projects, or for situations where you want to use the same frame in multiple applications, you need something easier. Fortunately, Delphi permits you to place individual frames onto the Component Palette, permitting these frames to be used easily and repeatedly, without the extra steps required by the Frame component. A frame can also be placed into the Object Repository, permitting it to be copied or inherited from easily. Both of these techniques are described in the following sections. Adding a Frame to the Component Palette By placing a particular frame onto the Component Palette, you make its placement as simple as placing almost any other component. By comparison, using the Frame component on the Standard page of the component palette requires four steps, and limits you to placing frames already defined within your project. To place a particular frame onto the Component Palette, use the following steps: 1. Save your frame to disk. If you want to use this frame in multiple applications it is highly recommended that you save the frame to a directory that will not be deleted when you update Delphi. For example, create a folder named c:\Program Files\Borland\DelphiFrames and store your frames there (you will also want to add this directory to your Library search path using the Enviroment Options dialog box). 2. Select the frame and right-click it. Select Add to Palette. Delphi displays the Component Template Information dialog box. 3. Define the name of the frame component in the Component Name field, the page of the Component Palette on which you want the frame to appear in the Palette page field, and, if you have created a custom 24 x 24 pixel, 16-color icon for the frame, use the Change button to select this bitmap file. Click OK when done. Using a Frame from the Component Palette To use a frame previously placed on the Component Palette, use the following steps: 1. Select the page of the Component Palette onto which you saved the frame. 2. Select the frame's icon and drop it onto the form on which you want an instance of that frame to appear. Notice that this process required only two steps, while using the Frame component from the Standard page requires four steps. Adding a Frame to the Object Repository By adding a frame to the object repository you make it easy to copy it into a new project. Especially important is the ability to use the inheritance offered by the Object Repository to place an inherited frame into a new project, thereby maintaining the relationship between the frame and its ancestor. To add a frame to the Object Repository use the following steps: 1. Save your frame to disk. You can save this frame to any directory, including the same one to which you save frames that you add to the Component Palette, to Delphi's OBJREPOS directory, or to a shared directory. (Saving the frame to a shared directory is especially nice if you are using a shared object repository. This permits multiple developers to share one or more frames.) 2. Right-click the frame and select Add to Repository. Delphi responds by displaying the Add to Repository dialog box. 3. Fill out the Add to Repository dialog box just as you would for any template you are adding to the Object Repository. Click OK when done. Using a Frame from the Object Repository To use a frame from the Object Repository use the following steps: 1. Select File | New. 2. Select the page of the Object Repository to which you saved your frame template. 3. Select the icon for the frame and then click the Inherit radio button on the Object Repository dialog box. 4. Click OK to add an inherited version of the frame to your project. Note that if you use the Copy radio button instead of the Inherit radio button, the newly adding frame will be a copy of the original frame. This is useful when you want to create a new frame, but do not want to maintain a relationship between the copy and the original. Component Palette versus Object Repository Does is make a difference whether you place a frame that you want to reuse on the Component Palette or the Object Repository? The answer is a strong "Yes!". In most cases you will want to place frames that you want to use frequently onto the Component Palette. When you place a frame from the Component Palette you are always placing an instance of the frame class. You can then easily change the properties and event handlers of this instance, as described earlier in this paper. By comparison, placing a frame from the Object Repository creates a new class, not an instance. This new class is either a copy of the original, or a descendant, depending on which radio button you select on the Object Repository dialog box. If you want to use a frame in a project I personally think that it makes a great deal of sense to always place an instance, rather than define a new class for your frame. For this purpose, saving the frame to the Component Palette is the best approach. The one situation where you might want to use the Object Repository is when you are specifically creating hierarchies of frames, where each frame descendant introduces additional objects, methods, or event handlers. Here the inheritance offered by the Object Repository makes it easier for you to create each new descendant. However, once you have define the frame descendants that you want to use regularly, I would again suggest that you add these to the Component Palette to simplify their use. Turning Frames into True Components Frames, like other classes that descend from TComponent, can be added to a design-time package in order to place the component on the Component palette. This is an interesting thing to do to a frame class, but it is not something that you will always want to do. Once you have placed a frame onto the Component palette, it is no longer treated as a frame by the designer. Instead it is treated like a compound component. The primary difference is that with a frame you can modify the properties of the objects contained in the frame, but with a compound component you cannot. Because the power of frames lies in your ability to modify the frame at design time, you might be inclined to rule out ever adding a frame to the component palette. But there are times when adding a frame to the component palette makes a lot of sense. Once you have visually designed the frame, adding it to the component palette locks its features, preventing the component user from making changes to the encapsulated components. Also, once a frame has been added to the component palette, you can easily create an ActiveX wrapper for it. Summary A frame is a container class that permits you to visually design compound components. In addition, the design understands the relationship between a frame and the components it contains, permit you to override the properties and event handlers of objects that appear in frame instances. By including frames in your application, you can improve object reuse, reduce maintainance overhead, and make more efficient use of Windows resources. About the Author Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based training and consulting company. He is an award-winning, best-selling co-author of eighteen books, including Building Kylix Applications (2001, Osborne Media Group), Oracle JDeveloper (1998, Oracle Press), JBuilder Essentials (1998, Osborne/McGraw-Hill), Delphi In Depth (1996, Osborne/McGraw-Hill), and Programming Paradox 5 for Windows (1995, Sybex). Cary is also Contributing Editor of Delphi Informant Magazine, where he writes the column DBNavigator. He is a popular speaker at conferences, workshops, and training seminars throughout North America and Europe. Cary has a Ph.D. in Human Factors Psychology, specializing in human-computer interaction. You can contact Cary at cjensen@JensenDataSystems.com. The Jensen Data Systems, Inc. web site is at www.JensenDataSystems.com. Cary is available for onsite training in Delphi and Kylix. For information on available courses, please visit www.JensenDataSystems.com.