MASTERING DELPHI 6 phần 5 pptx

108 210 0
MASTERING DELPHI 6 phần 5 pptx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

426 Building Your First Component Building components is an important activity for Delphi programmers. The basic idea is that any time you need the same behavior in two different places in an application, or in two differ- ent applications, you can place the shared code inside a class—or, even better, a component. In this section I’ll just introduce a couple of simple components, to give you an idea of the steps required to build one and to show you different things you can do to customize an existing component with a limited amount of code. The Fonts Combo Box Many applications have a toolbar with a combo box you can use to select a font. If you often use a customized combo box like this, why not turn it into a component? It would probably take less than a minute. To begin, close any active projects in the Delphi environment and start the Component Wizard, either by choosing Component ➢ New Component or by selecting File ➢ New to open the Object Repository and then choosing the Component in the New page. As you can see in Figure 11.1, the Component Wizard requires the following information: • The name of the ancestor type: the component class you want to inherit from. In this case we can use TComboBox. • The name of the class of the new component you are building; we can use TMdFontCombo. • The page of the Component Palette where you want to display the new component, which can be a new or an existing page. We can create a new page, called Md. FIGURE 11.1: Defining the new TMdFont- Combo component with the Component Wizard Chapter 11 • Creating Components Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 427 • The filename of the Pascal unit where Delphi will place the source code of the new component; we can type MdFontBox. • The current search path (which should be set up automatically). Click the OK button, and the Component Wizard will generate the following simple Pas- cal source file with the structure of your component. The Install button can be used to install the component in a package immediately. Let’s look at the code first, Listing 11.1, and then discuss the installation. ➲ Listing 11.1: Code of the TMdFontCombo, generated by the Component Wizard unit MdFontBox; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMdFontCombo = class (TComboBox) private { Private declarations } protected { Protected declarations } public { Public declarations } published { Published declarations } end; procedure Register; implementation procedure Register; begin RegisterComponents(‘Md’, [TMdFontCombo]); end; end. One of the key elements of this listing is the class definition, which begins by indicating the parent class. The only other relevant portion is the Register procedure. In fact, you can see that the Component Wizard does very little work. Building Your First Component Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 428 WARNING Starting with Delphi 4, the Register procedure must be written with an uppercase R. This requirement is apparently imposed for C++Builder compatibility (identifiers in C++ are case-sensitive). TIP Use a naming convention when building components. All the components installed in Delphi should have different class names. For this reason most Delphi component developers have chosen to add a two- or three-letter signature prefix to the names of their components. I’ve done the same, using Md (for Mastering Delphi) to identify components built in this book. The advantage of this approach is that you can install my TMdFontCombo component even if you’ve already installed a component named TFontCombo. Notice that the unit names must also be unique for all the components installed in the system, so I’ve applied the same prefix to the unit names. That’s all it takes to build a component. Of course, in this example there isn’t a lot of code. We need only copy all the system fonts to the Items property of the combo box at startup. To accomplish this, we might try to override the Create method in the class declaration, adding the statement Items := Screen.Fonts. However, this is not the correct approach. The prob- lem is that we cannot access the combo box’s Items property before the window handle of the component is available; the component cannot have a window handle until its Parent prop- erty is set; and that property isn’t set in the constructor, but later on. For this reason, instead of assigning the new strings in the Create constructor, we must perform this operation in the CreateWnd procedure, which is called to create the window control after the component is constructed, its Parent property is set, and its window handle is available. Again, we execute the default behavior, and then we can write our custom code. I could have skipped the Create constructor and written all the code in CreateWnd, but I decided to use both startup methods to demonstrate the difference between them. Here is the declaration of the component class: type TMdFontCombo = class (TComboBox) private FChangeFormFont: Boolean; procedure SetChangeFormFont(const Value: Boolean); public constructor Create (AOwner: TComponent); override; procedure CreateWnd; override; procedure Change; override; published property Style default csDropDownList; property Items stored False; Chapter 11 • Creating Components Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 429 property ChangeFormFont: Boolean read FChangeFormFont write SetChangeFormFont default True; end; And here is the source code of its two methods executed at startup: constructor TMdFontCombo.Create (AOwner: TComponent); begin inherited Create (AOwner); Style := csDropDownList; FChangeFormFont := True; end; procedure TMdFontCombo.CreateWnd; begin inherited CreateWnd; Items.Assign (Screen.Fonts); // grab the default font of the owner form if FChangeFormFont and Assigned (Owner) and (Owner is TForm) then ItemIndex := Items.IndexOf ((Owner as TForm).Font.Name); end; Notice that besides giving a new value to the component’s Style property, in the Create method, I’ve redefined this property by setting a value with the default keyword. We have to do both operations because adding the default keyword to a property declaration has no direct effect on the property’s initial value. Why specify a property’s default value then? Because prop- erties that have a value equal to the default are not streamed with the form definition (and they don’t appear in the textual description of the form, the DFM file). The default keyword tells the streaming code that the component initialization code will set the value of that property. TIP Why is it important to specify a default value for a published property? To reduce the size of the DFM files and, ultimately, the size of the executable files (which include the DFM files). The other redefined property, Items, is set as a property that should not be saved to the DFM file at all, regardless of the actual value. This is obtained with the stored directive fol- lowed by the value False. The component and its window are going to be created again when the program starts, so it doesn’t make any sense to save in the DFM file information that will be discarded later on (to be replaced with the new list of fonts). NOTE We could have even written the code of the CreateWnd method to copy the fonts to the combo box items only at run time. This can be done by using statements such as if not (csDesigning in ComponentState) then…. But for this first component we are building, the less efficient but more straightforward method described above offers a clearer illustration of the basic procedure. Building Your First Component Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 430 The third property, ChangeFormFont, is not inherited but introduced by the component. It is used to determine whether the current font selection of the combo box should determine the font of the form hosting the component. Again this property is declared with a default value, set in the constructor. The ChangeFormFont property is used in the code of the CreateWnd method, shown before, to set up the initial selection of the combo depending on the font of the form hosting the component. This is generally the Owner of the component, although I could have also walked the Parent tree looking for a form component. This code isn’t perfect, but the Assigned and is tests provide some extra safety. The ChangeFormFont property and the same if test play a key role in the Changed method, which in the base class triggers the OnChange event. By overriding this method we provide a default behavior, which can be disabled by toggling the value of the property, but also allow the execution of the OnChange event, so that users of this class can fully customize its behavior. The final method, SetChangeFormFont, has been modified to refresh the form’s font in case the property is being turned on. This is the complete code: procedure TMdFontCombo.Change; begin // assign the font to the owner form if FChangeFormFont and Assigned (Owner) and (Owner is TForm) then TForm (Owner).Font.Name := Text; inherited; end; procedure TMdFontCombo.SetChangeFormFont(const Value: Boolean); begin FChangeFormFont := Value; // refresh font if FChangeFormFont then Change; end; Creating a Package Now we have to install the component in the environment, using a package. For this example, we can either create a new package or use an existing one, like the default user’s package. In each case, choose the Component ➢ Install Component menu command. The resulting dialog box has a page to install the component into an existing package, and a page to create a new package. In this last case, simply type in a filename and a description for the package. Clicking OK opens the Package Editor (see Figure 11.2), which has two parts: • The Contains list indicates the components included in the package (or, to be more precise, the units defining those components). Chapter 11 • Creating Components Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 431 • The Requires list indicates the packages required by this package. Your package will generally require the rtl and vcl packages (the main run-time library package and core VCL package), but it might also need the vcldb package (which includes most of the database-related classes) if the components of the new package do any database-related operations. NOTE Package names in Delphi 6 aren’t version specific any more, even if the compiled packages still have a version number in the filename. See the section “Project and Library Names in Delphi 6” in Chapter 12, “Libraries and Packages,” for more details on how this is technically achieved. If you add the component to the new package we’ve just defined, and then simply compile the package and install it (using the two corresponding toolbar buttons of the package edi- tor), you’ll immediately see the new component show up in the Md page of the Component Palette. The Register procedure of the component unit file told Delphi where to install the new component. By default, the bitmap used will be the same as the parent class, because we haven’t provided a custom bitmap (we will do this in later examples). Notice also that if you move the mouse over the new component, Delphi will display as a hint the name of the class without the initial letter T. What’s Behind a Package? What is behind the package we’ve just built? The Package Editor basically generates the source code for the package project: a special kind of DLL built in Delphi. The package pro- ject is saved in a file with the DPK (for Delphi PacKage) extension. A typical package project looks like this: package MdPack; {$R *.RES} FIGURE 11.2: The Package Editor Building Your First Component Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 432 {$ALIGN ON} {$BOOLEVAL OFF} {$DEBUGINFO ON} {$DESCRIPTION ‘Mastering Delphi Package’} {$IMPLICITBUILD ON} requires vcl; contains MdFontBox in ‘MdFontBox.pas’; end. As you can see, Delphi uses specific language keywords for packages: the first is the pack- age keyword (which is similar to the library keyword I’ll discuss in the next chapter). This keyword introduces a new package project. Then comes a list with all the compiler options, some of which I’ve omitted from the listing. Usually the options for a Delphi project are stored in a separate file; packages, by contrast, include all the compiler options directly in their source code. Among the compiler options there is a DESCRIPTION compiler directive, used to make the package description available to the Delphi environment. In fact, after you’ve installed a new package, its description will be shown in the Packages page of the Project Options dialog box, a page you can also activate by selecting the Component ➢ Install Packages menu item. This dialog box is shown in Figure 11.3. FIGURE 11.3: The Project Options for packages. You can see the new package we’ve just created. Chapter 11 • Creating Components Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 433 Besides common directives like the DESCRIPTION one, there are other compiler directives specific to packages. The most common of these options are easily accessible through the Options button of the Package Editor. After this list of options come the requires and contains keywords, which list the items displayed visually in the two pages of the Package Editor. Again, the first is the list of packages required by the current one, and the second is a list of the units installed by this package. What is the technical effect of building a package? Besides the DPK file with the source code, Delphi generates a BPL file with the dynamic link version of the package and a DCP file with the symbol information. In practice, this DCP file is the sum of the symbol informa- tion of the DCU files of the units contained in the package. At design time, Delphi requires both the BPL and DCP files, because the first has the actual code of the components created on the design form and the symbol information required by the code insight technology. If you link the package dynamically (using it as a run-time pack- age), the DCP file will also be used by the linker, and the BPL file should be shipped along with the main executable file of the application. If you instead link the package statically, the linker refers to the DCU files, and you’ll need to distribute only the final executable file. For this reason, as a component designer, you should generally distribute at least the BPL file, the DCP file, and the DCU files of the units contained in the package and any correspond- ing DFM files, plus a Help file. As an option, of course, you might also make available the source code files of the package units (the PAS files) and of the package itself (the DPK file). WARNING Delphi, by default, will place all the compiled package files (BPL and DCP) not in the folder of the package source code but under the \Projects\BPL folder. This is done so that the IDE can easily locate them, and creates no particular problem. When you have to compile a project using components declared on those packages, though, Delphi might complain that it cannot find the corresponding DCU files, which are stored in the package source code folder. This problem can be solved by indicating the package source code folder in the Library Path (in the Environment Options, which affect all projects) or by indicating it in the Search Path of the current project (in the Project Options). If you choose the first approach, placing different components and packages in a single folder might result in a real time-saver. Installing the Components of This Chapter Having built our first package, we can now start using the component we’ve added to it. Before we do so, however, I should mention that I’ve extended the MdPack package to include all of the components we are going to build in this chapter, including different versions of the same component. I suggest you install this package. The best approach is to copy it into a directory of your path, so that it will be available both to the Delphi environment and to the programs you build with it. I’ve collected all the component source code files and the package definition Building Your First Component Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 434 in a single subdirectory, called MdPack. This allows the Delphi environment, or a specific pro- ject, to refer only to one directory when looking for the DCU files of this package. As sug- gested in the warning above, I could have collected all of the components presented in the book in a single folder on the companion CD, but I decided that keeping the chapter-based organization was actually more understandable for readers. Remember, anyway, that if you compile an application using the packages as run-time DLLs, you’ll need to install these new libraries on your clients’ computers. If you instead compile the programs by statically linking the package, the DLL will be required only by the development environment and not by the users of your applications. Using the Font Combo Box Now you can create a new Delphi program to test the Font combo box. Move to the Com- ponent Palette, select the new component, and add it to a new form. A traditional-looking combo box will appear. However, if you open the Items property editor, you’ll see a list of the fonts installed on your computer. To build a simple example, I’ve added a Memo component to the form with some text inside it. By leaving the ChangeFormFont property on, you don’t need to write any other code to the program, as you’ll see in the example. As an alternative I could have turned off the property and handled the OnChange event of the component, with code like this: Memo1.Font.Name := MdFontCombo1.Text; The aim of this simple program is only to test the behavior of the new component we have built. The component is still not very useful—we could have added a couple of lines of code to a form to obtain the same effect—but looking at a couple of simple components should help you get an idea of what is involved in component building. Creating Compound Components The next component I want to focus on is a digital clock. This example has some interesting features. First, it embeds a component (a Timer) in another component; second, it shows the live-data approach. NOTE The first feature has become even more relevant in Delphi 6, as the Object Inspector of the latest version of Delphi allows you to expose properties of subcomponents directly. As an effect, the example presented in this section has been modified (and simplified) compared to the previous edition of the book. I’ll actually mention the differences, when relevant. Since the digital clock will provide some text output, I considered inheriting from the TLabel class. However, this would allow a user to change the label’s caption—that is, the text of the clock. To avoid this problem, I simply used the TCustomLabel component as the parent class. Chapter 11 • Creating Components Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 435 A TCustomLabel object has the same capabilities as a TLabel object, but few published proper- ties. In other words, a TCustomLabel subclass can decide which properties should be available and which should remain hidden. NOTE Most of the Delphi components, particularly the Windows-based ones, have a TCustomXxx base class, which implements the entire functionality but exposes only a limited set of properties. Inheriting from these base classes is the standard way to expose only some of the properties of a component in a customized version. In fact, you cannot hide public or published properties of a base class. With past versions of Delphi, the component had to define a new property, Active, wrap- ping the Enabled property of the Timer. A wrapper property means that the get and set meth- ods of this property read and write the value of the wrapped property, which belongs to an internal component (a wrapper property generally has no local data). In this specific case, the code looked like this: function TMdClock.GetActive: Boolean; begin Result := FTimer.Enabled; end; procedure TMdClock.SetActive (Value: Boolean); begin FTimer.Enabled := Value; end; Publishing Subcomponents in Delphi 6 With Delphi 6 we can simply expose the entire subcomponent, the timer, in a property of its own, that will be regularly expanded by the Object Inspector, allowing a user to set each and every of its subproperties, and even to handle its events. Here is the full type declaration for the TMdClock component, with the subcomponent declared in the private data and exposed as a published property (in the last line): type TMdClock = class (TCustomLabel) private FTimer: TTimer; protected procedure UpdateClock (Sender: TObject); public constructor Create (AOwner: TComponent); override; published property Align; Creating Compound Components Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com [...]... Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 458 Chapter 11 • Creating Components WARNING As this is a rather advanced topic, feel free to skip this section if you are new to writing Delphi components But component messages are not documented in the Delphi help file, so I felt it was important to at least list them here Component Messages A Delphi component passes component messages to other... RegisterPropertyInCategory (‘Input’, TMdArrow, ‘OnArrowDblClick’); RegisterPropertyInCategory (‘Visual’, TMdArrow, ‘Filled’); end; Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 450 Chapter 11 • Creating Components In Delphi 5, the first parameter was a class indicating the category type; now the parameter is simply a string, a much simpler solution This change also makes it straightforward to define... TMdActiveButton class, demonstrates this by handling some internal Delphi messages to accomplish its task in a very simple way (For information about where these internal Delphi messages come from, see the sidebar “Component Messages and Notifications.”) The ActiveButton component handles the cm_MouseEnter and cm_MouseExit internal Delphi messages, which are received when the mouse cursor enters or... with the Image Editor (as shown in Figure 11 .5) , starting a new project and selecting the Delphi Component Resource (DCR) project type TIP DCR files are simply standard RES files with a different extension If you prefer, you can create them with any resource editor, including the Borland Resource Workshop, which is certainly a more powerful tool than the Delphi Image editor When you finish creating... built with Delphi) cm_InvokeHelp Sent by code in a DLL to call the InvokeHelp method cm_WindowHook Sent in a DLL to call the HookMainWindow and UnhookMainWindow methods You’ll rarely need to use these messages yourself There is also a cm_HintShowPause message, which is apparently never handled in VCL Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 460 Chapter 11 • Creating Components • Delphi internal... Graphical Component 441 As a short conclusion of this digression on frames compiled inside packages, I can certainly say that this approach is still far from linear It is certainly much better than in Delphi 5, where frames inside packages were really unusable The question is, is it worth the effort? In short, I’d say no If you work alone or with a small team, it’s better to use plain frames stored in... beginning, again followed by the property name These are just guidelines to make programs more readable The compiler doesn’t enforce them These conventions are described in the Delphi Component Writers’ Guide and are followed by the Delphi s class completion mechanism Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 444 Chapter 11 • Creating Components Writing the Paint Method Drawing the arrow in... a field of the type of the event This type is actually a method pointer type (see Chapter 5 for details) Here is the definition I’ve added in the private section of the TMdArrow class: fArrowDblClick: TNotifyEvent; In this case I’ve used the TNotifyEvent type, which has only a Sender parameter and is used by Delphi for many events, including OnClick and OnDblClick events Using this field I’ve defined... end; Registering Property Categories We’ve added to this component some custom properties and a new event If you arrange the properties in the Object Inspector by category (a feature available since Delphi 5) , all the new elements will show up in the generic Miscellaneous category Of course, this is far from ideal, but we can easily register the new properties in one of the available categories We can... file In fact, the streaming system by default ignores components that are not owned by the form Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com Creating Compound Components 437 FIGURE 11.4: In Delphi 6, the Object Inspector can automatically expand subcomponents, showing their properties, as in the case of the Timer property of the MdLabelClock component The key piece of the component’s code is . database-related operations. NOTE Package names in Delphi 6 aren’t version specific any more, even if the compiled packages still have a version number in the filename. See the section “Project and Library Names in Delphi 6 in Chapter. TMdClock.SetActive (Value: Boolean); begin FTimer.Enabled := Value; end; Publishing Subcomponents in Delphi 6 With Delphi 6 we can simply expose the entire subcomponent, the timer, in a property of its own,. approach. NOTE The first feature has become even more relevant in Delphi 6, as the Object Inspector of the latest version of Delphi allows you to expose properties of subcomponents directly.

Ngày đăng: 12/08/2014, 21:20

Tài liệu cùng người dùng

Tài liệu liên quan