在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
SuperComponents Copyright © 1996, 1997 Mark Miller What are SuperComponents? SuperComponents, also known as aggregate or compound components, are collections of existing sub-components and their relationships combined into a single component. The collections are typically arranged inside a container parent component that manages the visual layout of the sub-components.
Advantages SuperComponents take all of the advantages of normal components and build on them. Rapid application development, object reuse, and user interface consistency are all benefits. Code shrinkage is another. If you have two or more forms containing similar component relationships (either inside an application or among several), that component relationship is tied to each form with - you guessed it - code. That binding Object Pascal code is duplicated with each form the component group lies on. By fusing the component collections into a single component, the code to manage the sub-component relationships disappears from the user interface code. The code for each form more truly represents its functionality; it's easier to read and maintain.
Another benefit achieved through the use of SuperComponents is multiple form inheritance. You can take parts of different forms (SuperComponents) and group them together into a new form. This leads to even faster prototyping and user interface design than normally experienced with Delphi.
SuperComponents can simplify the properties needed to control the collection. Instead of dealing with properties and events for each component in the collection, the SuperComponent only exposes the properties and events it needs, thereby reducing the design-time complexity of the component.
As mentioned earlier, SuperComponents can embody the rules of the relationship among the subcomponents. This relationship might represent an algorithm or solution to a problem, or it might support state changes not present in any of the individual parts. Algorithm parameters and/or hooks to state changes can be easily exposed through new properties and events.
Finally, SuperComponents can be thought of as mini-apps. They are components themselves, and as such, lend themselves to a well-defined segregation from the rest of the project they are contained in. In team development environments, this means that developers most familiar with the problem and solution (whatever they be) can design the SuperComponents so that less-experienced developers can piece together the application with these building blocks.
Disadvantages There are two disadvantages to making SuperComponents. First, visual SuperComponents require an additional Windows handle. Second, there is a small amount of overhead in the container parent component that holds all the sub-components.
Before We Start It is useful to distinguish among the different roles developers and users take when dealing with components. There are three that we are concerned with:
•Application Users (a.k.a. "users") will use applications built with the components we make. •Application Developers (a.k.a. "developers") will build applications using our components. •Component Writers (that's us!) will create components that will make is easier for developers to build applications and easier for users to use. It's important to note that as a component writer, you're designing for two customers.
Visual Containment The steps to building a SuperComponent are roughly as follows:
1. Design the layout of your components inside a form in Delphi, placing all the components inside a TPanel (or a descendant thereof). 2. Select and copy the panel and paste it into a text file. 3. Replace all instances of " = " with " := ", and add a semi-colon to the end of each line. 4. Convert all DFM "object" declaration lines to appropriate object constructor code, setting the parent of all visual controls to the container panel. 5. Clean up any remaining code. Bitmaps will need to be placed in resource files. 6. Place this new pascal code inside a create constructor for your component. Within the constructor , group object sections under the appropriate sub-component creator.
Let's use an example to illustrate these steps. We'll make an OK/Cancel/Help button combination. Inside Delphi, the layout looks like this (Note: the TPanel's border is set to none):
Selecting, copying and pasting the above collection into a text file yields the following:
object Panel1: TPanel Left = 114 Top = 10 Width = 75 Height = 95 BevelOuter = bvNone TabOrder = 0 object OKButton: TButton Left = 0 Top = 0 Width = 75 Height = 25 Caption = 'OK' Default = True ModalResult = 1 TabOrder = 0 end object CancelButton: TButton Left = 0 Top = 35 Width = 75 Height = 25 Cancel = True Caption = 'Cancel' ModalResult = 2 TabOrder = 1 end object HelpButton: TButton Left = 0 Top = 70 Width = 75 Height = 25 Caption = 'Help' TabOrder = 2 end end
This is the text representation of our SuperComponent group. Next, we need to convert this text to something that looks a little more like Object Pascal:
object Panel1: TPanel Left := 114; Top := 10; Width := 75; Height := 95; BevelOuter := bvNone; TabOrder := 0; object OKButton: TButton Left := 0; Top := 0; Width := 75; Height := 25; Caption := 'OK'; Default := True; ModalResult := 1; TabOrder := 0; end object CancelButton: TButton Left := 0; Top := 35; Width := 75; Height := 25; Cancel := True; Caption := 'Cancel'; ModalResult := 2; TabOrder := 1; end object HelpButton: TButton Left := 0; Top := 70; Width := 75; Height := 25; Caption := 'Help'; TabOrder := 2; end end
Now we're getting closer to what we want. The next step is to transfer the panel initialization to the component's constructor. We'll create the embedded controls here, too:
constructor TOkCancelHelp.Create(AOwner: TComponent); { Creates an object of type TOkCancelHelp, and initializes properties. } begin inherited Create(AOwner); Width := 75; Height := 95; BevelOuter := bvNone; TabOrder := 0;
OKButton := TButton.Create(Self); OKButton.Parent := Self;
CancelButton := TButton.Create(Self); CancelButton.Parent := Self;
HelpButton := TButton.Create(Self); HelpButton.Parent := Self; end; { Create }
The three buttons, OKButton, CancelButton, and HelpButton need to be declared as fields of our new component. Our component's declaration looks like this:
type TOkCancelHelp = class(TPanel) OKButton: TButton; CancelButton: TButton; HelpButton: TButton; private { Private declarations } protected { Protected declarations } public { Public declarations } constructor Create(AOwner: TComponent); override; published { Published properties and events } end; { TOkCancelHelp } Now let's take that converted DFM text and initialize the three buttons. Although you can do this inside our component's constructor, some VCL sub-component initialization code depends on a windows handle existing in its parent. At the time when our new SuperComponent is created (inside the Create constructor), this handle does not yet exist. So we need to find a method we can override in the TPanel that is called before the component is displayed and before its loaded method is called, but after a windows handle is assigned to the TPanel. The CreateWindowHandle method is the best place to do this. An override of this method, with the DFM initialization code inserted, looks like this:
procedure TOkCancelHelp.CreateWindowHandle(const Params: TCreateParams); { Calls inherited CreateWindowHandle and initializes subcomponents. } begin inherited CreateWindowHandle(Params);
with OKButton do begin Left := 0; Top := 0; Width := 75; Height := 25; Caption := 'OK'; Default := True; ModalResult := 1; TabOrder := 0; end; { OKButton }
with CancelButton do begin Left := 0; Top := 35; Width := 75; Height := 25; Cancel := True; Caption := 'Cancel'; ModalResult := 2; TabOrder := 1; end; { CancelButton }
with HelpButton do begin Left := 0; Top := 70; Width := 75; Height := 25; Caption := 'Help'; TabOrder := 2; end; { HelpButton } end; { CreateWindowHandle }
And the component declaration now looks like this:
type TOkCancelHelp = class(TPanel) OKButton: TButton; CancelButton: TButton; HelpButton: TButton; private { Private declarations } protected { Protected declarations } procedure CreateWindowHandle(const Params: TCreateParams); override; public { Public declarations } constructor Create(AOwner: TComponent); override; published { Published properties and events } end; { TOkCancelHelp }
Finally, we need to add a register method so we can place our new component onto Delphi's component palette:
procedure Register; begin RegisterComponents('CDK', [TOkCancelHelp]); end; { Register }
Exposing Sub-Component Properties Grouping components together to create new SuperComponents is a pretty neat trick. One advantage is that it allows you to isolate many of the grouped components' properties from application developers. In fact unless you explicitly state otherwise, all of the grouped components properties will be hidden from developers!
So how do you expose a sub-component property? You need to create two transfer methods that transfer the sub-component's properties to the outside world.
For example, in our TOkCancelHelp component, it might be useful to expose the caption property so application writers can change it (e.g., for application development in a language other than English). Our transfer methods look a lot like the standard property Get and Set methods we're already familiar with. Here's the declaration for the OK button's caption property:
type TOkCancelHelp = class(TPanel) . . . private { Private declarations } procedure SetCaption_OKButton(newValue: TCaption); function GetCaption_OKButton: TCaption; . . . published { Published properties and events } property Caption_OKButton: TCaption read GetCaption_OKButton write SetCaption_OKButton; end;
These transfer methods pass the property values to and from the subcomponents. Their implementation looks like this:
function TOkCancelHelp.GetCaption_OKButton: TCaption; { Returns the Caption property from the OKButton subcomponent. } begin result := OKButton.Caption; end; { GetCaption_OKButton }
procedure TOkCancelHelp.SetCaption_OKButton(newValue: boolean); { Sets the OKButton subcomponent's Caption property to newValue. } begin OKButton.Caption := newValue; end; { SetCaption_OKButton }
You may notice that there is no field variable for this property. All sub-component properties rely on the sub-components themselves for storage. Also notice that unlike most Set methods, this one doesn't check to see if the internal value is different from the passed-in newValue. We let the sub-component handle this check if necessary.
Exposing Sub-Component Events Just as you would need to expose a sub-component's properties, you might also need to expose its events. The theory behind exposing sub-component events is similar to that used to expose properties. The difference is that with events, we need to store the event handler in a field variable (just like we would for a normal event). Additionally we have to hook in to any sub-componenent event that we want to make available to our component users, which means creating our own event handler for it. Event handlers must be assigned dynamically when the component is first created. The declaration for an exposed event and the event handler that surfaces it looks like this:
type TOkCancelHelp = class(TPanel) . . . private { Private declarations } FOnClick_OKButton: TNotifyEvent; . . . procedure Click_OKButtonTransfer(Sender: TObject); { TNotifyEvent } published { Published properties and events } . . . property OnClick_OKButton: TNotifyEvent read FOnClick_OKButton write FOnClick_OKButton; end;
Here, Click_OKButtonTransfer acts as the event handler. Notice that its type, TNotifyEvent, matches the expected type for the OnClick event (TNotifyEvent). The implementation for the transfer method looks like this:
procedure TOkCancelHelp.Click_OKButtonTransfer(Sender: TObject); { Transfers the OKButton OnClick event to the outside world. } begin if assigned(FOnClick_OKButton) then FOnClick_OKButton(Self); { Substitute Self for subcomponent's Sender. } end; { Click_OKButtonTransfer }
If you've triggered events before, you probably recognize this code. The if-clause checks to see if the event is assigned (typically performed via the Object Inspector at design-time), and if so calls it, passing a reference to itself (the SuperComponent) to the component user's event handler. So, the sub-component's event is handled by our transfer method, which in turn passes the event to a component user's event handler (that's right -- two event handlers for each event!). To hook up this chain of events, all we do is dynamically assign the event transfer method to the sub-component's event. We do this in the overridden CreateWindowHandle method:
procedure TOkCancelHelp.CreateWindowHandle(const Params: TCreateParams); { Calls inherited CreateWindowHandle and initializes subcomponents. } begin inherited CreateWindowHandle(Params);
with OKButton do begin . . . OnClick := Click_OKButtonTransfer; end; { OKButton } . . . end; { CreateWindowHandle }
Hooking in to Sub-Component Events Sometimes you want to respond to a sub-component event without exposing it. The steps involved here are similar to those you'd follow to expose a sub-component event, except you don't declare the event (and you don't need the corresponding event field variable).
type TOkCancelHelp = class(TPanel) . . . private { Private declarations } . . . procedure Click_CancelButtonHandler(Sender: TObject); { TNotifyEvent } published . . . end;
The handler looks like this:
procedure TOkCancelHelp.Click_CancelButtonHandler(Sender: TObject); { Handles the CancelButton OnClick event. } begin { Place your event-handling code here. } end; { Click_CancelButtonHandler }
We glue it all together by dynamically assigning our handler to the sub-component event, just as we do when we want to expose a sub-component event:
procedure TOkCancelHelp.CreateWindowHandle(const Params: TCreateParams); { Calls inherited CreateWindowHandle and initializes subcomponents. } begin inherited CreateWindowHandle(Params); . . . with CancelButton do begin . . . OnClick := Click_CancelButtonHandler; end; { CancelButton } . . . end; { CreateWindowHandle }
Summary SuperComponents promote consistency and reuse. They can embody commonly-used configurations of controls, dramatically cutting development time and slashing code size. And the techniques involved here are not difficult to master.
Recommended Reading The following books were current at the time of this writing. Make sure you check for the most recent edition.
Developing Custom Delphi Components by Ray Konopka; edited by Jeff Duntemann Coriolis Group Books (800) 410-0192 or (602) 483-0192 585 pages ISBN 1-883577-47-0
This book is highly recommended if you need additional information on component building or building business components. It is filled with useful information and excellent examples. The explanations are clear and easy to understand. In addition, Ray Konopka’s TRzBusinessComponent, included with the CDK, is presented and detailed in his book.
Secrets of Delphi 2 -- Exposing Undocumented Features of Delphi by Ray Lischner Waite Group Press 831 pages ISBN 1-57169-026-3
This is an amazing book, packed with valuable information you can’t get anywhere else. Also contains excellent coverage of property editors, component editors, and other advanced component-building topics.
The Delphi Magazine Edited by Chris J G Frizelle iTec Publishing To subscribe in the UK: Tel/Fax: +44 (0) 181 460 0650 Email: [email protected]
To subscribe in the US: Phone: (802) 244-7820 Email: [email protected]
This magazine consistently contains excellent technical articles on Delphi-related subjects. Also contains a monthly column by Bob Swart (a.k.a. Dr. Bob) on component building in Delphi.
Design Patterns--Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides Foreword by Grady Booch Addison-Wesley Publishing Company 395 pages ISBN 0-201-63361-2
This book organizes and presents a catalog of proven object-oriented solutions for architecting complex systems that you can apply to your own specific applications. These design constructs are labeled, allowing your development team to share a common vocabulary. The CDK help file references two of the design patterns in this book: the Proxy pattern and the Template Method pattern. The examples are in C++, but the patterns apply to all programming languages.
Component Writer’s Guide by Borland International, Inc. (408) 431-1000 156 pages
The Component Writers Guide is a lean but important resource for learning about how to create working components and to ensure that components you write are well-behaved parts of the Delphi environment. The book guides you to writing components that fit in well with any Delphi application.
About the Author Mark Miller is the lead software engineer at Eagle Software. Mark holds the primary vision for the Component Developer Kit for Delphi (the CDK) reAct, a component testing tool for Delphi, as well as several other Delphi add-ons currently in development. He has been programming in Pascal, Object Pascal, and Delphi for nearly 16 years.
He can be reached at [email protected], or via CompuServe at 76043,2422. |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论