In Chapter 4, I discussed the base classes of the Delphi library, focusing particularly on the TComponent class. One of the most important subclasses of TComponent is TControl, which corresponds to visual components. This base class is available in both CLX and VCL and defines general concepts, such as the position and the size of the control, the parent control hosting it, and more. For an actual implementation, though, you have to refer to its two subclasses. In VCL these are TWinControl and TGraphicControl; in CLX they are TWidgetControl and TGraphicControl. Here are their key features:
Window-Based Controls (Also Called Windowed Controls) Visual components based on an operating-system window. A TWinControl in VCL has a window handle, which is a number referring to an internal Windows structure. A TWidgetControl in CLX has a Qt handle, which is a reference to the internal Qt object. From a user perspective, windowed controls can receive the input focus, and some of them can contain other controls. This is the biggest group of components in the Delphi library. We can further divide windowed controls into two groups: wrappers of native controls of Windows or Qt; and custom controls, which generally inherit from TCustomControl.
Graphical Controls (Also Called Nonwindowed Controls) Visual components not based on an operating-system window. Therefore, these components have no handle, cannot receive the focus, and cannot contain other controls. They inherit from TGraphicControl and are painted by their parent control, which sends them mouse-related and other events. Examples of non-windowed controls are the Label and SpeedButton components. There are just a few controls in this group, which were critical to minimizing the use of system resources in the early days of Delphi (on 16-bit Windows). Using graphical controls to save Windows resources is still useful on Win9x/Me, which has pushed the system limits higher but hasn't fully gotten rid of them (unlike Windows NT/2000).
The Parent property of a control indicates which other control is responsible for displaying it. When you drop a component into a form in the Form Designer, the form will become both parent and owner of the new control. But if you drop the component inside a Panel, ScrollBox, or any other container component, this will become its parent, whereas the form will still be the owner of the control.
When you create the control at run time, you'll need to set the owner (using the Create constructor's parameter); but you must also set the Parent property, or the control won't be visible.
Like the Owner property, the Parent property has an inverse. The Controls array lists all the controls parented by the current one, numbered from 0 to ControlCount - 1. You can scan this property to operate on all the controls hosted by another control, eventually using a recursive method that operates on the controls parented by each subcontrol.
Some of the properties introduced by TControl and common to all controls are those related to size and position. The position of a control is determined by its Left and Top properties, and its size is specified by the Height and Width properties. Technically, all components have a position, because when you reopen an existing form at design time, you want to be able to see the icons for the nonvisual components in exactly the position where you've placed them. This position is visible in the form file.
As you change any of the positional or size properties, you end up calling the single SetBounds method. So, any time you need to change two or more of these properties at once, calling SetBounds directly will speed up the program. Another method, BoundsRect, returns the rectangle bounding the control and corresponds to accessing the properties Left, Top, Height, and Width.
An important feature of the position of a component is that, like any other coordinate, it always relates to the client area of its parent component (indicated by its Parent property). For a form, the client area is the surface included within its borders and caption (excluding the borders themselves). It would have been messy to work in screen coordinates, although some ready-to-use methods convert the coordinates between the form and the screen and vice versa.
Note, however, that the coordinates of a control are always relative to the parent control, such as a form or another container component. If you place a panel in a form and a button in a panel, the coordinates of the button relate to the panel and not to the form containing the panel. In this case, the parent component of the button is the panel.
You can use two basic properties to let the user activate or hide a component. The simpler is the Enabled property. When a component is disabled (when Enabled is set to False), usually some visual hint indicates this state to the user. At design time, the "disabled" property does not always have an effect; but at run time, disabled components are generally grayed.
For a more radical approach, you can completely hide a component, either by using the corresponding Hide method or by setting its Visible property to False. Be aware, however, that reading the status of the Visible property does not tell you whether the control is actually visible. If the container of a control is hidden, even if the control is set to Visible, you cannot see it. For this reason, you can read the value of the run-time, read-only Showing property to determine whether the control is really visible to the user; that is, if it is visible, its parent control is also visible, the parent control of the parent control is also visible, and so on.
The Color and Font properties are often used to customize the user interface of a component. Several properties are related to the color. The Color property itself usually refers to the background color of the component. There is also a Color property for fonts and many other graphical elements. Many components also have ParentColor and ParentFont properties, indicating whether the control should use the same font and color as its parent component, which is usually the form. You can use these properties to change the font of each control on a form by setting only the Font property of the form itself.
When you set a font, either by entering values for the attributes of the property in the Object Inspector or by using the standard font selection dialog box, you can choose one of the fonts installed in the system. The fact that Delphi allows you to use all the fonts installed on your system has both advantages and drawbacks. The main advantage is that if you have a number of nice fonts installed, your program can use any of them. The drawback is that if you distribute your application, these fonts might not be available on your users' computers.
If your program uses a font that your user doesn't have, Windows will select some other font to use in its place. A program's carefully formatted output can be ruined by the font substitution. For this reason, you should rely on standard Windows fonts (such as MS Sans Serif, System, Arial, Times New Roman, and so on).
There are various ways to set the value of a color. The type of this property is TColor, which isn't a class type but just an integer type. For properties of this type, you can choose a value from a series of predefined name constants or enter a value directly. The constants for colors include clBlue, clSilver, clWhite, clGreen, clRed, and many others (including Delphi 6's clMoneyGreen, clSkyBlue, clCream, and clMedGray). As a better alternative, you can use one of the colors used by the system to denote the status of given elements. These sets of colors are different in VCL and CLX.
VCL includes predefined Windows colors such as the background of a window (clWindow), the color of the text of a highlighted menu (clHighlightText), the active caption (clActiveCaption), and the ubiquitous button face color (clBtnFace). CLX includes a different and incompatible set of system colors, including clBackground, which is the standard color of a form; clBase, used by edit boxes and other visual controls; clActiveForeground, the foreground color for active controls; and clDisabledBase, the background color for disabled text controls. All the color constants mentioned here are listed in VCL and CLX Help files under the "TColor type" topic.
Another option is to specify a TColor as a number (a 4-byte hexadecimal value) instead of using a predefined value. If you use this approach, you should know that the low three bytes of this number represent RGB color intensities for blue, green, and red, respectively. For example, the value $00FF0000 corresponds to a pure blue color, the value $0000FF00 to green, the value $000000FF to red, the value $00000000 to black, and the value $00FFFFFF to white. By specifying intermediate values, you can obtain any of 16 million possible colors.
Instead of specifying these hexadecimal values directly, you should use the Windows RGB function, which has three parameters, all ranging from 0 to 255. The first indicates the amount of red, the second the amount of green, and the last the amount of blue. Using the RGB function makes programs generally more readable than using a single hexadecimal constant. RGB is almost a Windows API function; it is defined by the Windows-related units and not by Delphi units, but a similar function does not exist in the Windows API. C includes a macro that has the same name and effect, which is a welcome addition to the Windows unit. RGB is not available on CLX, so I've written my own version:
function RGB (red, green, blue: Byte): Cardinal; begin Result := blue + green * 256 + red * 256 * 256; end;
The highest-order byte of the TColor type is used to indicate which palette should be searched for the closest matching color, but palettes are too advanced a topic to discuss here. (Sophisticated imaging programs also use this byte to carry transparency information for each display element on the screen.)
Regarding palettes and color matching, note that Windows sometimes replaces an arbitrary color with the closest available solid color, at least in video modes that use a palette. This is always the case with fonts, lines, and so on. At other times, Windows uses a dithering technique to mimic the requested color by drawing a tight pattern of pixels with the available colors. In 16-color (VGA) adapters and at higher resolutions, you often end up seeing strange patterns of pixels of different colors rather than the color you had in mind.
In Windows, most elements of the user interface are windows. From a user standpoint, a window is a portion of the screen surrounded by a border, having a caption and usually a system menu. But technically speaking, a window is an entry in an internal system table, often corresponding to an element visible on the screen that has some associated code. Most of these windows have the role of controls; others are temporarily created by the system (for example, to show a pull-down menu). Still other windows are created by the application but remain hidden from the user and are used only as a way to receive a message (for example, nonblocking sockets use windows to communicate with the system).
The common denominator of all windows is that they are known by the Windows system and refer to a function for their behavior; each time something happens in the system, a notification message is sent to the proper window, which responds by executing some code. Each window of the system has an associated function (generally called its window procedure), which handles the various messages the window is interested in.
In Delphi, any TWinControl class can override the WndProc method or define a new value for the WindowProc property. Interesting Windows messages, however, can be better tracked by providing specific message handlers. Even better, VCL converts these lower-level messages into events. In short, Delphi allows you to work at a high level, making application development easier, but still allows you to go low-level when required.
Notice also that creating an instance of a TWinControl-based class doesn't automatically create its corresponding Window handle. Delphi uses a lazy initialization technique, so that the low-level control is created only when required—generally as soon as a method accesses the Handle property. The get method for this property calls HandleNeeded the first time, which eventually calls CreateHandle … and so on, eventually reaching CreateWnd, CreateParams, and CreateWindowHandle (the sequence is complex, and you don't need to know it in detail). At the opposite end, you can keep an existing (perhaps invisible) control in memory but destroy its window handle, to save system resources.
In CLX, every TWidgetControl has an internal Qt object, referenced using the Handle property. This property has the same name as the corresponding Windows property, but it is totally different behind the scenes.
A TWidgetControl object generally owns the corresponding Qt/C++ object. The CLX class uses delayed construction (the internal object is not created until one of its methods is required) implemented in InitWidget and other methods. The CLX class also frees the internal object when it is destroyed. However, it is also possible to create a widget around an existing Qt object: In this case, the CLX object won't own the Qt object and won't destroy it. This behavior is indicated in the OwnHandle property.
To be more precise, each VisualCLX component has two associated C++ objects: the Qt Handle and the Qt Hook, which is the object receiving the system events. With the current Qt design, the Qt Hook must be a C++ object, which acts as an intermediary to the event handlers of the Delphi language control. The HookEvents method associates the hook object to the CLX control.
Unlike Windows, Qt defines two different types of events:
Events are the translation of user input or system events (such as keystroke, mouse move, and paint).
Signals are internal component events (corresponding to VCL internal or abstract operations, such as OnClick and OnChange).
The events of a CLX component, however, merge events and signals. Generic Delphi CLX control events include OnMouseDown, OnMouseMove, OnKeyDown, OnChange, OnPaint, and many others, exactly as in the VCL (which fires most events as a response to Windows messages).
Expert programmers may notice that CLX includes a seldom-used EventHandler method, which corresponds more or less to the WndProc method of VCL TWinControl.