With Delphi, you create a user interface (UI) by selecting components from the Component palette and dropping them onto forms.
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.
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,
Now if you run the application, your new main form choice is displayed.
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).
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,
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.
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.
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.
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.
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.
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".
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.
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.
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.
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:
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);
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.
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.
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.
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;
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.
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.
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).
Delphi offers several ways to save and reuse work you've done with VCL components:
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,
To remove templates from the Component palette, choose Component|Configure Palette.
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.
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.
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.
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.
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.
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.
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:
For information about hooking up menu items to the code that executes when they are selected, see "Associating menu events with event handlers".
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.
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:
The Menu Designer appears, with the first (blank) menu item highlighted in the Designer, and the Caption property highlighted in the Object Inspector.
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".
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.
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:
For example, if you give a menu item a Caption property value of File, Delphi assigns the menu item a Name property of File1. If you fill in the Name property before filling in the Caption property, Delphi leaves the Caption property blank until you type in a value.
Note: If you enter characters in the Caption property that are not valid for Object Pascal identifiers, Delphi modifies the Name property accordingly. For example, if you want the caption to start with a number, Delphi precedes the number with a character to derive the Name property.
The following table demonstrates some examples of this, assuming all menu items shown appear in the same menu bar.
Removes all non-standard characters, adding preceding letter and numerical order | ||
Numerical ordering of second occurrence of caption with no standard characters |
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.
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,
If you've just opened the Menu Designer, the first position on the menu bar is already selected.
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".)
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,
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,
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.
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.
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.
For example, to add a Save menu command with the S as an accelerator key, type &Save.
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,
This list is only a subset of the valid combinations you can type in.
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.
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.)
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.
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,
To move a menu item into a menu list,
This causes the menu to open, enabling you to drag the item to its new location.
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:
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.
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.)
The menu appears in the form exactly as it will when you run the program.
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,
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".
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.
The following table summarizes the commands on the Menu Designer context menu.
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,
The Select Menu dialog box appears.
This dialog box lists all the menus associated with the form whose menu is currently open in the Menu Designer.
To use the Object Inspector to switch between menus in a form,
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,
(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.
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.
(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.
Delphi deletes the template from the templates list and from your hard disk.
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.
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.
The Save Template dialog box appears.
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.
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".
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.
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:
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;
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:
For instance, set the GroupIndex property to 0 (zero) for a menu that you always want to appear leftmost, such as a File menu. Similarly, specify a high number (it needn't be in sequence) for a menu that you always want to appear rightmost, such as a Help menu.
This can apply to groupings or to single items. For example, if your main form has an Edit menu item with a GroupIndex value of 1, you can replace it with one or more items from the child form's menu by giving them a GroupIndex value of 1 as well.
Giving multiple items in the child menu the same GroupIndex value keeps their order intact when they merge into the main menu.
For example, number the items in the main menu 0 and 5, and insert items from the child menu by numbering them 1, 2, 3 and 4.
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,
The imported menu can be part of a menu you are designing, or an entire menu in itself.
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.
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
To add a toolbar to a form using the panel component,
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:
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.
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,
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:
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.
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.
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.
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,
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:
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.
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,
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.
Table 5.4 lists some actions you can set to change a tool button's appearance:
GroupIndex property to a nonzero value and its Down property to True. | |
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.
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:
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.
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.
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.
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:
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.
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".
If you are using a toolbar (TToolBar) with tool buttons (TToolButton), you can associate menu with a specific button:
If the menu's AutoPopup property is set to True, it will appear automatically when the button is pressed.
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.
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.
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;
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.
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.
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.
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.
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.
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.
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.
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:
procedure TForm1.ActionList1ExecuteAction(Action: TBasicAction; var Handled: Boolean);
begin
{ Prevent execution of actions contained by ActionList1 }
Handled := True;
end;
If execution is not handled, at this point, in the action list event handler, then processing continues:
procedure TForm1.ApplicationExecuteAction(Action: TBasicAction; var Handled: Boolean);
begin
{ Prevent execution of all actions in Application }
Handled := True;
end;
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").
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.
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.
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.
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.
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".
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:
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:
procedure TEditCut.ExecuteTarget(Target: TObject); begin GetControl(Target).CutToClipboard; end;
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.
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);
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.
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.
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.