Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents
Bookmark and Share

Part I - Foundations
  Chapter 1 Delphi 7 and Its IDE
  Chapter 2 The Delphi Programming Language
  Chapter 3 The Run-Time Library
  Chapter 4 Core Library classes
  Chapter 5 Visual Controls
  Chapter 6 Building the User Interface
  Chapter 7 Working with Forms
Part II - Delphi Object-Oriented Architectures
  Chapter 8 The Architecture of Delphi Applications
  Chapter 9 Writing Delphi Components
  Chapter 10 Libraries and Packages
  Chapter 11 Modeling and OOP Programming (with ModelMaker)
  Chapter 12 From COM to COM+
Part III - Delphi Database-Oriented Architectures
  Chapter 13 Delphi's Database Architecture
  Chapter 14 Client/Server with dbExpress
  Chapter 15 Working with ADO
  Chapter 16 Multitier DataSnap Applications
  Chapter 17 Writing Database Components
  Chapter 18 Reporting with Rave
Part IV - Delphi, the Internet, and a .NET Preview
  Chapter 19 Internet Programming: Sockets and Indy
  Chapter 20 Web Programming with WebBroker and WebSnap
  Chapter 21 Web Programming with IntraWeb
  Chapter 22 Using XML Technologies
  Chapter 23 Web Services and SOAP
  Chapter 24 The Microsoft .NET Architecture from the Delphi Perspective
  Chapter 25 Delphi for .NET Preview: The Language and the RTL
       
  Appendix A Extra Delphi Tools by the Author
  Appendix B Extra Delphi Tools from Other Sources
  Appendix C Free Companion Books on Delphi
       
  Index    
  List of Figures    
  List of tables    
  List of Listings    
  List of Sidebars  

 
Previous Section Next Section

Understanding Frames

Chapter 1, "Delphi 7 And Its IDE," briefly discussed frames. You've seen that you can create a new frame, place components in it, write event handlers for the components, and then add the frame to a form. In other words, a frame is similar to a form, but it defines only a portion of a window, not a complete window. The interesting element of frames is that you can create multiple instances of a frame at design time, and you can modify the class and the instance at the same time. Thus frames are an effective tool for creating customizable composite controls at design time—something close to a visual component-building tool.

In visual form inheritance, you can work on both a base form and a derived form at design time, and any changes you make to the base form are propagated to the derived one (unless doing so overrides a property or event). With frames, you work on a class (as usual in Delphi), but you can also customize one or more instances of the class at design time. When you work on a form, you cannot change a property of the TForm1 class for a specific instance of this form, and not the others, at design time. With frames, you can.

Once you realize you are working with a class and one or more of its instances at design time, there is nothing more to understand about frames. In practice, frames are useful when you want to use the same group of components in multiple forms within an application. In this case, you can customize each instance at design time. You could already do this with component templates, but component templates were based on the concept of copying and pasting components and their code. You could not change the original definition of the template and see the effect every place it was used. With frames (and, in a different way, with visual form inheritance), changes to the original version (the class) are reflected in the copies (the instances).

Let's discuss a few more elements of frames with an example called Frames2. This program has a frame with a list box, an edit box, and three buttons with code operating on the components. The frame also has a bevel aligned to its client area, because frames have no border. Of course, the frame has also a corresponding class, which looks like a form class:

type
  TFrameList = class(TFrame)
    ListBox: TListBox;
    Edit: TEdit;
    btnAdd: TButton;
    btnRemove: TButton;
    btnClear: TButton;
    Bevel: TBevel;
    procedure btnAddClick(Sender: TObject);
    procedure btnRemoveClick(Sender: TObject);
    procedure btnClearClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

What is different from a form is that you can add the frame to a form. I've used two instances of the frame in the example (as you can see in Figure 8.9) and modified the behavior slightly. The first instance of the frame has the list box items sorted. When you change a property of a component of a frame, the DFM file of the hosting form will list the differences, as it does with visual form inheritance:

object FormFrames: TFormFrames
  Caption = 'Frames2'
  inline FrameList1: TFrameList
    Left = 8
    Top = 8
    inherited ListBox: TListBox
      Sorted = True
    end
  end
  inline FrameList2: TFrameList
    Left = 232
    Top = 8
    inherited btnClear: TButton
      OnClick = FrameList2btnClearClick
    end
  end
end
Click To expand
Figure 8.9: A frame and two instances of it at design time, in the Frames2 example

As you can see from the listing, the DFM file for a form that has frames uses a specific DFM keyword, inline. The references to the modified components of the frame, however, use the inherited keyword, although this term is used with an extended meaning: In this case, inherited doesn't refer to a base class you are inheriting from, but to the class from which you are instancing (or inheriting) an object. It was a good idea, though, to use an existing feature of visual form inheritance and apply it to the new context. This approach lets you use the Revert to Inherited command of the Object Inspector or of the form to cancel the changes and get back to the default value of properties.

Notice also that unmodified components of the frame class are not listed in the DFM file of the form using the frame; and, the form has two frames with different names, but the com-ponents on the two frames have the same name. These components are not owned by the form, but are owned by the frame. This implies that the form has to reference those components through the frame, as you can see in the code for the buttons that copy items from one list box to the other:

procedure TFormFrames.btnLeftClick(Sender: TObject);
begin
  FrameList1.ListBox.Items.AddStrings (FrameList2.ListBox.Items);
end;

Finally, in addition to modifying properties of any instance of a frame, you can change the code of any of its event handlers. If you double-click one of the frame's buttons while working on the form (not on the stand-alone frame), Delphi will generate this code for you:

procedure TFormFrames.FrameList2btnClearClick(Sender: TObject);
begin
  FrameList2.btnClearClick(Sender);
end;

The line of code automatically added by Delphi corresponds to a call to the inherited event handler of the base class in visual form inheritance. This time, however, to get the default behavior of the frame, you need to call an event handler and apply it to a specific instance—the frame object itself. The current form doesn't include this event handler and knows nothing about it. Whether you leave this call in place or remove it depends on the effect you are looking for.

Tip 

Note that because the event handler has some code, leaving it as Delphi generated it and saving the form won't remove it as usual: It isn't empty! Instead, if you want to omit the default code for an event, you need to add at least a comment to it to avoid the system removing it automatically.

Frames and Pages

When a dialog box has many pages full of controls, the code underlying the form becomes very complex because all the controls and methods are declared in a single form. In addition, creating all these components (and initializing them) might delay the display of the dialog box. Frames don't reduce the construction and initialization time of equivalently loaded forms; quite the contrary, because loading frames is more complicated for the streaming system than loading simple components. However, using frames, you can load only the visible pages of a multipage dialog box, reducing the initial load time, which is what the user perceives.

Frames can solve both of these issues. You can easily divide the code of a single complex form into one frame per page. The form will host all the frames in a PageControl. This approach yields simpler, more focused units and makes it easier to reuse a specific page in a different dialog box or application. Reusing a single page of a PageControl without using a frame or an embedded form is far from simple. (For an alternative approach, see the sidebar "Forms in Pages.")

As an example of this approach, I've built the FramePag example. It has some frames placed inside the three pages of a PageControl, as you can see in Figure 8.10 by looking at the Object TreeView on the side of the design-time form. All the frames are aligned to the client area, using the entire surface of the tab sheet (the page) hosting them. Two of the pages have the same frame, but the two instances of the frame have some differences at design time. The Frame3 frame in the example has a list box populated with a text file at startup, and it has buttons to modify the items in the list and save them to a file. The filename is placed in a label so you can easily select a file for the frame at design time by changing the Caption of the label.

Click To expand
Figure 8.10:   Each page of the FramePag example contains a frame, thus separating the code of this complex form into more manageable chunks.

Being able to use multiple instances of a frame is one of the reasons this technique was introduced, and customizing the frame at design time is even more important. Because adding properties to a frame and making them available at design time requires some customized and complex code, it is nice to use a component to host these custom values. You have the option of hiding these components (such as the label in this example) if they don't pertain to the user interface.

In the example, you need to load the file when the frame instance is created. Because frames have no OnCreate event, your best choice is probably to override the CreateWnd method. Writing a custom constructor doesn't work, because it is executed too early—before the specific label text is available. In the CreateWnd method, you load the list box content from a file.

Note 

When questioned about the issue of the missing OnCreate event handler for frames, Borland R&D members have stated that they could not fire it in correspondence with the wm_Create message, because it happens with forms. The creation of the frame window (as is true for most controls) is delayed for performance reasons. More trouble happens in the case of inheritance among forms holding frames, so to avoid problems, this features has been disabled—programmers can write the code they deem reasonable.

Multiple Frames with No Pages

Another approach avoids creating all the pages along with the form hosting them, by leaving the PageControl empty and creating the frames only when a page is displayed. When you have frames on multiple pages of a PageControl, the windows for the frames are created only when they are first displayed, as you can find out by placing a breakpoint in the creation code of the previous example.

As an even more radical approach, you can get rid of the page controls and use a TabControl. Used this way, the tab has no connected tab sheets (or pages) and can display only one set of information at a time. For this reason, you must create the current frame and either destroy the previous one or hide it by setting its Visible property to False or calling the new frame's BringToFront. Although this sounds like a lot of work, in a large application this technique can be worth it because of the reduced resource and memory usage you can obtain.

To demonstrate this approach, I've built the FrameTab example, which is similar to the previous one but it is based on a TabControl and dynamically created frames. The main form, visible at run time in Figure 8.11, has a TabControl with one page for each frame:

Click To expand
Figure 8.11: The first page of the FrameTab example at run time. The frame inside the tab is created at run time.
object Form1: TForm1
  Caption = 'Frame Pages'
  OnCreate = FormCreate
  object Button1: TButton...
  object Button2: TButton...
  object Tab: TTabControl
    Anchors = [akLeft, akTop, akRight, akBottom]
    Tabs.Strings = ( 'Frame2' 'Frame3' )
    OnChange = TabChange
  end
end

I've given each tab a caption corresponding to the name of the frame, because I'll use this information to create the new pages. When the form is created, and whenever the user changes the active tab, the program gets the tab's current caption and passes it to the custom ShowFrame method. The method's code checks whether the requested frame already exists (frame names in this example follow the Delphi standard of having a number appended to the class name) and then brings it to the front. If the frame doesn't exist, the method uses the frame name to find the related frame class, creates an object of that class, and assigns a few properties to it. The code makes extensive use of class references and dynamic creation techniques:

type
  TFrameClass = class of TFrame;
   
procedure TForm1.ShowFrame(FrameName: string);
var
  Frame: TFrame;
  FrameClass: TFrameClass;
begin
  Frame := FindComponent (FrameName + '1') as TFrame;
  if not Assigned (Frame) then
  begin
    FrameClass := TFrameClass (FindClass ('T' + FrameName));
    Frame := FrameClass.Create (Self);
    Frame.Parent := Tab;
    Frame.Visible := True;
    Frame.Name := FrameName + '1';
  end;
  Frame.BringToFront;
end;

To make this code work, remember to add a call to RegisterClass in the initialization section of each unit defining a frame.


 
Previous Section Next Section


 


 


Copyright © 2004-2016 "Delphi Sources". Delphi Programming Guide
     Twitter     Facebook