Chapter 5
Developing the application user interface

With Delphi, you create a user interface (UI) by selecting components from the Component palette and dropping them onto forms.

Understanding TApplication, TScreen, and TForm

TApplication, TScreen, and TForm are VCL classes that form the backbone of all Delphi applications by controlling the behavior of your project. The TApplication class forms the foundation of a Windows application by providing properties and methods that encapsulate the behavior of a standard Windows program. TScreen is used at runtime to keep track of forms and data modules that have been loaded as well as system specific information such as screen resolution and what fonts are available for display. Instances of the TForm class are the building blocks of your application's user interface. The windows and dialog boxes of your application are based on TForm.

Using the main form

TForm is the key class for creating Windows GUI applications.

The first form you create and save in a project becomes, by default, the project's main form, which is the first form created at runtime. As you add forms to your projects, you might decide to designate a different form as your application's main form. Also, specifying a form as the main form is an easy way to test it at runtime, because unless you change the form creation order, the main form is the first form displayed in the running application.

To change the project main form,

  1. Choose Project|Options and select the Forms page.
  2. In the Main Form combo box, select the form you want as the project main form and choose OK.

Now if you run the application, your new main form choice is displayed.

Adding additional forms

To add an additional form to your project, select File|New Form. You can see all your project's forms and their associated units listed in the Project Manager (View|Project Manager).

Linking forms

Adding a form to a project adds a reference to it in the project file, but not to any other units in the project. Before you can write code that references the new form, you need to add a reference to it in the referencing forms' unit files. This is called form linking.

A common reason to link forms is to provide access to the components in that form. For example, you'll often use form linking to enable a form that contains data-aware components to connect to the data-access components in a data module.

To link a form to another form,

  1. Select the form that needs to refer to another.
  2. Choose File|Use Unit.
  3. Select the name of the form unit for the form to be referenced.
  4. Choose OK.

Linking a form to another just means that the uses clauses of one form unit contains a reference to the other's form unit, meaning that the linked form and its components are now in scope for the linking form.

Avoiding circular unit references

When two forms must reference each other, it's possible to cause a "Circular reference" error when you compile your program. To avoid such an error, do one of the following:

Do not place both uses clauses in the interface parts of their respective unit files. This will generate the "Circular reference" error at compile time.

Working at the application level

The global variable Application, of type TApplication, is in every Delphi Windows application. Application encapsulates your application as well as providing many functions that occur in the background of the program. For instance, Application would handle how you would call a help file from the menu of your program. Understanding how TApplication works is more important to a component writer than to developers of stand-alone applications, but you should set the options that Application handles in the Project|Options Application page when you create a project.

In addition, Application receives many events that apply to the application as a whole. For example, the OnActivate event lets you perform actions when the application first starts up, the OnIdle event lets you perform background processes when the application is not busy, the OnMessage event lets you intercept Windows messages, and so on. Although you can't use the IDE to examine the properties and events of the global Application variable, another component, TApplicationEvents, intercepts the events and lets you supply event-handlers using the IDE.

Handling the screen

An global variable of type TScreen called Screen is created when you create a project. Screen encapsulates the state of the screen on which your application is running. Common tasks performed by Screen include specifying the look of the cursor, the size of the window in which your application is running, the list of fonts available to the screen device, and multiple screen behavior. If your application runs on multiple monitors, Screen maintains a list of monitors and their dimensions so that you can effectively manage the layout of your user interface.

Managing layout

At its simplest, you control the layout of your user interface by how you place controls in your forms. The placement choices you make are reflected in the control's Top, Left, Width, and Height properties. You can change these values at runtime to change the position and size of the controls in your forms.

Controls have a number of other properties, however, that allow them to automatically adjust to their contents or containers. This allows you to lay out your forms so that the pieces fit together into a unified whole.

Two properties affect how a control is positioned and sized in relation to its parent. The Align property lets you force a control to fit perfectly within its parent along a specific edge or filling up the entire client area after any other controls have been aligned. When the parent is resized, the controls aligned to it are automatically resized and remain positioned so that they fit against a particular edge.

If you want to keep a control positioned relative to a particular edge of its parent, but don't want it to necessarily touch that edge or be resized so that it always runs along the entire edge, you can use the Anchors property.

If you want to ensure that a control does not grow too big or too small, you can use the Constraints property. Constraints lets you specify the control's maximum height, minimum height, maximum width, and minimum width. Set these to limit the size (in pixels) of the control's height and width. For example, by setting the MinWidth and MinHeight of the constraints on a container object, you can ensure that child objects are always visible.

The value of Constraints propagates through the parent/child hierarchy so that an object's size can be constrained because it contains aligned children that have size constraints. Constraints can also prevent a control from being scaled in a particular dimension when its ChangeScale method is called.

TControl introduces a protected event, OnConstrainedResize, of type TConstrainedResizeEvent:

TConstrainedResizeEvent = procedure(Sender: TObject; var MinWidth, MinHeight, MaxWidth, 
MaxHeight: Integer) of object;

This event allows you to override the size constraints when an attempt is made to resize the control. The values of the constraints are passed as var parameters which can be changed inside the event handler. OnConstrainedResize is published for container objects (TForm, TScrollBox, TControlBar, and TPanel). In addition, component writers can use or publish this event for any descendant of TControl.

Controls that have contents that can change in size have an AutoSize property that causes the control to adjust its size to its font or contained objects.

Working with messages

A message is a notification that some event has occurred that is sent by Windows to an application. The message itself is a record passed to a control by Windows. For instance, when you click a mouse button on a dialog box, Windows sends a message to the active control and the application containing that control reacts to this new event. If the click occurs over a button, the OnClick event could be activated upon receipt of the message. If the click occurs just in the form, the application can ignore the message.

The record type passed to the application by Windows is called a TMsg. Windows predefines a constant for each message, and these values are stored in the message field of the TMsg record. Each of these constants begin with the letters wm.

The VCL automatically handles messages unless you override the message handling system and create your own message handlers. For more information on messages and message handling, see "Understanding the message-handling system", "Changing message handling", and "Creating new message handlers".

More details on forms

When you create a form in Delphi from the IDE, Delphi automatically creates the form in memory by including code in the WinMain() function. Usually, this is the desired behavior and you don't have to do anything to change it. That is, the main window persists through the duration of your program, so you would likely not change the default Delphi behavior when creating the form for your main window.

However, you may not want all your application's forms in memory for the duration of the program execution. That is, if you do not want all your application's dialogs in memory at once, you can create the dialogs dynamically when you want them to appear.

Forms can be modal or modeless. Modal forms are forms with which the user must interact before switching to another form (for example, a dialog box requiring user input). Modeless forms, though, are windows that are displayed until they are either obscured by another window or until they are closed or minimized by the user.

Controlling when forms reside in memory

By default, Delphi automatically creates the application's main form in memory by including the following code in the application's project source unit:

Application.CreateForm(TForm1, Form1);

This function creates a global variable with the same name as the form. So, every form in an application has an associated global variable. This variable is a pointer to an instance of the form's class and is used to reference the form while the application is running. Any unit that includes the form's unit in its uses clause can access the form via this variable.

All forms created in this way in the project unit appear when the program is invoked and exist in memory for the duration of the application.

Displaying an auto-created form

If you choose to create a form at startup, and do not want it displayed until sometime later during program execution, the form's event handler uses the ShowModal method to display the form that is already loaded in memory:

procedure TMainForm.Button1Click(Sender: TObject);
begin
  ResultsForm.ShowModal;
end;

In this case, since the form is already in memory, there is no need to create another instance or destroy that instance.

Creating forms dynamically

You may not always want all your application's forms in memory at once. To reduce the amount of memory required at load time, you may want to create some forms only when you need to use them. For example, a dialog box needs to be in memory only during the time a user interacts with it.

To create a form at a different stage during execution using the IDE, you:

  1. Select the File|New Form from the Component bar to display the new form.
  2. Remove the form from the Auto-create forms list of the Project Options|Forms page.

    This removes the form's invocation at startup. As an alternative, you can manually remove the following line from the project source:

    Application.CreateForm(TResultsForm, ResultsForm);
    
  3. Invoke the form when desired by using the form's Show method, if the form is modeless, or ShowModal method, if the form is modal.

An event handler for the main form must create an instance of the result form and destroy it. One way to invoke the result form is to use the global variable as follows. Note that ResultsForm is a modal form so the handler uses the ShowModal method.

procedure TMainForm.Button1Click(Sender: TObject);
begin
  ResultsForm:=TResultForm.Create(self)
  ResultsForm.ShowModal;
  ResultsForm.Free;
end;

The event handler in the example deletes the form after it is closed, so the form would need to be recreated if you needed to use ResultsForm elsewhere in the application. If the form were displayed using Show you could not delete the form within the event handler because Show returns while the form is still open.

Note: If you create a form using its constructor, be sure to check that the form is not in the Auto-create forms list on the Project Options|Forms page. Specifically, if you create the new form without deleting the form of the same name from the list, Delphi creates the form at startup and this event-handler creates a new instance of the form, overwriting the reference to the auto-created instance. The auto-created instance still exists, but the application can no longer access it. After the event-handler terminates, the global variable no longer points to a valid form. Any attempt to use the global variable will likely crash the application.

Creating modeless forms such as windows

You must guarantee that reference variables for modeless forms exist for as long as the form is in use. This means that these variables should have global scope. In most cases, you use the global reference variable that was created when you made the form (the variable name that matches the name property of the form). If your application requires additional instances of the form, declare separate global variables for each instance.

Using a local variable to create a form instance

A safer way to create a unique instance of a modal form is to use a local variable in the event handler as a reference to a new instance. If a local variable is used, it does not matter whether ResultsForm is auto-created or not. The code in the event handler makes no reference to the global form variable. For example:

procedure TMainForm.Button1Click(Sender: TObject);
var
  RF:TResultForm;
begin
  RF:=TResultForm.Create(self)
  RF.ShowModal;
  RF.Free;
end;

Notice how the global instance of the form is never used in this version of the event handler.

Typically, applications use the global instances of forms. However, if you need a new instance of a modal form, and you use that form in a limited, discrete section of the application, such as a single function, a local instance is usually the safest and most efficient way of working with the form.

Of course, you cannot use local variables in event handlers for modeless forms because they must have global scope to ensure that the forms exist for as long as the form is in use. Show returns as soon as the form opens, so if you used a local variable, the local variable would go out of scope immediately.

Passing additional arguments to forms

Typically, you create forms for your application from within the IDE. When created this way, the forms have a constructor that takes one argument, Owner, which is the owner of the form being created. (The owner is the calling application object or form object.) Owner can be nil.

To pass additional arguments to a form, create a separate constructor and instantiate the form using this new constructor. The example form class below shows an additional constructor, with the extra argument whichButton. This new constructor is added to the form class manually.

TResultsForm = class(TForm)
  ResultsLabel: TLabel;
  OKButton: TButton;
  procedure OKButtonClick(Sender: TObject);
private
public
  constructor CreateWithButton(whichButton: Integer; Owner: TComponent);
end;

Here's the manually coded constructor that passes the additional argument, whichButton. This constructor uses the whichButton parameter to set the Caption property of a Label control on the form.

constructor CreateWithButton(whichButton: Integer; Owner: TComponent);
begin
  case whichButton of
    1: ResultsLabel.Caption := 'You picked the first button.';
    2: ResultsLabel.Caption := 'You picked the second button.';
    3: ResultsLabel.Caption := 'You picked the third button.';
  end;
end;

When creating an instance of a form with multiple constructors, you can select the constructor that best suits your purpose. For example, the following OnClick handler for a button on a form calls creates an instance of TResultsForm that uses the extra parameter:

procedure TMainForm.SecondButtonClick(Sender: TObject);
var
  rf: TResultsForm;
begin
  rf := TResultsForm.CreateWithButton(2, self);
  rf.ShowModal;
  rf.Free;
end;

Retrieving data from forms

Most real-world applications consist of several forms. Often, information needs to be passed between these forms. Information can be passed to a form by means of parameters to the receiving form's constructor, or by assigning values to the form's properties. The way you get information from a form depends on whether the form is modal or modeless.

Retrieving data from modeless forms

You can easily extract information from modeless forms by calling public member functions of the form or by querying properties of the form. For example, assume an application contains a modeless form called ColorForm that contains a listbox called ColorListBox with a list of colors ("Red", "Green", "Blue", and so on). The selected color name string in ColorListBox is automatically stored in a property called CurrentColor each time a user selects a new color. The class declaration for the form is as follows:

TColorForm = class(TForm)
  ColorListBox:TListBox;
  procedure ColorListBoxClick(Sender: TObject);
private
  FColor:String;
public
  property CurColor:String read FColor write FColor;
end;

The OnClick event handler for the listbox, ColorListBoxClick, sets the value of the CurrentColor property each time a new item in the listbox is selected. The event handler gets the string from the listbox containing the color name and assigns it to CurrentColor. The CurrentColor property uses the setter function, SetColor, to store the actual value for the property in the private data member FColor:

procedure TColorForm.ColorListBoxClick(Sender: TObject);
var
  Index: Integer;
begin
  Index := ColorListBox.ItemIndex;
  if Index >= 0 then
    CurrentColor := ColorListBox.Items[Index]
  else
    CurrentColor := '';
end;

Now suppose that another form within the application, called ResultsForm, needs to find out which color is currently selected on ColorForm whenever a button (called UpdateButton) on ResultsForm is clicked. The OnClick event handler for UpdateButton might look like this:

procedure TResultForm.UpdateButtonClick(Sender: TObject);
var
  MainColor: String;
begin
  if Assigned(ColorForm) then
  begin
    MainColor := ColorForm.CurrentColor;
    {do something with the string MainColor}
  end;
end;

The event handler first verifies that ColorForm exists using the Assigned function. It then gets the value of ColorForm's CurrentColor property.

Alternatively, if ColorForm had a public function named GetColor, another form could get the current color without using the CurrentColor property (for example, MainColor := ColorForm.GetColor;). In fact, there's nothing to prevent another form from getting the ColorForm's currently selected color by checking the listbox selection directly:

with ColorForm.ColorListBox do
  MainColor := Items[ItemIndex];

However, using a property makes the interface to ColorForm very straightforward and simple. All a form needs to know about ColorForm is to check the value of CurrentColor.

Retrieving data from modal forms

Just like modeless forms, modal forms often contain information needed by other forms. The most common example is form A launches modal form B. When form B is closed, form A needs to know what the user did with form B to decide how to proceed with the processing of form A. If form B is still in memory, it can be queried through properties or member functions just as in the modeless forms example above. But how do you handle situations where form B is deleted from memory upon closing? Since a form does not have an explicit return value, you must preserve important information from the form before it is destroyed.

To illustrate, consider a modified version of the ColorForm form that is designed to be a modal form. The class declaration is as follows:

TColorForm = class(TForm)
  ColorListBox:TListBox;
  SelectButton: TButton;
  CancelButton: TButton;
  procedure CancelButtonClick(Sender: TObject);
  procedure SelectButtonClick(Sender: TObject);
private
  FColor: Pointer;
public
  constructor CreateWithColor(Value: Pointer; Owner: TComponent);
end;

The form has a listbox called ColorListBox with a list of names of colors. When pressed, the button called SelectButton makes note of the currently selected color name in ColorListBox then closes the form. CancelButton is a button that simply closes the form.

Note that a user-defined constructor was added to the class that takes a Pointer argument. Presumably, this Pointer points to a string that the form launching ColorForm knows about. The implementation of this constructor is as follows:

constructor TColorForm(Value: Pointer; Owner: TComponent);
begin
  FColor := Value;
  String(FColor^) := '';
end;

The constructor saves the pointer to a private data member FColor and initializes the string to an empty string.

Note: To use the above user-defined constructor, the form must be explicitly created. It cannot be auto-created when the application is started. For details, see "Controlling when forms reside in memory".

In the application, the user selects a color from the listbox and presses SelectButton to save the choice and close the form. The OnClick event handler for SelectButton might look like this:

procedure TColorForm.SelectButtonClick(Sender: TObject);
begin
  with ColorListBox do
    if ItemIndex >= 0 then
      String(FColor^) := ColorListBox.Items[ItemIndex];
  end;
  Close;
end;

Notice that the event handler stores the selected color name in the string referenced by the pointer that was passed to the constructor.

To use ColorForm effectively, the calling form must pass the constructor a pointer to an existing string. For example, assume ColorForm was instantiated by a form called ResultsForm in response to a button called UpdateButton on ResultsForm being clicked.

The event handler would look as follows:

procedure TResultsForm.UpdateButtonClick(Sender: TObject);
var
  MainColor: String;
begin
  GetColor(Addr(MainColor));
  if MainColor <> '' then
    {do something with the MainColor string}
  else
    {do something else because no color was picked}
end;

procedure GetColor(PColor: Pointer);
begin
  ColorForm := TColorForm.CreateWithColor(PColor, Self);
  ColorForm.ShowModal;
  ColorForm.Free;
end;

UpdateButtonClick creates a String called MainColor. The address of MainColor is passed to the GetColor function which creates ColorForm, passing the pointer to MainColor as an argument to the constructor. As soon as ColorForm is closed it is deleted, but the color name that was selected is still preserved in MainColor, assuming that a color was selected. Otherwise, MainColor contains an empty string which is a clear indication that the user exited ColorForm without selecting a color.

This example uses one string variable to hold information from the modal form. Of course, more complex objects can be used depending on the need. Keep in mind that you should always provide a way to let the calling form know if the modal form was closed without making any changes or selections (such as having MainColor default to an empty string).

Reusing components and groups of components

Delphi offers several ways to save and reuse work you've done with VCL components:

Creating and using component templates

You can create templates that are made up of one or more components. After arranging components on a form, setting their properties, and writing code for them, save them as a component template. Later, by selecting the template from the Component palette, you can place the preconfigured components on a form in a single step; all associated properties and event-handling code are added to your project at the same time.

Once you place a template on a form, you can reposition the components independently, reset their properties, and create or modify event handlers for them just as if you had placed each component in a separate operation.

To create a component template,

  1. Place and arrange components on a form. In the Object Inspector, set their properties and events as desired.
  2. Select the components. The easiest way to select several components is to drag the mouse over all of them. Gray handles appear at the corners of each selected component.
  3. Choose Component|Create Component Template.
  4. Specify a name for the component template in the Component Name edit box. The default proposal is the component type of the first component selected in step 2 followed by the word "Template". For example, if you select a label and then an edit box, the proposed name will be "TLabelTemplate". You can change this name, but be careful not to duplicate existing component names.
  5. In the Palette Page edit box, specify the Component palette page where you want the template to reside. If you specify a page that does not exist, a new page is created when you save the template.
  6. Under Palette Icon, select a bitmap to represent the template on the palette. The default proposal will be the bitmap used by the component type of the first component selected in step 2. To browse for other bitmaps, click Change. The bitmap you choose must be no larger than 24 pixels by 24 pixels.
  7. Click OK.

To remove templates from the Component palette, choose Component|Configure Palette.

Working with frames

A frame (TFrame), like a form, is a container for other components. It uses the same ownership mechanism as forms for automatic instantiation and destruction of the components on it, and the same parent-child relationships for synchronization of component properties. In some ways, however, a frame is more like a customized component than a form. Frames can be saved on the Component palette for easy reuse, and they can be nested within forms, other frames, or other container objects. After a frame is created and saved, it continues to function as a unit and to inherit changes from the components (including other frames) it contains. When a frame is embedded in another frame or form, it continues to inherit changes made to the frame from which it derives.

Creating frames

To create an empty frame, choose File|New Frame, or choose File|New and double-click on Frame. You can now drop components (including other frames) onto your new frame.

It is usually best--though not necessary--to save frames as part of a project. If you want to create a project that contains only frames and no forms, choose File|New Application, close the new form and unit without saving them, then choose File|New Frame and save the project.

Note: When you save frames, avoid using the default names Unit1, Project1, and so forth, since these are likely to cause conflicts when you try to use the frames later.

At design time, you can display any frame included in the current project by choosing View|Forms and selecting a frame. As with forms and data modules, you can toggle between the Form Designer and the frame's .DFM file by right-clicking and choosing View as Form or View as Text.

Adding frames to the Component palette

Frames are added to the Component palette as component templates. To add a frame to the Component palette, open the frame in the Form Designer (you cannot use a frame embedded in another component for this purpose), right-click on the frame, and choose Add to Palette. When the Component Template Information dialog opens, select a name, palette page, and icon for the new template.

Using and modifying frames

To use a frame in an application, you must place it, directly or indirectly, on a form. You can add frames directly to forms, to other frames, or to other container objects such as panels and scroll boxes.

The Form Designer provides two ways to add a frame to an application:

When you drop a frame onto a form or other container, Delphi declares a new class that descends from the frame you selected. (Similarly, when you add a new form to a project, Delphi declares a new class that descends from TForm.) This means that changes made later to the original (ancestor) frame propagate to the embedded frame, but changes to the embedded frame do not propagate backward to the ancestor.

Suppose, for example, that you wanted to assemble a group of data-access components and data-aware controls for repeated use, perhaps in more than one application. One way to accomplish this would be to collect the components into a component template; but if you started to use the template and later changed your mind about the arrangement of the controls, you would have to go back and manually alter each project where the template was placed. If, on the other hand, you put your database components into a frame, later changes would need to be made in only one place; changes to an original frame automatically propagate to its embedded descendants when your projects are recompiled. At the same time, you are free to modify any embedded frame without affecting the original frame or other embedded descendants of it.

Figure 5.1   A Frame with data-aware controls and a data source component

In addition to simplifying maintenance, frames can help you to use resources more efficiently. For example, to use a bitmap or other graphic in an application, you might load the graphic into the Picture property of a TImage control. If, however, you use the same graphic repeatedly in one application, each Image object you place on a form will result in another copy of the graphic being added to the form's resource file. (This is true even if you set TImage.Picture once and save the Image control as a component template.) A better solution is to drop the Image object onto a frame, load your graphic into it, then use the frame where you want the graphic to appear. This results in smaller form files and has the added advantage of letting you change the graphic everywhere it occurs simply by modifying the Image on the original frame.

Sharing frames

You can share a frame with other developers in two ways:

To add a frame to the Repository, open any project that includes the frame, right-click in the Form Designer, and choose Add to Repository. For more information, see "Using the Object Repository".

If you send a frame's unit and form files to other developers, they can open them and add them to the Component palette. If the frame has other frames embedded in it, they will have to open it as part of a project.

Creating and managing menus

Menus provide an easy way for your users to execute logically grouped commands. The Menu Designer enables you to easily add a menu--either predesigned or custom tailored--to your form. You simply add a menu component to the form, open the Menu Designer, and type menu items directly into the Menu Designer window. You can add or delete menu items, or drag and drop them to rearrange them during design time.

You don't even need to run your program to see the results--your design is immediately visible in the form, appearing just as it will during runtime. Your code can also change menus at runtime, to provide more information or options to the user.

This chapter explains how to use the Menu Designer to design menu bars and pop-up (local) menus. It discusses the following ways to work with menus at design time and runtime:

Figure 5.2   Menu terminology

For information about hooking up menu items to the code that executes when they are selected, see "Associating menu events with event handlers".

Opening the Menu Designer

To start using the Menu Designer, first add either a MainMenu or PopupMenu component to your form. Both menu components are located on the Standard page of the Component palette.

Figure 5.3    MainMenu and PopupMenu components

A MainMenu component creates a menu that's attached to the form's title bar. A PopupMenu component creates a menu that appears when the user right-clicks in the form. Pop-up menus do not have a menu bar.

To open the Menu Designer, select a menu component on the form, and then choose from one of the following methods:

Figure 5.4   Menu Designer for a main menu

Figure 5.5   Menu Designer for a pop-up menu

Building menus

You add a menu component to your form, or forms, for every menu you want to include in your application. You can build each menu structure entirely from scratch, or you can start from one of the predesigned menu templates.

This section discusses the basics of creating a menu at design time. For more information about menu templates, see "Using menu templates".

Naming menus

As with all components, when you add a menu component to the form, Delphi gives it a default name; for example, MainMenu1. You can give the menu a more meaningful name that follows Object Pascal naming conventions.

Delphi adds the menu name to the form's type declaration, and the menu name then appears in the Component list.

Naming the menu items

In contrast to the menu component itself, you need to explicitly name menu items as you add them to the form. You can do this in one of two ways:

As with the menu component, Delphi adds any menu item names to the form's type declaration, and those names then appear in the Component list.

Adding, inserting, and deleting menu items

The following procedures describe how to perform the basic tasks involved in building your menu structure. Each procedure assumes you have the Menu Designer window open.

To add menu items at design time,

  1. Select the position where you want to create the menu item.

    If you've just opened the Menu Designer, the first position on the menu bar is already selected.

  2. Begin typing to enter the caption. Or enter the Name property first by specifically placing your cursor in the Object Inspector and entering a value. In this case, you then need to reselect the Caption property and enter a value.
  3. Press Enter.

    The next placeholder for a menu item is selected.

    If you entered the Caption property first, use the arrow keys to return to the menu item you just entered. You'll see that Delphi has filled in the Name property based on the value you entered for the caption. (See "Naming the menu items".)

  4. Continue entering values for the Name and Caption properties for each new item you want to create, or press Esc to return to the menu bar.

    Use the arrow keys to move from the menu bar into the menu, and to then move between items in the list; press Enter to complete an action. To return to the menu bar, press Esc.

To insert a new, blank menu item,

  1. Place the cursor on a menu item.
  2. Press Ins.

    Menu items are inserted to the left of the selected item on the menu bar, and above the selected item in the menu list.

To delete a menu item or command,

  1. Place the cursor on the menu item you want to delete.
  2. Press Del.

Note: You cannot delete the default placeholder that appears below the item last entered in a menu list, or next to the last item on the menu bar. This placeholder does not appear in your menu at runtime.

Adding separator bars

Separator bars insert a line between menu items. You can use separator bars to indicate groupings within the menu list, or simply to provide a visual break in a list.

To make the menu item a separator bar, type a hyphen (-) for the caption.

Specifying accelerator keys and keyboard shortcuts

Accelerator keys enable the user to access a menu command from the keyboard by pressing Alt+ the appropriate letter, indicated in your code by the preceding ampersand. The letter after the ampersand appears underlined in the menu.

Delphi automatically checks for duplicate accelerators and adjusts them at runtime. This ensures that menus built dynamically at runtime contain no duplicate accelerators and that all menu items have an accelerator. You can turn off this automatic checking by setting the AutoHotkeys property of a menu item to maManual.

To specify an accelerator,

Keyboard shortcuts enable the user to perform the action without accessing the menu directly, by typing in the shortcut key combination.

To specify a keyboard shortcut,

When you add a shortcut, it appears next to the menu item caption.

Caution: Keyboard shortcuts, unlike accelerator keys, are not checked automatically for duplicates. You must ensure uniqueness yourself.

Creating submenus

Many application menus contain drop-down lists that appear next to a menu item to provide additional, related commands. Such lists are indicated by an arrow to the right of the menu item. Delphi supports as many levels of such submenus as you want to build into your menu.

Organizing your menu structure this way can save vertical screen space. However, for optimal design purposes you probably want to use no more than two or three menu levels in your interface design. (For pop-up menus, you might want to use only one submenu, if any.)

Figure 5.6   Nested menu structures

To create a submenu,

  1. Select the menu item under which you want to create a submenu.
  2. Press Ctrl + Right (right arrow) to create the first placeholder, or right-click and choose Create Submenu.
  3. Type a name for the submenu item, or drag an existing menu item into this placeholder.
  4. Press Enter, or Down (down arrow), to create the next placeholder.
  5. Repeat steps 3 and 4 for each item you want to create in the submenu.
  6. Press Esc to return to the previous menu level.

Creating submenus by demoting existing menus

You can create a submenu by inserting a menu item from the menu bar (or a menu template) between menu items in a list. When you move a menu into an existing menu structure, all its associated items move with it, creating a fully intact submenu. This pertains to submenus as well--moving a menu item into an existing submenu just creates one more level of nesting.

Moving menu items

During design time, you can move menu items simply by dragging and dropping. You can move menu items along the menu bar, or to a different place in the menu list, or into a different menu entirely.

The only exception to this is hierarchical: you cannot demote a menu item from the menu bar into its own menu; nor can you move a menu item into its own submenu. However, you can move any item into a different menu, no matter what its original position is.

While you are dragging, the cursor changes shape to indicate whether you can release the menu item at the new location. When you move a menu item, any items beneath it move as well.

To move a menu item along the menu bar,

  1. Drag the menu item along the menu bar until the arrow tip of the drag cursor points to the new location.
  2. Release the mouse button to drop the menu item at the new location.

To move a menu item into a menu list,

  1. Drag the menu item along the menu bar until the arrow tip of the drag cursor points to the new menu.

    This causes the menu to open, enabling you to drag the item to its new location.

  2. Drag the menu item into the list, releasing the mouse button to drop the menu item at the new location.

Adding images to menu items

Images can help users navigate in menus by matching glyphs and images to menu item action, similar to toolbar images. To add an image to a menu item:

  1. Drop a TMainMenu or TPopupMenu object on a form.
  2. Drop a TImageList object on the form.
  3. Open the ImageList editor by double clicking on the TImageList object.
  4. Click Add to select the bitmap or bitmap group you want to use in the menu. Click OK.
  5. Set the TMainMenu or TPopupMenu object's Images property to the ImageList you just created.
  6. Create your menu items and submenu items as described above.
  7. Select the menu item you want to have an image in the Object Inspector and set the ImageIndex property to the corresponding number of the image in the ImageList (the default value for ImageIndex is -1, which doesn't display an image).

Note: Use images that are 16 by 16 pixels for proper display in the menu. Although you can use other sizes for the menu images, alignment and consistency problems may result when using images greater than or smaller than 16 by 16 pixels.

Viewing the menu

You can view your menu in the form at design time without first running your program code. (Pop-up menu components are visible in the form at design time, but the pop-up menus themselves are not. Use the Menu Designer to view a pop-up menu at design time.)

To view the menu,

  1. If the form is visible, click the form, or from the View menu, choose the form whose menu you want to view.
  2. If the form has more than one menu, select the menu you want to view from the form's Menu property drop-down list.

    The menu appears in the form exactly as it will when you run the program.

Editing menu items in the Object Inspector

This section has discussed how to set several properties for menu items--for example, the Name and Caption properties--by using the Menu Designer.

The section has also described how to set menu item properties, such as the ShortCut property, directly in the Object Inspector, just as you would for any component selected in the form.

When you edit a menu item by using the Menu Designer, its properties are still displayed in the Object Inspector. You can switch focus to the Object Inspector and continue editing the menu item properties there. Or you can select the menu item from the Component list in the Object Inspector and edit its properties without ever opening the Menu Designer.

To close the Menu Designer window and continue editing menu items,

  1. Switch focus from the Menu Designer window to the Object Inspector by clicking the properties page of the Object Inspector.
  2. Close the Menu Designer as you normally would.

    The focus remains in the Object Inspector, where you can continue editing properties for the selected menu item. To edit another menu item, select it from the Component list.

For information about assigning event handlers to menus, see "Associating menu events with event handlers".

Using the Menu Designer context menu

The Menu Designer context menu provides quick access to the most common Menu Designer commands, and to the menu template options. (For more information about menu templates, refer to "Using menu templates".)

To display the context menu, right-click the Menu Designer window, or press Alt+F10 when the cursor is in the Menu Designer window.

Commands on the context menu

The following table summarizes the commands on the Menu Designer context menu.

Table 5.2   Menu Designer context menu commands 

Menu command

Action

Insert

Inserts a placeholder above or to the left of the cursor.

Delete

Deletes the selected menu item (and all its sub-items, if any).

Create Submenu

Creates a placeholder at a nested level and adds an arrow to the right of the selected menu item.

Select Menu

Opens a list of menus in the current form. Double-clicking a menu name opens the designer window for the menu.

Save As Template

Opens the Save Template dialog box, where you can save a menu for future reuse.

Insert From Template

Opens the Insert Template dialog box, where you can select a template to reuse.

Delete Templates

Opens the Delete Templates dialog box, where you can choose to delete any existing templates.

Insert From Resource

Opens the Insert Menu from Resource file dialog box, where you can choose an .MNU file to open in the current form.

Switching between menus at design time

If you're designing several menus for your form, you can use the Menu Designer context menu or the Object Inspector to easily select and move among them.

To use the context menu to switch between menus in a form,

  1. Right-click in the Menu Designer and choose Select Menu.

    The Select Menu dialog box appears.

    Figure 5.7   Select Menu dialog box

    This dialog box lists all the menus associated with the form whose menu is currently open in the Menu Designer.

  2. From the list in the Select Menu dialog box, choose the menu you want to view or edit.

To use the Object Inspector to switch between menus in a form,

  1. Give focus to the form whose menus you want to choose from.
  2. From the Component list, select the menu you want to edit.
  3. On the Properties page of the Object Inspector, select the Items property for this menu, and then either click the ellipsis button, or double-click [Menu].

Using menu templates

Delphi provides several predesigned menus, or menu templates, that contain frequently used commands. You can use these menus in your applications without modifying them (except to write code), or you can use them as a starting point, customizing them as you would a menu you originally designed yourself. Menu templates do not contain any event handler code.

The menu templates shipped with Delphi are stored in the BIN subdirectory in a default installation. These files have a .DMT (Delphi menu template) extension.

You can also save as a template any menu that you design using the Menu Designer. After saving a menu as a template, you can use it as you would any predesigned menu. If you decide you no longer want a particular menu template, you can delete it from the list.

To add a menu template to your application,

  1. Right-click the Menu Designer and choose Insert From Template.

    (If there are no templates, the Insert From Template option appears dimmed in the context menu.)

    The Insert Template dialog box opens, displaying a list of available menu templates.

    Figure 5.8   Sample Insert Template dialog box for menus

  2. Select the menu template you want to insert, then press Enter or choose OK.

    This inserts the menu into your form at the cursor's location. For example, if your cursor is on a menu item in a list, the menu template is inserted above the selected item. If your cursor is on the menu bar, the menu template is inserted to the left of the cursor.

To delete a menu template,

  1. Right-click the Menu Designer and choose Delete Templates.

    (If there are no templates, the Delete Templates option appears dimmed in the context menu.)

    The Delete Templates dialog box opens, displaying a list of available templates.

  2. Select the menu template you want to delete, and press Del.

    Delphi deletes the template from the templates list and from your hard disk.

Saving a menu as a template

Any menu you design can be saved as a template so you can use it again. You can use menu templates to provide a consistent look to your applications, or use them as a starting point which you then further customize.

The menu templates you save are stored in your BIN subdirectory as .DMT files.

To save a menu as a template,

  1. Design the menu you want to be able to reuse.

    This menu can contain as many items, commands, and submenus as you like; everything in the active Menu Designer window will be saved as one reusable menu.

  2. Right-click in the Menu Designer and choose Save As Template.

    The Save Template dialog box appears.

    Figure 5.9   Save Template dialog box for menus

  3. In the Template Description edit box, type a brief description for this menu, and then choose OK.

    The Save Template dialog box closes, saving your menu design and returning you to the Menu Designer window.

Note: The description you enter is displayed only in the Save Template, Insert Template, and Delete Templates dialog boxes. It is not related to the Name or Caption property for the menu.

Naming conventions for template menu items and event handlers

When you save a menu as a template, Delphi does not save its Name property, since every menu must have a unique name within the scope of its owner (the form). However, when you insert the menu as a template into a new form by using the Menu Designer, Delphi then generates new names for it and all of its items.

For example, suppose you save a File menu as a template. In the original menu, you name it MyFile. If you insert it as a template into a new menu, Delphi names it File1. If you insert it into a menu with an existing menu item named File1, Delphi names it File2.

Delphi also does not save any OnClick event handlers associated with a menu saved as a template, since there is no way to test whether the code would be applicable in the new form. When you generate a new event handler for the menu template item, Delphi still generates the event handler name.

You can easily associate items in the menu template with existing OnClick event handlers in the form. For more information, see "Associating an event with an existing event handler".

Manipulating menu items at runtime

Sometimes you want to add menu items to an existing menu structure while the application is running, to provide more information or options to the user. You can insert a menu item by using the menu item's Add or Insert method, or you can alternately hide and show the items in a menu by changing their Visible property. The Visible property determines whether the menu item is displayed in the menu. To dim a menu item without hiding it, use the Enabled property.

For examples that use the menu item's Visible and Enabled properties, see "Disabling menu items".

In multiple document interface (MDI) and Object Linking and Embedding (OLE) applications, you can also merge menu items into an existing menu bar.The following section discusses this in more detail.

Merging menus

For MDI applications, such as the text editor sample application, and for OLE client applications, your application's main menu needs to be able to receive menu items either from another form or from the OLE server object. This is often called merging menus.

You prepare menus for merging by specifying values for two properties:

Specifying the active menu: Menu property

The Menu property specifies the active menu for the form. Menu-merging operations apply only to the active menu. If the form contains more than one menu component, you can change the active menu at runtime by setting the Menu property in code. For example,

Form1.Menu := SecondMenu;

Determining the order of merged menu items: GroupIndex property

The GroupIndex property determines the order in which the merging menu items appear in the shared menu bar. Merging menu items can replace those on the main menu bar, or can be inserted.

The default value for GroupIndex is 0. Several rules apply when specifying a value for GroupIndex:

Importing resource files

Delphi supports menus built with other applications, so long as they are in the standard Windows resource (.RC) file format. You can import such menus directly into your Delphi project, saving you the time and effort of rebuilding menus that you created elsewhere.

To load existing .RC menu files,

  1. In the Menu Designer, place your cursor where you want the menu to appear.

    The imported menu can be part of a menu you are designing, or an entire menu in itself.

  2. Right-click and choose Insert From Resource.

    The Insert Menu From Resource dialog box appears.

  3. In the dialog box, select the resource file you want to load, and choose OK.

    The menu appears in the Menu Designer window.

Note : If your resource file contains more than one menu, you first need to save each menu as a separate resource file before importing it.

Designing toolbars and cool bars

A toolbar is a panel, usually across the top of a form (under the menu bar), that holds buttons and other controls. A cool bar (also called a rebar) is a kind of toolbar that displays controls on movable, resizable bands. If you have multiple panels aligned to the top of the form, they stack vertically in the order added.

You can put controls of any sort on a toolbar. In addition to buttons, you may want to put use color grids, scroll bars, labels, and so on.

There are several ways to add a toolbar to a form:

How you implement your toolbar depends on your application. The advantage of using the Panel component is that you have total control over the look and feel of the toolbar.

By using the toolbar and cool bar components, you are ensuring that your application has the look and feel of a Windows application because you are using the native Windows controls. If these operating system controls change in the future, your application could change as well. Also, since the toolbar and cool bar rely on common components in Windows, your application requires the COMCTL32.DLL. Toolbars and cool bars are not supported in WinNT 3.51 applications.

The following sections describe how to

Adding a toolbar using a panel component

To add a toolbar to a form using the panel component,

  1. Add a panel component to the form (from the Standard page of the Component palette).
  2. Set the panel's Align property to alTop. When aligned to the top of the form, the panel maintains its height, but matches its width to the full width of the form's client area, even if the window changes size.
  3. Add speed buttons or other controls to the panel.

Speed buttons are designed to work on toolbar panels. A speed button usually has no caption, only a small graphic (called a glyph), which represents the button's function.

Speed buttons have three possible modes of operation. They can

To implement speed buttons on toolbars, do the following:

Adding a speed button to a panel

To add a speed button to a toolbar panel, place the speed button component (from the Additional page of the Component palette) on the panel.

The panel, rather than the form, "owns" the speed button, so moving or hiding the panel also moves or hides the speed button.

The default height of the panel is 41, and the default height of speed buttons is 25. If you set the Top property of each button to 8, they'll be vertically centered. The default grid setting snaps the speed button to that vertical position for you.

Assigning a speed button's glyph

Each speed button needs a graphic image called a glyph to indicate to the user what the button does. If you supply the speed button only one image, the button manipulates that image to indicate whether the button is pressed, unpressed, selected, or disabled. You can also supply separate, specific images for each state if you prefer.

You normally assign glyphs to speed buttons at design time, although you can assign different glyphs at runtime.

To assign a glyph to a speed button at design time,

  1. Select the speed button.
  2. In the Object Inspector, select the Glyph property.
  3. Double-click the Value column beside Glyph to open the Picture Editor and select the desired bitmap.

Setting the initial condition of a speed button

Speed buttons use their appearance to give the user clues as to their state and purpose. Because they have no caption, it's important that you use the right visual cues to assist users.

Table 5.3 lists some actions you can set to change a speed button's appearance:

Table 5.3   Setting speed buttons' appearance

To make a speed button:

Set the toolbar's:

Appear pressed

GroupIndex property to a value other than zero and its Down property to True.

Appear disabled

Enabled property to False.

Have a left margin

Indent property to a value greater than 0.

If your application has a default drawing tool, ensure that its button on the toolbar is pressed when the application starts. To do so, set its GroupIndex property to a value other than zero and its Down property to True.

Creating a group of speed buttons

A series of speed buttons often represents a set of mutually exclusive choices. In that case, you need to associate the buttons into a group, so that clicking any button in the group causes the others in the group to pop up.

To associate any number of speed buttons into a group, assign the same number to each speed button's GroupIndex property.

The easiest way to do this is to select all the buttons you want in the group, and, with the whole group selected, set GroupIndex to a unique value.

Allowing toggle buttons

Sometimes you want to be able to click a button in a group that's already pressed and have it pop up, leaving no button in the group pressed. Such a button is called a toggle. Use AllowAllUp to create a grouped button that acts as a toggle: click it once, it's down; click it again, it pops up.

To make a grouped speed button a toggle, set its AllowAllUp property to True.

Setting AllowAllUp to True for any speed button in a group automatically sets the same property value for all buttons in the group. This enables the group to act as a normal group, with only one button pressed at a time, but also allows every button to be up at the same time.

Adding a toolbar using the toolbar component

The toolbar component (TToolBar) offers button management and display features that panel components do not. To add a toolbar to a form using the toolbar component,

  1. Add a toolbar component to the form (from the Win32 page of the Component palette). The toolbar automatically aligns to the top of the form.
  2. Add tool buttons or other controls to the bar.

Tool buttons are designed to work on toolbar components. Like speed buttons, tool buttons can

To implement tool buttons on a toolbar, do the following:

Adding a tool button

To add a tool button to a toolbar, right-click on the toolbar and choose New Button.

The toolbar "owns" the tool button, so moving or hiding the toolbar also moves or hides the button. In addition, all tool buttons on the toolbar automatically maintain the same height and width. You can drop other controls from the Component palette onto the toolbar, and they will automatically maintain a uniform height. Controls will also wrap around and start a new row when they do not fit horizontally on the toolbar.

Assigning images to tool buttons

Each tool button has an ImageIndex property that determines what image appears on it at runtime. If you supply the tool button only one image, the button manipulates that image to indicate whether the button is disabled. To assign images to tool buttons at design time,

  1. Select the toolbar on which the buttons appear.
  2. In the Object Inspector, assign a TImageList object to the toolbar's Images property. An image list is a collection of same-sized icons or bitmaps.
  3. Select a tool button.
  4. In the Object Inspector, assign an integer to the tool button's ImageIndex property that corresponds to the image in the image list that you want to assign to the button.

You can also specify separate images to appear on the tool buttons when they are disabled and when they are under the mouse pointer. To do so, assign separate image lists to the toolbar's DisabledImages and HotImages properties.

Setting tool button appearance and initial conditions

Table 5.4 lists some actions you can set to change a tool button's appearance:

Table 5.4   Setting tool buttons' appearance

To make a tool button:

Set the toolbar's:

Appear pressed

GroupIndex property to a nonzero value and its Down property to True.

Appear disabled

Enabled property to False.

Have a left margin

Indent property to a value greater than 0.

Appear to have "pop-up" borders, thus making the toolbar appear transparent

Flat property to True.

Note: Using the Flat property of TToolBar requires version 4.70 or later of COMCTL32.DLL.

To force a new row of controls after a specific tool button, Select the tool button that you want to appear last in the row and set its Wrap property to True.

To turn off the auto-wrap feature of the toolbar, set the toolbar's Wrapable property to False.

Creating groups of tool buttons

To create a group of tool buttons, select the buttons you want to associate and set their Style property to tbsCheck; then set their Grouped property to True. Selecting a grouped tool button causes other buttons in the group to pop up, which is helpful to represent a set of mutually exclusive choices.

Any unbroken sequence of adjacent tool buttons with Style set to tbsCheck and Grouped set to True forms a single group. To break up a group of tool buttons, separate the buttons with any of the following:

Allowing toggled tool buttons

Use AllowAllUp to create a grouped tool button that acts as a toggle: click it once, it is down; click it again, it pops up. To make a grouped tool button a toggle, set its AllowAllUp property to True.

As with speed buttons, setting AllowAllUp to True for any tool button in a group automatically sets the same property value for all buttons in the group.

Adding a cool bar component

The cool bar component--also called a rebar--displays windowed controls on independently movable, resizable bands. The user can position the bands by dragging the resizing grips on the left side of each band.

To add a cool bar to a form,

  1. Add a cool bar component to the form (from the Win32 page of the Component palette). The cool bar automatically aligns to the top of the form.
  2. Add windowed controls from the Component palette to the bar.

Only components that descend from TWinControl are windowed controls. You can add graphic controls--such as labels or speed buttons--to the cool bar, but they will not appear on separate bands.

Note: The cool bar component requires version 4.70 or later of COMCTL.DLL.

Setting the appearance of the cool bar

The cool bar component offers several useful configuration options. Table 5.5 lists some actions you can set to change a tool button's appearance:

Table 5.5   Setting a cool button's appearance

To make the cool bar:

Set the toolbar's:

Resize automatically to accommodate the bands it contains

AutoSize property to True.

Bands maintain a uniform height

FixedSize property to True.

Reorient to vertical rather than horizontal

Vertical property to True. This changes the effect of the FixedSize property.

Prevent the Text properties of the bands from displaying at runtime

ShowText property to False. Each band in a cool bar has its own Text property.

Remove the border around the bar

BandBorderStyle to bsNone.

Keep users from changing the bands' order at runtime. (The user can still move and resize the bands.)

FixedOrder to True.

Create a background image for the cool bar

Bitmap property to TBitmap object.

Choose a list of images to appear on the left of any band

Images property to TImageList object.

To assign images to individual bands, select the cool bar and double-click on the Bands property in the Object Inspector. Then select a band and assign a value to its ImageIndex property.

Responding to clicks

When the user clicks a control, such as a button on a toolbar, the application generates an OnClick event which you can respond to with an event handler. Since OnClick is the default event for buttons, you can generate a skeleton handler for the event by double-clicking the button at design time. For more information, see "Working with events and event handlers" and "Generating a handler for a component's default event".

Assigning a menu to a tool button

If you are using a toolbar (TToolBar) with tool buttons (TToolButton), you can associate menu with a specific button:

  1. Select the tool button.
  2. In the Object Inspector, assign a pop-up menu (TPopupMenu) to the tool button's DropDownMenu property.

If the menu's AutoPopup property is set to True, it will appear automatically when the button is pressed.

Adding hidden toolbars

Toolbars do not have to be visible all the time. In fact, it is often convenient to have a number of toolbars available, but show them only when the user wants to use them. Often you create a form that has several toolbars, but hide some or all of them.

To create a hidden toolbar,

  1. Add a toolbar, cool bar, or panel component to the form.
  2. Set the component's Visible property to False.

Although the toolbar remains visible at design time so you can modify it, it remains hidden at runtime until the application specifically makes it visible.

Hiding and showing toolbars

Often, you want an application to have multiple toolbars, but you do not want to clutter the form with them all at once. Or you may want to let users decide whether to display toolbars. As with all components, toolbars can be shown or hidden at runtime as needed.

To hide or show a toolbar at runtime, set its Visible property to False or True, respectively. Usually you do this in response to particular user events or changes in the operating mode of the application. To do this, you typically have a close button on each toolbar. When the user clicks that button, the application hides the corresponding toolbar.

You can also provide a means of toggling the toolbar. In the following example, a toolbar of pens is toggled from a button on the main toolbar. Since each click presses or releases the button, an OnClick event handler can show or hide the Pen toolbar depending on whether the button is up or down.

procedure TForm1.PenButtonClick(Sender: TObject);
begin
  PenBar.Visible := PenButton.Down;
end;

Using action lists

Action lists let you centralize the response to user commands (actions) for objects such as menus and buttons that respond to those commands. This section is an overview of actions and action lists, describing how to use them and how they interact with their clients and targets.

Action objects

Actions are user commands that operate on target objects. You create actions in the action list component editor. These actions are later connected to client controls via their action links. Following are descriptions of each type of component in the action/action list mechanism:

Figure 5.10 shows the relationship of these objects. In this diagram, Cut1 is the action, ActionList1 is the action list containing Cut1, SpeedButton1 is the client of Cut1, and Memo1 is the target. Unlike actions, action lists, action clients, and action targets, action links are non-visual objects. The action link in this diagram is therefore indicated by a white rectangle. The action link associates the SpeedButton1 client to the Cut1 action contained in ActionList1.

Figure 5.10   Action list mechanism

The VCL includes TAction, TActionList, and TActionLink type classes for working with Action lists. By unit, these are

There are also two units, StdActns and DBActns, that contain auxiliary classes that implement specific, commonly used standard Windows and data set actions. These are described in "Pre-defined action classes". Many of the VCL controls include properties (such as Action) and methods (such as ExecuteAction) that enable them to be used as action clients and targets.

Using Actions

You can add an action list to your forms or data modules from the standard page of the Component Palette. Double-click the action list to display the Action List editor, which lets you add, delete, and rearrange actions in much the same way you use the collection editor.

In the Object Inspector, set the properties for each action. The Name property identifies the action, and the other properties and events (Caption, Checked, Enabled, HelpContext, Hint, ImageIndex, ShortCut, and Visible) correspond to the properties of client controls. These are typically, but not necessarily, the same name as the client property. For example, an action's Checked property corresponds to a TToolButton's Down property.

Centralizing code

A number of controls such as TToolButton, TSpeedButton, TMenuItem, and TButton have a published property called Action. When you set the Action property to one of the actions in your action list, the values of the corresponding properties in the action are copied to those of the control. All properties and events in common with the action object (except Name and Tag) are dynamically linked to the control. Thus, for example, instead of duplicating the code that disables buttons and menu items, you can centralize this code in an action object, and when the action is disabled, all corresponding buttons and menu items are disabled.

Linking properties

The client's action link is the mechanism through which its properties are associated with (linked to) the properties of an action. When an action changes, the action link is responsible for updating the client's properties. For details about which properties a particular action link class handles, refer to the individual action link classes in the VCL reference.

You can selectively override the values of the properties controlled by an associated action object by setting the property's value in the client component or control. This does not change the property in the action, so only the client is affected.

Executing actions

When a client component or control is clicked, the OnExecute event occurs for it's associated action. For example, the following code illustrates the OnExecute event handler for an action that toggles the visibility of a toolbar when the action is executed:

procedure TForm1.Action1Execute(Sender: TObject);
begin
  { Toggle Toolbar1's visibility }
  ToolBar1.Visible := not ToolBar1.Visible;
end;

Note: If you are using a tool button or a menu item, you must manually set the Images property of the corresponding toolbar or menu component to the Images property of the action list. This is true even though the ImageIndex property is dynamically linked to the client.

For general information about events and event handlers, see "Working with events and event handlers".

Figure 5.11 illustrates the dispatching sequence for the execution cycle of an action called Cut1. This diagram assumes the relationship of the components in Figure 5.10, meaning that the Speedbutton1 client is linked to the Cut1 action via its action link. Speedbutton1's Action property is therefore Cut1. Consequently, Speedbutton1's Click method invokes Cut1's Execute method.

Figure 5.11   Execution cycle for an action

Note: In the description of this sequence, one method invoking another does not necessarily mean that the invocation is explicit in the code for that method.

Clicking on Speedbutton1 initiates the following execution cycle:

If execution is not handled, at this point, in the action list event handler, then processing continues:

If execution is not handled in the application's event handler, then Cut1 send the CM_ACTIONEXECUTE message to the application's WndProc, passing itself as a parameter. The application then tries to find a target on which to execute the action (see "Action targets").

Updating actions

When the application is idle, the OnUpdate event occurs for every action that is linked to a visible control or menu item that is showing. This provides an opportunity for applications to execute centralized code for enabling and disabling, checking and unchecking, and so on. For example, the following code illustrates the OnUpdate event handler for an action that is "checked" when the toolbar is visible:

procedure TForm1.Action1Update(Sender: TObject);
begin
  { Indicate whether ToolBar1 is currently visible }
  (Sender as TAction).Checked := ToolBar1.Visible;
end;

See also the RichEdit demo(Demos\RichEdit).

The dispatching cycle for updating actions follows the same sequence as the execution cycle in Figure 5.11.

Note: Do not add time-intensive code to the OnUpdate event handler. This executes whenever the application is idle. If the event handler takes too much time, it will adversely affect performance of the entire application.

Pre-defined action classes

Component writers can use the classes in the StdActns and DBActns units as examples for deriving their own action classes that implement behaviors specific to certain controls or components. The base classes for these specialized actions (TEditAction, TWindowAction) generally override HandlesTarget, UpdateTarget, and other methods to limit the target for the action to a specific class of objects. The descendant classes typically override ExecuteTarget to perform a specialized task.

Standard edit actions

The standard edit actions are designed to be used with an edit control target. TEditAction is the base class for descendants that each override the ExecuteTarget method to implement copy, cut, and paste tasks by using the Windows Clipboard.

Standard Window actions

The standard Window actions are designed to be used with forms as targets in an MDI application. TWindowAction is the base class for descendants that each override the ExecuteTarget method to implement arranging, cascading, closing, tiling, and minimizing MDI child forms.

DataSet actions

The standard dataset actions are designed to be used with a dataset component target. TDataSetAction is the base class for descendants that each override the ExecuteTarget and UpdateTarget methods to implement navigation and editing of the target.

The TDataSetAction introduces a DataSource property which ensures actions are performed on that dataset. If DataSource is nil, the currently focused data-aware control is used. For details, refer to "Action targets".

Writing action components

The pre-defined actions are examples of extending the VCL action classes. The following topics are useful if you are writing your own action classes:

How actions find their targets

Figure 5.11 illustrates the execution cycle for the standard VCL action classes. If execution is not handled by the action list, the application, or the default action event handlers, then the CM_ACTIONEXECUTE message is sent to the application's WndProc. Figure 5.12 continues the execution sequence at this point. The pre-defined action classes described previously as well as any action class that you create, use this path of execution:

Figure 5.12   Action targets

If the control were not an appropriate target, processing would continue as follows:

Note: If the action involved is a TCustomAction type, then the action is automatically disabled for you if, the action is not handled and, its DisableIfNoHandler property is True.

Registering actions

You can register and unregister your own actions with the IDE by using the global routines in the ActnList unit:

procedure RegisterActions(const CategoryName: string; const AClasses: array of 
TBasicActionClass; Resource: TComponentClass);

procedure UnRegisterActions(const AClasses: array of TBasicActionClass);

Use these routines the same way you would when registering components RegisterComponents). For example, the following code registers the standard actions with the IDE:

{ Standard action registration }

RegisterActions('', [TAction], nil);

RegisterActions('Edit', [TEditCut, TEditCopy, TEditPaste], TStandardActions);

RegisterActions('Window', [TWindowClose, TWindowCascade, TWindowTileHorizontal, 
TWindowTileVertical, TWindowMinimizeAll, TWindowArrange], TStandardActions);

Writing action list editors

You may want to write your own component editor for action lists. If you do, you can assign your own procedures to the four global procedure variables in the ActnList unit:

CreateActionProc: function (AOwner: TComponent; ActionClass: TBasicActionClass): 
TBasicAction = nil;

EnumRegisteredActionsProc: procedure (Proc: TEnumActionProc; Info: Pointer) = nil;

RegisterActionsProc: procedure (const CategoryName: string; const AClasses: array of 
TBasicActionClass; Resource: TComponentClass) = nil;

UnRegisterActionsProc: procedure (const AClasses: array of TBasicActionClass) = nil;

You only need to reassign these if you want to manage the registration, unregistration, creation, and enumeration procedures of actions differently from the default behavior. If you do, write your own handlers and assign them to these variables within the initialization section of your design-time unit.

Demo programs

For examples of programs that use actions and action lists, refer to Demos\RichEdit. In addition, the Application wizard (File|New Project page) demos, MDI Application, SDI Application, and Win95 Logo Application can use the action and action list objects.