Creating components in DELPHI. Introduction to Creating Delphi Components

Before creating your component, you need to select an ancestor for it. Who can be the ancestor for your component?

Typically used as ancestors of TComponent, TControl, TWinControl, TGraphicControl, TCustomXXXXXX, as well as all components of the component palette.
Let's take for example the TOpenDialog component, which is located on the Dialogs page of the component palette. It does its job well, but it has one small inconvenience. Each time you use it, you must change the value of the Options property each time. And, as a rule, these are the same actions.
OpenDialog1.Options:= OpenDialog1.Options + ;

so that the file we are trying to open with this dialog actually exists on the disk.
We have already chosen a task for ourselves, all that remains is to create a component. We create a blank for the component by selecting the Component/New Component... command from the menu and selecting in the dialog box
Ancestor type: TOpenDialog
Class Name: TOurOpenDialog
Palette Page: Our Test
We clicked Ok and we now have a template for our future component.

We override the constructor for this component, i.e. in the public section insert the line:

constructor Create(AOwner: TComponent); override;

Clicking on this line Ctrl + Shift + C creates a template for this method, inside which we insert the following lines:

inherited Create(AOwner); (Call the inherited constructor)
Options:= Options + ; (We carry out the actions we need)

Please note: The keyboard shortcuts Ctrl + Shift + up/down arrows allow you to navigate between a method declaration and its implementation.

Installing the created component Component/Install Component...
Install Into New Package
Package file name: C:Program FilesBorlandDelphi4LibOurTest.dpk
Package description: Our tested package

Don't you like that our component has the same icon as the standard one? Then let's create our own for him.
To do this we need to call Tools/Image Editor. Create a new *.dcr file.
Insert the Resource/New/Bitmap image into it. Set the image size to 24x24 pixels. And then - your creativity...
Please note: the color of the dots that matches the color of the dot in the lower left corner of the picture will be considered TRANSPARENT!
Once you've created your drawing, rename it from Bitmap1 to TOurOpenDialog and save the file as OurOpenDialog.dcr.
Remove the component from the package and install it again (only in this case a link to the *.dcr file will be added).
Compile, Install and good luck!

unit OurOpenDialog; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TOurOpenDialog = class(TOpenDialog) private(Private declarations) protected public(Public declarations) constructor Create(AOwner: TComponent); override; published end; procedure register; implementation procedure register; begin RegisterComponents("Samples", ); end; (TOurOpenDialog) constructor TOurOpenDialog.Create(AOwner: TComponent); begin inherited Create(AOwner); (Call the inherited constructor) Options:= Options + ; (We carry out the actions we need) end; end.

A component declaration consists of sections such as private, protected, public and published. What do they mean?
These are visibility directives.
Everything that is declared in the section private, available only inside the module in which the class is declared (private declarations). Here, as a rule, variables are declared that store property values, as well as methods (procedures or functions) for accessing them.
Everything that is declared in the section protected, is available as in the private section, as well as to the descendants of this class (developer interface).
Here you can declare methods for accessing property values ​​(if you want to allow children of your component to change these methods),
as well as properties, methods and events (methods for reacting to events) in components of type TCustomXXX.
Everything that is declared in the section public, available to any user of the component (runtime interface).
Methods are usually declared here. Only properties and events can be declared in the published section (they are declared as properties).
They are available during application design (design phase interface).

Properties

Type properties massif- regular Object Pascal arrays, but unlike the latter they can be indexed not only by numeric values ​​but also by string ones. Unfortunately, this type of property requires a custom property editor (in the Object Inspector, the property editor has a button with three dots [...]), so in the example below the property ArrayProp announced in section public.

type TOurComponent = class(TComponent) private( Private declarations ) FArrayProp: array of integer; function GetArrayProp(aIndex: integer): integer; procedure SetArrayProp(aIndex: integer; const Value: integer); protected(Protected declarations) public(Public declarations) property ArrayProp: integer read GetArrayProp write SetArrayProp; published(Published declarations) end;

Property specifiers

Specifier default specifies whether to save the property value in the form file or not. If the property value matches the value default- the value in the form file is not saved, if the values ​​are not equal - it is saved. You can check this by placing the component on the form and selecting the "View as Text" menu item with the right mouse button. Default does not set the initial value of the property to the specified one. This must be done in the component's constructor.

unit OurComponent; interface uses Windows, SysUtils, Classes, Graphics, Forms, Controls; type TOurComponent = class(TComponent) private( Private declarations ) FMyInteger: Integer; protected(Protected declarations) public(Public declarations) constructor Create(AOwner: TComponent); override; published(Published declarations) property MyInteger: Integer read FMyInteger write FMyInteger default 10; end; implementation constructor TOurComponent.Create(AOwner: TComponent); begin inherited Create(AOwner); FInteger:= 10; end; end.

Specifier nodefault overrides the default property value. This specifier is typically used to override the default value of an inherited property.
For example: property AutoSize nodefault;

Specifier stored specifies when to save the property value in the form file. After stored can stand true(always save) false(never save) or the name of a function that returns a Boolean result.

property OneProp: integer read FOneProp write SetOneProp stored False; property TwoProp: integer read FTwoProp write SetTwoProp stored True; property ThreeProp: integer read FThreeProp write SetThreeProp stored Fuct;

And the last thing:
To add a picture to a component for demonstration in the component panel, you need to: - create it with a size of 24*24 with the file name.dcr (in the resource, the name of the picture is equal to the name of the component, in capital letters)
- place the picture next to the component.

Development software for Windows OS and other popular ones can be done using a variety of types of tools. Among those that are very popular among Russian and foreign programmers is the Delphi program. What are the specifics of this development tool? What are its most notable features?

General information about Delphi

Delphi is a development environment for application programs that are designed to run on Windows, MacOS, and mobile devices. operating systems- iOS and Android. It is characterized by the simplicity of the language and code generation procedures.

If necessary, provides low-level communication with the OS and libraries written in C and C++. Programs created using Delphi do not require third-party shells to run, such as the Java Virtual Machine. Delphi is a development environment that can be successfully used by both professionals and for educational purposes. In order to master its basic capabilities, it is not necessary to have high qualifications and knowledge of complex programming languages.

Main advantages

Let's study what the key advantages of the software product in question are. When a particular IT company is justifying the choice of a development environment, Delphi becomes the choice of many programmers and is recommended by them for use. This is due to the fact that this environment allows you to create applications in the fastest possible time, ensuring their high performance even on those computers that have modest hardware characteristics. A significant argument in favor of choosing the development environment in question is that it can be supplemented with new tools that are not provided by the standard set of solutions present in the Delphi interface.

Let us now study the nuances of practical use of Delphi capabilities.

Interface specifics

First of all, you can pay attention to some features of the interface of the software development environment in question. Thus, the structure of the program’s workspace involves simultaneous work with several main windows. Let's consider this property in more detail.

The Delphi development environment, version 7 in particular, involves the use of the following key modules: form designer, editor, palette, object inspector, and reference book. In some Delphi modifications, the marked components may be named differently. For example, an editor may correspond to a program code window, and a designer may correspond to a form window. However functional purpose theirs will be the same. The marked Delphi can complement various auxiliary tools. The first two are considered the main ones from the point of view of program development procedures. But the rest are also important. Let's look at the features of using the marked Delphi modules.

Form designer, editor and palette

With the help of the form designer, the developer creates the interface of his program. In turn, its code is written in the editor. Many programmers who recommend choosing the Delphi development environment as the most optimal solution cite the ease of use of the form designer as an argument. Some experts believe that this process is more like a game.

As soon as the user starts creating a program and launches the form designer, initially there are no elements in it, it is empty. But you can immediately fill it out using tools located on another Delphi module - the palette. Elements of the program interface that are configured in the form designer must be controlled by commands, which, in turn, are written in the editor.

But let's return to the palette for now. Using it, you can place the necessary objects in the form designer area. In order to use a particular tool, you should click once on it - while it is in the palette area, and a second time - in the form designer window. After this, the corresponding object will move to the development area, and you can write code for it in the editor.

Object Inspector

Another significant element that Delphi, an application development environment for Windows and other common platforms, contains is the object inspector. You may notice that the information displayed in it changes: this is affected by the status of the object that is selected in the form designer area.

The structure of the object inspector is as follows. It consists of two windows. Each one contains algorithms that determine the behavior of the corresponding components. The first displays properties, the second displays events. If the programmer wants to make adjustments to the algorithms that affect a specific component, then the capabilities of the object inspector are used. For example, you can change the positioning of certain program interface elements, their height and width.

The Object Inspector has tabs that let you switch between pages that display properties or events that are directly related to the editor. So, if you double-click on the right side of any of the items displayed on the screen, the code that corresponds to a particular event will be recorded in the editor.

Software development in Delphi involves using the Object Inspector to solve a variety of problems. This is predetermined by the fact that with the help of this tool you can change the properties of virtually any objects located on the form, as well as the form itself. Let's take a closer look at some of the features of working with the object inspector.

Object Inspector: Using Features

In order to understand how the Delphi IDE functions in terms of interaction between the Object Inspector and the Form Inspector, you can try making changes to the properties of some common software interface elements in Windows - for example, Memo, Button and Listbox (we will explore their essence in more detail a little later). First, you need to place them on the form using the available Delphi tools.

You can try experimenting with the Ctl3D property. To do this, you need to click on the form, then go to the object inspector and change the value of the property in question. After this, the form will change significantly. At the same time, the Ctl3D property will be changed on each of the elements that are placed in the design window.

After the experiments have been carried out, we can go back to the form and activate the Ctl3D value. After that, let's look at the Memo and Listbox elements. Now you can change their properties, location on the form, and appearance. For example, by selecting the Edit option in the menu item and then Size, the programmer can change the width and height of objects. There is an option to center them by selecting Edit and Align. The corresponding actions will affect the elements displayed in the Object Inspector.

Using the Delphi module in question, you can change the properties of components. So, for example, if the task is to determine a specific color for them, then there are options for using several tools at once. First, you can enter a command corresponding to a color - for example, red - clRed - into the area Second, the user can select the desired color from a list. Thirdly, there is an option to double-click on the Color properties - a color selection window will appear. Similarly, the developer can change other attributes of objects - for example, the font type, its color or size.

Directory

Delphi is a development environment that is complemented by a fairly detailed help system. To access it, select Help from the menu. After this, one of the ones we noted above will be displayed in the window. software modules development environment in question - reference book. The peculiarity of using it is that when you press F1, the user will receive a specific hint reflecting the specifics of using the current tool. For example, if a programmer is working with the Object Inspector, he can select one of the properties, then press F1 and get help information about the corresponding option. The same can be done when working with any other interface element that includes the Delphi 7 development environment and other versions of the corresponding type of software.

Other interface elements

Among other significant components of the interface in question software solution- menu, panel quick access, as well as an image editor. Regarding the menu, it allows the programmer to quickly access the necessary components present in the structure of the development environment. You can use it either using the mouse or using hot keys. Just below the menu is the quick access panel. Some of its functions duplicate those of the menu, but they are faster to access. Delphi is somewhat similar to the Paint program on Windows. That is, with the help of it you can make simple adjustments to pictures, put inscriptions and other elements on them.

Programming Tools

Delphi is a development environment that includes a large number of tools designed to improve programmer productivity. Thus, the key modules we discussed above are complemented by a set of special tools. These include a debugger, a compiler, as well as WinSight and WinSpector components. Note that in some versions of Delphi, the marked elements must be installed separately. Let's study their specifics.

Delphi Debugger

Regarding the debugger, this tool complements the code editor in terms of carrying out the necessary checks of the corresponding software algorithms for correctness. With it, a developer can actually examine his source code line by line. In some cases, when solving a task such as component development, Delphi as an independent product can be supplemented with an external debugger, which gives the programmer enhanced capabilities for checking the code of the software being created.

Delphi compiler

Let us now study the specifics of the compiler of the development environment in question. Note that there may be several corresponding elements in the Delphi structure. So, there is an option to use the DCC compiler, which is useful in cases where the task is to work with the application in an external debugger.

Winsight and WinSpector

The specified modules are those that need to be installed additionally on Delphi. They are characterized by relative difficulty in mastering. However, many programmers who have chosen the Delphi development environment believe that these components must be learned to use. Thus, the Winsight module is used to monitor Windows messages. A component such as WinSpector is needed to record the state of the computer in a special file. If you encounter any problems during software development, you can always open this file and see what could be causing the problem.

Standard Components

The Delphi development environment, which we're learning about in general, includes a number of standard components that are also useful to know about. Experts classify these as: MainMenu, PopupMenu, Label, Edit, Memo, Button, Checkbox, Radiobutton, Listbox, Combobox, Scrollbar, Groupbox, Panel, and Scrollbox. Let's study their specifics in more detail.

The MainMenu component is designed to place the main menu in the interface of the created program. To do this, you need to place the corresponding element on the form, then call the Items property through the object inspector, and then determine the necessary menu items.

The PopupMenu component is designed to place pop-up menus in the interface of the created program, that is, those that open by right-clicking the mouse.

The Label component is used to display text in the program window. It can be customized, for example, setting the desired font in the object inspector.

The Edit component is used to display a piece of text on the screen that the user can edit while the program is running. It is complemented by the Memo component, which, in turn, can be used to work with larger texts. This element includes, for example, options such as copying text.

The Button component is designed to perform certain actions by pressing a button while the program is running. It is necessary to place the corresponding element on the form, and then enter the required program code.

The Checkbox component allows you to display lines on the screen with a small window in which a checkbox can be placed using the mouse. A similar element is Radiobutton. They differ, firstly, appearance- the second component is made in the form of a circle, and secondly, the first element allows the simultaneous selection of several options, Radiobutton - only one.

The Listbox component is used to display a list on the screen that the user can scroll through using the mouse. Another element, Combobox, is somewhat similar to it, but it is complemented by the ability to enter text in a special field.

The Scrollbar component is a scroll bar in windows. Typically appears automatically as soon as the text space or form with objects becomes larger than the window.

The Groupbox component is used to record the order in which you move between windows when you press the TAB key. Can be supplemented with a Panel element, which can be used to move several objects on the form.

The Scrollbox component allows you to fix an area on a form that can be scrolled both horizontally and vertically. This property characterizes the main Delphi development windows by default. But if there is a need to enable such an option on a specific section of the form, you can use the Scrollbox component.

Summary

Delphi is a powerful application development environment that is at the same time characterized by ease of use of its core functions. With the help of the tools included in its structure, you can create the most different types programs for Windows and other popular operating systems.

The choice of Delphi development tools by many programmers is determined by the ease of use of the interfaces of the corresponding software, as well as a wide range of tools useful for working at any stage of program creation - at the stage of design, algorithm programming or debugging.


Developing your own components

If you're not happy with the standard components that come with Delphi, then it's time for you to try your hand at creating your own. We will start with simple ones first and gradually move on to more complex ones. So, let's begin.

Before creating your component, it is important to choose the right ancestor for it. Who can be the ancestor for your component? Typically used as ancestors of TComponent, TControl, TWinControl, TGraphicControl, TCustomXXXXXX, as well as all components of the component palette. Let's take for example the TOpenDialog component, which is located on the Dialogs page of the component palette. It does its job well, but it has one small inconvenience. Each time you use it, you must change the value of the Options property each time. And, as a rule, these are the same actions.


Clicking on this line Ctrl + Shift + C creates a template for this method, inside which we insert the following lines:


Installing the created component Component/Install Component...

  • Install Into New Package
  • Package file name: C:\Program Files\Borland\Delphi4\Lib\OurTest.dpk
  • Package description: Our tested package

Don't you like that our component has the same icon as the standard one? Then let's create our own for him. To do this we need to call Tools/Image Editor. Create a new *.dcr file. Insert the Resource/New/Bitmap image into it. Set the image size to 24x24 pixels. And then - your creativity... Please note: the color of the dots that matches the color of the dot in the lower left corner of the picture will be considered TRANSPARENT!

Once you've created your drawing, rename it from Bitmap1 to TOurOpenDialog and save the file as OurOpenDialog.dcr. Remove the component from the package and install it again (only in this case a link to the *.dcr file will be added).

Compile, Install and good luck!

Professional application development with Delphi 5 | Development Tools | ComputerPress 2"2001

Creating Delphi Components

Introduction to Creating Delphi Components

When developing applications using Borland Delphi, it is convenient to create components for the following reasons:

  1. Ease of use. The component is placed on the form, and you need to set property values ​​for it and write event handler code. Therefore, if in a project any combination of controls and event handlers associated with them occurs in two places, then it makes sense to think about creating a corresponding component. If the combination of controls and event handlers associated with them occurs more than twice, then creating a component is guaranteed to save effort when developing the application.
  2. Simple organization of group project development. In group development, individual parts of the project can be defined as components and the work can be assigned to different programmers. Components can be debugged separately from the application, which is quite easy to do.
  3. Simple and effective method sharing code with other programmers. There are many sites, for example http://www.torry.net/, where you can find freely distributed components or purchase them for a nominal fee.

Feature Packs

In Delphi, components are stored in packages. The list of used component packages can be called up using the Component/Install Packages menu item (however, for some reason this dialog has the title Project Options).

Using this dialog, you can add a new package (Add) or remove an existing one (Remove). Deleting does not mean physically deleting a file from disk, but rather removing the link from the development environment to this package. When you add a new package, the components stored in it appear on the palette, but when you delete it, on the contrary, they disappear. Instead of deleting a package, you can “hide” its contents at the development stage by unchecking the package name in the list. You can also view the components and their icons (Components). Finally, you can edit user-added packages (Edit) - packages that come with Delphi cannot be edited (the Edit button is grayed out).

In this dialog, you can specify how to create the project: using runtime packages or without them. It is clear from this that there are two types of component packages: runtime package (package that works at runtime) and design-time package (package used during development). They are all DLLs (dynamically loaded libraries).

Runtime packages (*.bpl extension) are delivered to the end user along with the project if the project was compiled with the Build with runtime packages option enabled. The application itself (*.exe or *.dll) in this case turns out to be small, but rather large *.bpl files must be transferred along with it. According to experts, delivering a project with runtime packages gives an advantage in the volume of supplied files, only if it includes five or more modules (*.exe or *.dll) written in Delphi. When these modules work together, operating system resources are saved, since one package loaded into RAM serves several modules.

Design-time packages (*.dcp extension) are used only at the development stage. During development, they support the creation of components on a form. The compiled Delphi project includes code not from the component package, but from *.dcu files. Although the *.dcp file is generated from the *.dcu file, their contents may not be the same if changes have been made to the *.pas file and the package has not been recompiled. Compilation is only possible for packages created by programmers. This is achieved by clicking the Edit button in the above dialog. After this, a form appears that allows you to manipulate the package.

The package contains two sections. The Contains section contains a list of modules that form the components of this package (*.pas- and *.dcu-files) and their icons (*.dcr-files). The Required section contains links to other packages required for these components to work. Adding a new component to the package is done with the Add button, and removing an existing one is done with the Remove button. Until the package is compiled by clicking the Compile button, any changes made to the package will not appear in the development environment. Finally, the Install command is available when the package contents have been removed from the development environment by unchecking the package name in the previous dialog.

The Option command allows you to select options for compiling a package that are similar to the project options. These can be used to determine what type of package a given package is: run-time, design-time, or both (default package type). The options define the directories in which to search for the necessary modules and save the compilation results. They also define the actions required for debugging: whether or not to check the range of acceptable values, how to perform optimizations, how to handle I/O errors. Finally, the options may include package version information. This is very important if the application is distributed along with runtime packages: when running the installation program, version information will allow you to correctly replace outdated versions of packages, and vice versa, if you try to install a package of an earlier version than the one already available on a given computer, the latter will not be overwritten.

Component Templates

Delphi allows you to create simple composite components from a few common components selected on a form at design time. The corresponding expert is called using the menu item Components/Create Component Template. This menu item is available if at least one component is selected on the form. After selecting it, the Component Template Information dialog box appears.

In this dialog, you should specify the name of the class and the name of the page on the component palette where the new component should be placed. If a page with this name is not on the component palette, it will be created. You can also change the proposed icon for the new component by uploading a suitable *.bmp file.

When a template is created, both the properties changed by the programmer in the Object Inspector and the event handlers associated with the selected controls are remembered. In this case, event handlers are remembered completely, without filtering calls to other (not selected on the form) components, global variables, methods, etc. Accordingly, if such components (variables, methods) are missing in another project, then when trying to compile such a project, the Unknown Identifier diagnostic message will be received.

When should you use templates? First of all, in cases where it is necessary to change any properties that are available by default in the base class. For example, an application uses a control to edit a line of yellow text. You can place the TEdit component on the form, change the Color property to yellow, mark this component and save it as a template. After this, you can access this template, and the component placed on the form will have a yellow color. However, you should not abuse this opportunity, because a new class will be created for the control with a changed color and all virtual methods will be duplicated in memory. This will negatively impact operating system resources.

It is also convenient to use component templates when you need to transfer a number of components along with event handlers from one form to another. To do this, they are all selected, a component template is created, which is placed on a new form. In this case, not only the components themselves will be transferred, but also the event handlers, which cannot be achieved when calling the Copy/Paste commands - in the latter case, the event handlers will be lost.

Components created using the Create Component Template command are quite different from regular components created using the standard method (described below). Visually, the main difference is this: if a template includes several controls, then after such a component is placed on the form, you can select an individual control and delete it - while the rest will remain on the form. For standard components, if they include several controls, it is not possible to select one of them and delete it - the component is selected and deleted entirely.

Creating a Simple Component

When writing a new component, you need to be clear that the component is being written for programmers, not end users. In this case, it is desirable that the programmer does not delve into the details of the component’s implementation, but simply uses the properties and events exposed by it. This is achieved through very thorough testing. A new component must be tested even in situations for which it is not clearly designed.

Let's pose the problem as follows. You need to create a button that will beep when pressed, and implement it as a component so that the programmer can place it on a form and use it. In general, when considering components, we will quite often use the simplest external effects: beeping, displaying a message, etc. This implies that in those places where external effects are used, any fairly complex code can be placed. We're just not interested in him at the moment.

Creating a component begins by selecting the Component/New components menu item. After this, the New Component dialog immediately appears.

In this dialog, you need to define the ancestor class, the name of the newly created class, the page on the palette where the new component will be placed, the name of the module containing the implementation of the new component, and the path to it. If the new component uses other modules, the path to which is not described, then they must be defined in the Search Path field.

So, the first (and, perhaps, the main) task is choosing an ancestor class. In the drop-down list, all components available in the palette are offered as an ancestor class, including those that are not included in the standard Delphi distribution. It is necessary to select as an ancestor class a class that is as close in properties as possible to the class being created. For our task, we can, for example, select TWinControl as an ancestor, but in this case we will need to implement all the visual effects of button clicks, etc. Therefore, we choose TButton as the ancestor.

The name of the newly created class must reflect the content of the component and in no case coincide with the name of an already registered component! At the stage of filling out this dialogue, names are not checked for matches - adventures associated with such an error will begin later...

When selecting a page, you need to know that if you specify the name of a non-existent page, a new one will be created.

Finally, when you click both the Install and OK buttons, a template will be created to implement the new component. However, when you click the Install button, the template will be placed on the component palette, and when you click OK, it is simply created. It is recommended to use the Install button. After the component is installed, it can be placed on the form. Now all changes made to the component implementation code will be compiled along with the project, and the programmer will immediately receive error messages. If the component is not installed, then to find errors it must be compiled through the package editor (see above) by clicking the Compile button, which is less convenient.

So, after clicking the Install button, another dialog appears, which allows you to determine the package where this component will be placed.

This dialog has two pages, on the first of which you can select one of the existing packages, and on the second you can create a new one. It is highly advisable to provide a short text description of the package; this is what will be shown in the dialog called by the Component/Install packages command (see above). After selecting a package and pressing the OK button, the package editor is called, where the newly created module for implementing the new component is automatically placed. It is useful not to close it, but to move it to one of the corners of the screen so that it can be activated by pressing the mouse button.

At the same time, a “blank” will be created in the code editor to describe the new component:

Unit ButtonBeep; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TButtonBeep = class(TButton) private ( Private declarations ) protected ( Protected declarations ) public ( Public declarations ) published ( Published declarations ) end; procedure Register; implementation procedure Register; begin RegisterComponents("Samples", ); end; end.

The new class itself declares four sections, the meaning of which is described in detail in the section “Visibility of variables and methods” of the previous article in this series (ComputerPress No. 1 "2001). In addition, the new class defines the Register procedure, which is called by the Delphi development environment during installation of this module as a component. It contains the name of the page on the palette where this component is placed, and in square brackets - the name of the class. In general, the Register method takes an array of class types as a parameter, because several components can be implemented in one module. Therefore, they are separated separated by a comma, for example:

Procedure Register; begin RegisterComponents("Samples", ); end;

Let's continue solving the task at hand - creating a button that makes a squeak. First, let's do something trivial (but as it turns out later, incorrectly) - assign an OnClick event handler in the button constructor. To do this, in the private section, we define the header of the new method BtClick(Sender:TObject) and implement it in the implementation section:

Procedure TButtonBeep.BtClick(Sender:TObject); beginBeep; end;

constructor Create(AOwner:TComponent); override;

with a mandatory override directive! Let's implement it in the implementation section:

Constructor TButtonBeep.Create(AOwner:TComponent); begin inherited Create(AOwner); OnClick:=BtClick; end;

After this, let's compile the component. Let's put a button on the form from the Samples page and run the project for execution. You can make sure that the button beeps when pressed!

Now let's go back to the development environment and assign an OnClick event handler in the object inspector. In the event handler, we will display the text in the form title:

Procedure TForm1.ButtonBeep1Click(Sender:TObject); begin Caption:="Test"; end;

Let's run the project and try to press the button. The form title changes, but the button stops beeping! The error is that we tried to define two handlers for one event of the OnClick button: one inside the BtClick component, and the other assigned using the object inspector. After working out the TButtonBeep constructor, we had a link to the first BtClick handler. Then the resources are loaded, and the ButtonBeep1Click method is assigned to the OnClick event handler. In this case, the link to the first handler - BtClick - is irretrievably lost.

Therefore, when writing new components, you should always consider changing properties and event handlers using the Object Inspector. If a property (event) should not change, it should not be shown in the Object Inspector. And if it is already visible, you should hide it (we'll talk about this later). The programmer has every right to change any properties in the object inspector, and if after this the component stops working, the developer of the component is to blame, but in no case the programmer using it.

How can we solve this problem correctly? One way to create components is to rewrite existing methods. When looking at the StdCtrls.pas file, where the source code for the TButton component is implemented, you can note that it contains a dynamic Click method that can be rewritten. Therefore, we return again to the source code created by the Delphi expert when creating the component (we remove the constructor and the BtClick method). Then in the public section we define the method header:

Procedure Click; override;

and here is the implementation of the method:

Procedure TButtonBeep.Click; begin inherited Click; beep; end;

You can verify that the button makes a squeak when pressed. In addition, when assigning an event handler in the object inspector, this handler is executed and the beep does not disappear! The component is implemented correctly.

Using this example, it is useful to analyze possible errors when writing code:

  1. Forgotten override directive when defining the Click method header. The button stops beeping, therefore the Click method is not called.
  2. A forgotten call to the ancestor method (inherited Click) in the implementation of the Click procedure. The button continues to beep when pressed, but the code in the event handler assigned in the object inspector is not executed. Therefore, the Click method of the TButton class raises the OnClick event.

Now let's change the icon of the TButtonBeep component on the palette. By default, the icon of the ancestor component is used for a new component. To do this, call the Image Editor using the Tools/Image Editor command. In the editor, call the command File/New/Component Resource File (*.dcr). After the Resource/New/Bitmap command, a dialog will appear suggesting an icon size of 32x32. These default sizes should be changed to 24x24 - this is the size that component icons must have! After clicking OK, you should draw an image using standard tools similar to Paint editor. Remember that the color of the bottom left pixel is the mask color - this color will be “transparent”.

After this, you need to redefine the name of the resource with the icon; by default, its name is Bitmap1. The new resource name must match the name of the class - in our case TButtonBeep.

Now you need to save the icon file in the same directory as the module containing the Register procedure for this component, and with the same name as the module name. Only the file extension will not be *.pas, but *.dcr. The file with the component icon is ready. However, if we look at the Components palette, we will see that the old icon is still there. If you restart Delphi or even the operating system, the old icon will still remain on the palette. In order to change the icon, the component must be re-registered. To do this you need:

This example should be considered a test exercise. Before writing a new component, you need to see if similar ones exist among freely distributed components. There are almost any buttons: transparent, running away, round, colored, etc. The situation is approximately the same with other components - descendants of the same class. Therefore, most often you have to implement components consisting of several controls.

Thus, in this example we explored the use of method rewriting to create new components.

Creating a Complex Component

Let's say you need to enter a list of customer names in an application. In the same application you will also need to enter a list of telephone numbers. Entering a list is a fairly common operation, so you should consider implementing it as a component.

To enter a new element into the list, you will need an editor - the TEdit component. Next, the user should be able to view the list - the TListBox component will be needed. In addition, you will need commands to write the current value from TEdit to the list, edit the selected list item, and delete it. The easiest way to implement these commands is using buttons. To simplify the task, we will place one button on the form, when clicked we will add the contents of the TEdit component to the list.

So we need to create a new component that includes a TEdit, a TListBox, and a TButton. As always, let's start creating it with the Component/New Component command. After this, a dialog appears in which you should define the ancestor class, class name, and module name. There are no difficulties with the class name and module name, but the name of the ancestor class is unclear. We have three controls. The common ancestor class for them is TWinControl. But if we choose it as an ancestor class, we are faced with a very long and tedious implementation of the TButton, TEdit and TListBox code. In such cases, it is necessary to select a component as an ancestor class that can be a “father” in relation to other components. Among the standard components distributed with Delphi, there are three: TPanel, TGroupBox, TScrollBox. Let's select the panel as the ancestor class, but not the TPanel component itself, but the TCustomPanel class. We will discuss the advantages of choosing TCustomPanel over TPanel below.

Let's name the new class TListAdd and click the Install button. After selecting the package, the component will be installed in the palette, from where it can be placed on the form of the newly created application. This is convenient because when the project is compiled, the component module will also be compiled and if there are errors, the compiler will display a message.

It would be convenient to place our controls on some form and then create a component from them. There is no such expert in the standard Delphi package. Therefore, you will need to create the components yourself and place them on the panel. It is reasonable to create controls - TButton, TEdit and TListBox - in the TCustomPanel constructor, which obviously requires rewriting it. For now, let's place the controls in a 100x100 square. Their coordinates also need to be determined in the constructor. It should be borne in mind that after the constructor of any control element has been worked out, it does not yet have a parent, that is, it does not know which window it should measure the coordinates of the upper left corner with respect to. Attempting to change the coordinates of a child window that does not have a parent will immediately throw an exception. Therefore, the first operator after calling the control's constructor will be to assign it a parent, for which we will select TCustomPanel. We will also make it their owner, in this case there will be no need to rewrite the destructor.

So, in the uses section we add the StdCtrls module, where the descriptions of the TEdit, TButton and TListBox classes are located, and in the private section we define three variables:

Private FEdit:TEdit; FListBox:TListBox; FButton:TButton;

In the public section we declare the constructor header with the obligatory override directive:

Constructor Create(AOwner:TComponent); override;

We implement the constructor in the implementation section:

Constructor TListAdd.Create(AOwner:TComponent); begin inherited Create(AOwner); FButton:=TButton.Create(Self); FButton.Parent:=Self; FButton.Left:=5; FButton.Top:=5; FButton.Width:=40; FButton.Height:=25; FEdit:=TEdit.Create(Self); FEdit.Parent:=Self; FEdit.Left:=50; FEdit.Top:=5; FEdit.Width:=45; FEdit.Height:=25; FListBox:=TListBox.Create(Self); FListBox.Parent:=Self; FListBox.Left:=5; FListBox.Top:=35; FListBox.Width:=90; FListBox.Height:=60; end;

It should be emphasized once again that the destructor in this case does not need to be rewritten: the panel is the owner of all controls, and when its destructor is called, the control destructors will be called automatically.

After recompiling the component using the package editor, changes in the component can already be seen visually, at the development stage.

The first drawback that catches your eye is the inadequate behavior of the controls when scaling the component. When changing its dimensions, the dimensions and position of the elements do not change. In addition, the component can be made small so that three controls will not fit on it. And finally, when installing a component on a form from the component palette with a simple mouse click, its dimensions also leave much to be desired.

First, let's correct the default sizes of components, that is, those that are assigned to it automatically when you click on the components palette and then click on the form. To do this, you simply need to specify the new panel dimensions in the constructor:

Width:=100; Height:=100;

Then you need to improve the component's behavior when scaling. To do this, you need to receive a message that the dimensions have changed. When the size of a control changes, the system sends a WM_SIZE message to it. This message must be intercepted. To do this, in the private section we describe the header of the message interceptor:

Procedure WMSize(var Message:Tmessage); message WM_SIZE;

and in the implementation section we implement its handler:

Procedure TListAdd.WMSize(var Message:TMessage); begin inherited; if Width<100 then Width:=100; if Height<100 then Height:=100; FEdit.Width:=Width-55; FListBox.Width:=Width-10; FListBox.Height:=Height-40; end;

The first statement is a call to the default WM_SIZE handler (inherited). After calling it, the Width and Height properties will contain the new width and height of the panel. After this, the minimum dimensions of the component are determined, in this case 100x100. If the horizontal or vertical size is less than the minimum, then it is assigned the minimum value. The controls are then scaled so that they fill the entire panel with some indentation. By compiling the component through the package editor, you can already note at the development stage that the controls on the panel behave correctly when scaled, and also that the size of the component cannot be made less than 100x100.

Now it will be useful to run the entire project, try entering data into a one-line text editor and clicking the button. In this case, nothing is added to the list. And not surprisingly, nowhere in our component is it specified what should be done when the button is pressed. In order to make an event handler associated with the click of a button, you can proceed as when writing the TbuttonBeep component, that is, define a new class - a descendant of TButton and rewrite the Click method. However, defining a new class requires system resources (virtual methods are multiplied). If we mark the component on the form and look at the object inspector, we will find that the TlistAdd component exposes few properties and no events, including no event handlers for the OnClick button. Therefore, what we rejected in the last chapter as an incorrect method, redefining the OnClick button handler, is applicable in this case, since the programmer cannot assign a new handler in the object inspector. So, in the private section we describe the header of the new method:

Procedure BtClick(Sender:TObject);

In the implementation of the TListAdd constructor, we assign this handler to the FButton.OnClick event handler:

FButton.OnClick:=BtClick;

Finally, let's implement the BtClick method:

Procedure TListAdd.BtClick(Sender:TObject); begin if length(FEdit.Text)>0 then begin FListBox.Items.Add(FEdit.Text); FEdit.Text:=""; FEdit.SetFocus; end; end;

First, let's check if the one-line editor is empty: we won't add empty lines to the list. Then we transfer the contents of the editor to the list (FListBox.Items.Add(FEdit.Text);) and prepare the editor for entering the next value - namely, we clear it of text (which has already been transferred to the list) and transfer the input focus to it. Now, after compiling and running the application, you can make sure that it works correctly - when you press the button, the contents of the editor are transferred to the list.

Adding Properties and Methods

If you place the TPanel component next to the TListAdd component and compare what is shown in the object inspector, you will notice that a fairly large number of properties and events are exposed for the panel, while only a few properties are exposed for the TListAdd. Meanwhile, the TCustomPanel class is the ancestor of both components. In order to understand the reason, let's open the ExtCtrls.pas module and look at the difference between the TCustomPanel and TPanel classes. It can be noted that all methods and variables that provide the functionality of the panel are defined at the TCustomPanel class level. It also defines properties that are then displayed in the object inspector for TPanel, only these properties are defined in the Protected section. The implementation of the TPanel class is extremely simple: TCustomPanel is defined as an ancestor, and the properties of this class are declared, but in the published section. It becomes clear what needs to be done in the TListAdd class for the properties and methods of the TcustomPanel class to appear in the object inspector, namely to declare the properties. In the published section of the TListAdd class we write:

Property Align; property OnMouseDown;

When declaring a property, you do not need to specify its type or reference variables or methods for reading or writing the property. After compiling the component through the package editor in the object inspector, you can observe the appearance of the Align property and the OnMouseDown event. Thus, for descendants of TCustom... classes, the programmer has the opportunity to choose which properties and events should be displayed in the object inspector and which not. It is for this reason that TCustom... classes are recommended to be used as ancestors for creating components.

Now let's look at how you can introduce a new property (what we did above is the re-declaration of existing properties). The text on the button can be used as a suitable property to display in the object inspector: let the programmer using the TListAdd component change the text at design time. Trying to introduce a new property (let's call it BtCaption) using a declaration:

Property BtCaption:string read FButton.Caption write FButton.Caption;

results in an error when trying to compile the component. Therefore, we define the headers of two methods in the private section:

Function GetBtCaption:string; procedure SetBtCaption(const Value:string);

In the published section we declare the BtCaption property:

Property BtCaption:string read GetBtCaption write SetBtCaption;

Finally, we implement the two declared methods in the implementation section:

Function TListAdd.GetBtCaption:string; begin Result:=FButton.Caption; end; procedure TListAdd.SetBtCaption(const Value:string); begin FButton.Caption:=Value; end;

After compiling a component using the package editor, a new property appears in the Object Inspector. Changing the value of this property is reflected directly at the development stage.

Now let's define a new event. In this case, it would be reasonable to create an event that allows the programmer using this component to analyze the text before adding the contents of the editor to the list and allow or disable adding text to the list. Therefore, this method must contain as a parameter the current value of the text in the editor and depend on a Boolean variable, which the programmer can assign the value True or False. In addition, any event handler in a component must depend on the Sender parameter, in which the component that calls it passes a reference to itself. This is necessary because in the Delphi development environment the same event handler can be called from several different components and the programmer must be able to analyze which component called the handler. So, after the word type in the interface section, before defining TListAdd, we define a new method type:

Type TFilterEvent=procedure(Sender:TObject; const EditText:string; var CanAdd:boolean) of object;

FOnFilter:TFilterEvent;

And in the published section we define a property of this type:

Property OnFilter:TFilterEvent read FOnFilter write FOnFilter;

When defining a new property, we refer to the FOnFilter variable, and not to the methods - they are not required here. Now, if you compile the component using the package editor, you can see the OnFilter event appear in the Object Inspector. However, if we assign a handler to it and run the project for execution, it may not be called. This is because we haven't called it anywhere in our component. A good place to call the OnFilter event is in the OnClick event handler for the FButton, which is already implemented. Therefore, we will change the implementation code of the previously defined BtClick method:

Procedure TListAdd.BtClick(Sender:TObject); var CanAdd:boolean; begin if length(FEdit.Text)>0 then begin CanAdd:=True; if Assigned(FOnFilter) then FOnFilter(Self,FEdit.Text,CanAdd); if CanAdd then begin FListBox.Items.Add(FEdit.Text); FEdit.Text:=""; FEdit.SetFocus; end else beep; end; end;

So, in the above code snippet, the boolean variable CanAdd is defined. When writing code, be aware that the programmer may not create an OnFilter event handler. Therefore, we set the default value of the CanAdd variable to True - add all rows to the list. Next, before calling FonFilter, you should check whether the programmer has created an event handler. This is achieved by calling the Assigned method, which returns a boolean value. For a pointer, calling the Assigned method is equivalent to checking P<>nil. For an object method we cannot use FOnFilter check<>nil, since the object method is characterized by two addresses and such a check will not be allowed by the compiler. But calling the Assigned method perfectly checks whether an event handler has been made. The above code is absolutely standard way calling an event handler from a component.

All that remains is to test the event handler. Let's place two TListAdd components on the form, for one we will allow adding only integers, and for the other - only words starting with capital English letters. Accordingly, the code for the OnFilter event handlers will look like this:

Procedure TForm1.ListAdd1Filter(Sender: TObject; const EditText: String; var CanAdd: Boolean); var I,N:integer; begin Val(EditText,N,I); CanAdd:=I=0; end; procedure TForm1.ListAdd2Filter(Sender: TObject; const EditText: String; var CanAdd: Boolean); begin CanAdd:=False; if length(EditText)>0 then CanAdd:=(EditText>="A") and (EditText<="Z"); end;

The code is easy to understand, the only caveat is that it checks that the text is not an empty string before checking the first letter of the text in the ListAdd2Filter event handler. Carrying out such a check is mandatory: strings in Object Pascal are objects, and an empty string corresponds to a nil pointer. If you try to check the first letter of an empty string, the application will try to dereference nil, which will throw an exception. In this case, this is not a problem: before calling the FOnFilter event handler from the TListAdd component, the string is checked for non-zero length. However, for components whose source code is not available to you, such verification is mandatory!

Hiding Properties in the Object Inspector

Suppose you are making a data access component, for example, a descendant of the TTable class. Let's say this component analyzes the list of tables available in the database, and based on some criteria (for example, the presence of a field of a certain type and with a certain name), one is selected for work. For normal operation of the component, the name of this table must be entered in the TableName property. But this property is visible in the object inspector! A programmer using this component could change its value during development, which would presumably make the component inoperable. And he will be right! If any of the properties or events cannot be changed, they should be hidden.

We will continue working on the TListAdd component and remove the Cursor property from the object inspector as a model task. This property is defined in the published section of the TControl class and is displayed in the object inspector for TListAdd from the very beginning of component development. Based on this, you can try to override this property in the protected section. The compiler will allow such an override, but it will not lead to the desired result: the Cursor property will remain in the object inspector as it was... Any property, once defined in the published section, will always be displayed in the object inspector for all descendants of this class.

To hide a property from the object inspector, we use two features of the Delphi compiler, namely:

  1. When declaring a new property with the same name as an existing property, the previously defined property is “shadowed.”
  2. Properties that have read-only or write-only access do not appear in the object inspector, even if they are declared in the published section.

Before you start working on hiding the Cursor property, it's a good idea to remove the TListAdd components from the form, otherwise an exception may occur when reading the form resource. So, in the private section we declare the variable FDummy:integer (the name and type of the variable can be anything) and in the published section we define a new property:

Property Cursor:integer read FDummy;

The new property must be called Cursor, its type must match the type of the variable defined above, the property must be read-only or write-only. After compiling the component using the package editor, you should place the TListAdd component on the form again. You may find that the Cursor property is no longer visible in the Object Inspector.

Now let's complicate the task a little. Suppose you want the cursor to be shown not as an arrow, but as an hourglass (crHourGlass). To change the default value of properties, the new value must be assigned to a variable in the constructor. When trying to assign a new value to Cursor in the constructor

Cursor:=crHourGlass;

The Delphi compiler will issue a diagnostic message stating that a new value cannot be assigned to a read-only variable. If you make a new “write-only” property, the compiler will issue a different diagnostic message – about incompatible data types. If you declare the variable FDummy:TCursor and make it write-only, the compiler will allow this assignment, but the appearance of the cursor will not change: it will still be an arrow.

A trivial solution to this problem is to declare a descendant class of TCustomPanel, in the constructor of which you need to assign a new value to the Cursor variable, and from it produce our TListAdd component. This solution has two disadvantages:

  1. It is resource-intensive - virtual methods multiply.
  2. We hid the property in the object inspector from the programmer who will use this component. We want to work with this property.

Therefore, the solution to this problem looks like this: in the TListAdd constructor we declare the operator:

Inherited Cursor:=crHourGlass;

and that's it! This is enough to change the cursor.

Previously, we used the inherited function word only to call an ancestor method. This construction allows you to better understand the meaning of inherited as a reference to an ancestor class. You can access both properties and methods. When accessing a property, you can either read it or assign a new value to it; in this case, the auxiliary word inherited appears to the left of the assignment sign. Similarly, you can call hidden ancestor methods. Hierarchy calls higher than the ancestor class are prohibited - construction

Inherited inherited Cursor:=crHourGlass;

will not compile.

At this point we will consider this project completed. In the new component, we intercepted the message, declared the properties, added new properties and events, and hid the previously declared property. All these methods are used to create components. Below we will look at another interesting method.

Using Hook Procedures to Create Components

It was mentioned earlier that each TWinControl child has a procedure that receives and processes messages. If there is a reference to the window handle (HWND), then you can determine the address of this procedure and, more importantly, override this address and thus process the received messages in your own way. As a rule, no one writes complete handlers for all messages; the old default method is called more often. In this case, the new procedure is used as a filter: when an event arrives, the code is executed. In fact, this is a “spy” in TwinControl: we are notified when a message arrives and some code can be executed. If the Hook procedure is implemented correctly, TWinControl continues to work as usual, unaware that it is sharing its messages with someone else.

The hook procedure is defined as follows:

Procedure(var Message:TMessage) of object;

It depends on a variable of type TMessage, which contains all the information about the message. But defining this procedure is not enough. It must be copied for each TWinControl to which it will be attached. This is achieved by calling the WinAPI method MakeObjectInstance. This method takes an object method as a parameter, makes a copy of it in memory, and returns the address of the new method. It is clear that this reserves system resources that need to be returned to the system. This is achieved by calling the FreeObjectInstance method.

Another important condition: before destroying TWinControl, communication with the old message processing procedure must be restored, otherwise resources will not be returned to the system. This means that you will have to remember the pointer to the old procedure, which can be found by calling the Win API GetWindowLong method with the GWL_WNDPROC parameter. This pointer will also be used to call TWinControl's default event handlers. The reverse method, SetWindowLong, is used to set a Hook procedure.

So, let's formulate the problem for the next exercise. Let's say we want to create a component that will make other components - descendants of TWinControl - beep when the mouse button is pressed. It is clear that this component should not be shown during application execution, so we will select TComponent as its ancestor class. Let's define the class name as TBeepWnd. In the private section we define three variables:

FOldProc,FNewProc:pointer; FControl:TWinControl;

From the names it is clear that we will remember the link to the old procedure in the FOldProc variable, the link to the new procedure (after executing the MakeObjectInstance method) will be stored in the FNewProc variable. And in the FControl variable we will store a link to the control element on which the Hook procedure is currently “hung”. Let's define three methods in the same section:

Procedure HookProc(var Message:TMessage); procedure HookWindow(W:TWinControl); procedure UnhookWindow;

and in the implementation section we implement them:

Procedure TBeepWnd.HookProc(var Message:TMessage); begin case Message.Msg of WM_LBUTTONDOWN:begin (Our task) Beep; Message.Result:=CallWindowProc(FOldProc, FControl.Handle, Message.Msg, Message.WParam, Message.lParam); end; WM_DESTROY:begin (When window is about destroying, remove hook) Message.Result:=CallWindowProc(FOldProc, FControl.Handle, Message.Msg, Message.WParam, Message.lParam); UnhookWindow; end; (Call default handler) else Message.Result:=CallWindowProc(FOldProc, FControl.Handle, Message.Msg, Message.WParam, Message.lParam); end; end;

In the Hook procedure itself, a message is intercepted, to which a reaction occurs - WM_LBUTTONDOWN. In addition, any Hook procedure must handle the WM_DESTROY message. This is the last message that is sent to the window before it is destroyed. Our response is to restore the previous method by calling the UnhookWindow method described below. Finally, default message handlers are called throughout using the CallWindowProc method. Forgetting the default event handler is the same as forgetting inherited in the event handler; in 80% of cases this will lead to incorrect application behavior. In no case should you forget to assign the result of calling the CallWindowProc method to the Result field of the Message variable! The code will not work in this case!

Procedure TBeepWnd.HookWindow(W:TWinControl); begin if csDesigning in ComponentState then begin (Checking if component at design or run-time) FControl:=W; Exit; end; if FControl<>nil then UnhookWindow; (Remove hook if it was previously installed) if W<>nil then begin FOldProc:=pointer(GetWindowLong(W.Handle,GWL_WNDPROC)); (Determines address of old procedure) FNewProc:=MakeObjectInstance(HookProc); (Make copy in memory) SetWindowLong(W.Handle,GWL_WNDPROC,integer(FNewProc)); (Set new procedure) end; FControl:=W; (Store reference at control) end;

This method is used to set up a new message handling routine. First, it checks which stage the component is in: at the development stage or at the execution stage. If the component is at the development stage, that is, the csDesigning flag is set in the ComponentState property, then simply a link to the component is saved without installing a Hook procedure. This is done in order to avoid installing a Hook procedure on the Delphi development environment. If this procedure was previously set on another control, it is removed by calling the UnhookWindow method. After this, the address of the old procedure is remembered (GetWindowLong), a copy is made in memory of the new procedure (MakeObjectInstance) and the address of the new procedure is set (SetWindowLong). Type casting from integer to pointer is used, and vice versa - called methods require (or return) variables of not quite suitable types. Finally, the reference to the control is stored in the FControl variable, which we defined in the private section.

Procedure TBeepWnd.UnhookWindow; begin if (FControl=nil) or (FOldProc=nil) or (FNewProc=nil) then Exit; (No hook was installed) SetWindowLong(FControl.Handle,GWL_WNDPROC,integer(FOldProc)); (Set old window procedure) FreeObjectInstance(FNewProc); (Free resources) FControl:=nil; (Initiate variables) FOldProc:=nil; FNewProc:=nil; end;

This method restores the old event handler. It is called from the HookProc method and must also be called from the component's destructor - the Hook must be removed both when the window is destroyed and when this component is destroyed. The SetWindowLong method with the address of the old method restores the old message handler. After this, you should return the resources to the system by calling the FreeObjectInstance method.

So, the basic methods for working with the Hook procedure have been defined. Now you need to rewrite the destructor so that the Hook procedure is removed when this component is destroyed:

Destructor TBeepWnd.Destroy; begin UnhookWindow; inherited Destroy; end;

And finally, in the published section we define a property that will be displayed in the object inspector:

property Control:TWinControl read FControl write HookWindow;

To install a new component, we refer to a previously defined method, which, while the application is running, will immediately “hang” a Hook procedure on the component, which will beep when the button is pressed. Recall that instead of the Beep operator, you can write any executable code.

The component is tested quite simply: it is placed on a form on which several TWinControl descendant components are placed. After selecting the TBeepWnd component in the background, clicking the mouse in the Control field in the object inspector expands a list containing all TWinControl defined on the form. You should select one of them and launch the application. When you click the left mouse button on the selected component, it emits a squeak.

Property editors and component editors

Everything covered in the previous sections relates to creating the application code that will be distributed to users. However, the Delphi development environment allows you to modify itself. This does not require knowledge of a special language, since all methods for changing the development environment are written in Delphi. Here these methods, namely property editors and component editors, are considered partially - in terms of creating tools for working with components. When reading the materials in this section, you should clearly understand that the end user working with your application will never see either the property editor or the component editor - they are created for programmers and work only in the Delphi development environment.

Property editors

During application development, properties are displayed in the Object Inspector. Please note that properties are edited differently in the Object Inspector. Some properties (Width, Caption) can only be assigned a new text value. A property of type Cursor provides a drop-down list that you can click to select a value. A property of type TFont has a "+" sign on the left; When you click on it, it expands, allowing you to modify individual fields. In addition, on the right there is a button with three dots (elliptic button), when clicked, the property editor dialog appears.

Each of the above properties has its own editor, and a big advantage of the Delphi development environment is the ability to create your own property editors. New property editors are quite common among distributed components. But they must be treated with caution: initially run the tests on a computer where you can re-install Delphi if necessary. As a rule, they are created by qualified programmers and there are no complaints about the code, but they often forget to include any DLL in the distributed property editor. After installing such an editor, we get a number of properties that cannot be edited - the old editor is blocked, and the new one does not work...

Before creating a new property editor, it makes sense to think about whether it is worth doing this - you can probably find a suitable one among the standard editors. If you have to create a property editor, you must follow the rule: you should avoid creating editors for standard data types (integer, string, etc.). Other programmers are used to standard editors, and they may not like yours. Therefore, you will have to be modest and register the editor for your class, and not for the TComponent class. If programmers like your property editor, most of them will be able to change the registration themselves so that the editor works for all components. We will discuss the issue of registering an editor below.

So, let's pose a model problem, for the implementation of which it will be necessary to implement a property editor. Suppose some component has the property - day of the week. In principle, to enter the day of the week, you can use a standard editor with a drop-down list. However, we want the programmer at the development stage to be able to enter the day of the week, specifying either its serial number (1 - Monday, 2 - Tuesday, etc.), or text in the national or English language. When entering text, mixing uppercase and lowercase letters is allowed.

First of all, you need to create a component that will store the day of the week. Let's create a new component by calling the Component/New component command. Let's select TComponent as the ancestor class and give the new class the name TDayStore. After that, install the component in the palette. Now we need to decide in what form to store the day of the week. It is clear that for unambiguous identification and saving resources it should be stored as an integer with valid ranges 1-7. However, if we are going to create a property editor, we should remember the rule about not creating new editors for existing types. Therefore, we will define a new type - TDayWeek, and all operations with it will be performed as with integers. Let's define the FDay variable in the private section of the component. Because this variable will be initialized to 0 when the default constructor is executed, and this number is outside the allowed values, the constructor must be rewritten. Finally, let's define the DayWeek property in the published section to display it in the object inspector. The final component looks like this:

Type TDayWeek=type integer; TDayStore = class(TComponent) private ( Private declarations ) FDay:TDayWeek; protected ( Protected declarations ) public ( Public declarations ) constructor Create(AOwner:TComponent); override; published ( Published declarations ) property DayWeek:TDayWeek read FDay write FDay; end; ... implementation constructor TDayStore.Create(AOwner:TComponent); begin inherited Create(Aowner); FDay:=1; end;

It is worth paying attention to the rare construction of the definition of a new type

TDayWeek=type integer;

Thus, a new data type is introduced, which has the same size as the integer type; all operations on this data type are carried out as with integers. The point of this operation is to declare a new data type so that our property editor can be applied specifically to it and does not affect other data types.

Now let's create an editor for the TDayWeek property. To do this, add a new form to the existing project, remember it under some suitable name (DayPropE.pas) and exclude it from the project. After this, we will open the form as a separate file and implement the property editor in it. At the first stage we will not need the form, but later we will implement a dialogue on it.

The module for creating property editors is called DsgnIntf.pas (Design Interface), it defines the base class TPropertyEditor and descendant classes intended for editing standard properties - TIntegerProperty, TFloatProperty, TStringProperty, etc. The mechanism of operation of property editors is as follows:

  1. It is registered in the Delphi development environment by calling the RegisterPropertyEditor method. This method takes the following values ​​as parameters:

    a) information about the type of properties for editing which this editor is intended. Because of this information, we had to define a new type TDayWeek;

    b) information about the component in which this editor is applicable. The editor will be called not only for the specified component, but also for all its descendants. If you set this to TComponent, the editor will be called for any component;

    c) the name of the property for which this editor is used. If name is an empty string, the above two filters are used;

  2. The GetValue method is called when the current value of a property needs to be read from the component. For any property, this method returns a string that is placed in the object inspector.
  3. The SetValue method is called when the programmer has entered a new property value in the Object Inspector. A new string is passed as a parameter. In the method, it must be analyzed and converted to the type of the property being edited.

The GetValue and SetValue methods are virtual; when they are rewritten, new property editors are created. So now we can start creating a new property editor.

Let's refer to the DsgnIntf ​​module in the uses section of the DayPropE.pas module and define a new class in the Interface section:

Type TDWPropED=class(TPropertyEditor) public function GetValue:string; override; procedure SetValue(const Value:string); override; end;

The implementation section should implement both of these methods. In this case, we additionally need a list of names of the days of the week - in the initial formulation of the problem it is required that the programmer be able to enter the day of the week:

Const DayWeek:array of string = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"); DayWeekEn:array of string = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday");

Publications on the topic