|
Understanding FramesChapter 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 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.
Frames and PagesWhen 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. 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.
Multiple Frames with No PagesAnother 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: 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. |
|
Copyright © 2004-2024 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide |
|