Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents

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

Building Your First Component

Building components is an important activity for Delphi programmers. Basically, any time you need the same behavior in two different places in an application, or in two different applications, you can place the shared code in a class—or, even better, in a component.

In this section, I'll introduce a couple of components to give you an idea of the steps required to build one. I'll also 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 such a customized combo box, why not turn it into a component? Doing so will 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 ® Other to open the Object Repository and then choosing the component in the New page. As you can see, the Component Wizard requires the following information:

  • The name of the ancestor type: the component class you want to inherit from. In this case, use TComboBox.

  • The name of the class of the new component you are building; use TMdFontCombo.

  • The Component Palette page where you want to display the new component, which can be a new or an existing page. Create a new page, called Md.

  • The filename of the unit where Delphi will place the source code of the new component; type MdFontCombo.

  • The current search path (which should be set up automatically).

Click To expand

Click the OK button, and the Component Wizard will generate the source file shown in Listing 9.1 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 and then discuss the installation.

Listing 9.1: Code of the TMdFontCombo Class, Generated by the Component Wizard
Start example
unit MdFontCombo;
   
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.
End example

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. As you can see, the Component Wizard does very little work.

Warning 

The Register procedure must be written with an uppercase R. This requirement is 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, this example doesn't include much code. You need only copy all the system fonts to the Items property of the combo box at startup. To do so, you 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 problem is, you 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 property is set; and that property isn't set in the constructor, but later.

For this reason, instead of assigning the new strings in the Create constructor, you 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, you execute the default behavior, and then you can write your 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;
    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, you redefine this property by setting a value with the default keyword. You 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 properties 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 

It is 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 behavior is obtained with the stored directive followed by the value False. The component and its window will be created again when the program starts, so it doesn't make sense to save in the DFM file information that will be discarded later (to be replaced with the new list of fonts).

Note 

You can write the code of the CreateWnd method to copy the fonts to the combo box items only at run time, using statements such as if not (csDesigning in ComponentState). But for this first component you are building, the less efficient but more straightforward method I've described offers a clearer illustration of the basic procedure.

The third property, ChangeFormFont, is not inherited but introduced by the component. It is used to determine whether the current font selection in the combo box should specify the font of the form hosting the component. Again, this property is declared with a default value, which is set in the constructor. The ChangeFormFont property is used in the code of the CreateWnd method, shown earlier, to set up the initial selection of the combo depending on the font of the form hosting the component. This is generally the component's Owner, although I could also have 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, you 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. The complete code is as follows:

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 you have to install the component in the environment, using a package. For this example, you can either create a new package or use an existing one, such as the default user's package.

In either case, choose the Component ® Install Component menu command. The resulting dialog box has a page that lets you install the component into an existing package, and a page where you can create a new package. In the latter case, type in a filename and a description for the package. Clicking OK opens the Package Editor (see Figure 9.1), which has two parts:

  • The Contains list indicates the components included in the package (or, to be more precise, the units defining those components).

  • 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.

Click To expand
Figure 9.1: The Package Editor
Note 

Since Delphi 6, package names aren't version specific, even if the compiled packages have a version number in the filename. See the section "Changing Project and Library Names" in Chapter 10 for more details about how this is achieved technically.

Add the component to the new package you've just defined, and then compile the package and install it (using the two corresponding toolbar buttons in the Package Editor); the new component will immediately appear in the Md page of the Component Palette. The component unit file's Register procedure told Delphi where to install the new component. By default, the bitmap used will be the same as that of the parent class, because you haven't provided a custom bitmap (you will do this in later examples). Notice also that if you move the mouse over the new component, Delphi displays as a hint the name of the class without the initial letter T.

What's Behind a Package?

The Package Editor generates the source code for the package project: a special kind of DLL built in Delphi. The package project is saved in a file with the DPK (for Delphi PacKage) extension, displayed if you press the F12 key in the package editor. A typical package project looks like this:

package MdPack;
   
{$R *.RES}
{$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 package keyword (similar to the library keyword I'll discuss in the next chapter), which introduces a new package project. Then comes a list of 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 is a DESCRIPTION compiler directive, used to make the package description available to the Delphi environment. After you've installed a new package, its description will appear 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 9.2.

Click To expand
Figure 9.2: The Project Options for packages

In addition to common directives like DESCRIPTION, there are other compiler directives specific to packages. The most common of these options are easily accessible through the Package Editor's Options button. 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, requires lists the packages required by the current package, and contains lists the units installed by this package.

Let's consider 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 information for the DCU files of the units contained in the package.

At design time, Delphi requires both the BPL and DCP files, because the BPL file has the 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 package), the DCP file will also be used by the linker, and the BPL file should be shipped along with the application's main executable file. 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 corresponding DFM files, plus a Help file. As an option, of course, you can also make available the source code files of the package units (the PAS files) and of the package itself (the DPK file).

Warning 

By default, Delphi places all the compiled package files (BPL and DCP) not in the package source code's folder but under the \Projects\BPL folder. It does this so the IDE can easily locate the files, and the location creates no particular problem. However, when you have to compile a project using components declared in those packages, Delphi may complain that it cannot find the corresponding DCU files, which are stored in the package source code folder. You can solve this problem by indicating the package source code folder in the library path (specified in the Environment Options, which affect all projects) or by indicating it in the search path for the current project (in the Project Options). If you choose the first approach, placing different components and packages in a single folder may result in a real time savings.

Remember, if you compile an application using the packages as run-time libraries, you'll need to install these new libraries on your clients' computers. If you instead compile the programs by statically linking to the units contained in the package, the package library will be required only by the development environment and not by the users of your applications.

Using the Font Combo Box

Let's create a new Delphi program to test the Font combo box. Move to the Component 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 added a Memo component to the form with some text in it. By leaving the ChangeFormFont property on, you don't need to write any other, as you'll see in the example. As an alternative, I could have turned off the property and handled the component's OnChange event with code like this:

Memo1.Font.Name := MdFontCombo1.Text;

The aim of this program is only to test the behavior of the new component you have built. The component is still not very useful—you could have added a couple of lines of code to a form to obtain the same effect—but looking at a couple of components should help you get an idea of what is involved in component building.

The Component Palette Bitmaps

Before installing a component, you can take one further step: define a bitmap for the Component Palette. If you fail to do so, the Palette uses the parent class's bitmap, or a default object's bitmap if the parent class is not an installed component. Defining a new bitmap for the component is easy, once you know the rules. You can create one with the Image Editor included in Delphi by starting a new project and selecting the Delphi Component Resource (DCR) project type.

Tip 

DCR files are 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 the resource file, rename the RES file to use a DCR extension.

The resource file should have one (or more) bitmap, each 24×24 pixels. The only important rules refer to naming. In this case, the naming rules are not just a convention; they are required so that the IDE can find the image for a given component class:

  • The name of the bitmap resource must match the name of the component, including the initial T. In this case, the name of the bitmap resource should be TMDFONTCOMBO. The name of the bitmap resource must be uppercase—this is mandatory.

  • If you want the Package Editor to recognize and include the resource file, the name of the DCR file must match the name of the compiled unit that defines the component. In this case, the filename should be MdFontBox.dcr. If you manually include the resource file via a $R directive, you can give it any name you like, as well as use an RES extension and add multiple bitmaps in it.

When the bitmap for the component is ready, you can install the component in Delphi by using the Package Editor's Install Package toolbar button. After this operation, the Contains section of the editor will list both the component's PAS file and the corresponding DCR file. In Figure 9.3, you can see all the files (including the DCR files) of the final version of the MdPack package. If the DCR installation doesn't work properly, you can manually add the {$R unitname.dcr} statement in the package source code.

Click To expand
Figure 9.3: The Contains section of the Package Editor shows both the units that are included in the package and the component resource files.

 
Previous Section Next Section


 


 

Delphi Sources


Copyright © 2004-2024 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide
ร๐๓๏๏เ ยส๎ํ๒เ๊๒ๅ   Facebook   ั๑๛๋๊เ ํเ Twitter