Designing and registering Actions is pretty involved and I have a page that describes the basics. However, to just use a generic action to implement shortcut keys for a single application (the topic of this page) is much simpler.
Note that Actions are components, not controls, meaning that they are not displayed at run time. To provide a visual presence, they are usually attached to menus and buttons, and are called when one of those is selected. (The Action property is defined as public in TControl and published in some of the derived control.)
However, the action operates on some other control (the target) - not the menu selection or the button (the client).
Overview
In the case where several shortcuts preform the same function - such as ctrl-C and ctrl-Ins - you simply define 2 actions of the same type - one for each shortcut.
Menus items are also associated with keyboard shortcuts but, like actions, they are allowed to be directly associated with only a single shortcut. As a result, when multiple shortcuts are associated with a single menu selection, it makes sense to create 2 actions and to associate one of them with the menu.
Besides being associated with a keyboard shortcut, each action can be associated with any number of menu selections and buttons. One typical application might assign a Copy task to
Action properties
Enabled | Grey out options currently not available / meaningful |
Visible | |
Caption | Displayed in the menu or on a button |
Checked | Used with checkboxes |
Hint | What is displayed when the mouse hovers over an associated control |
HelpContext | What is displayed when F1 is pressed |
Creating a generic action
Action1: TAction; |
Name | This is displayed in the ActionList - edit as appropriate |
Category | In the ActionList, the actions are grouped by this value |
Shortcut | This is why we are creating this - set as appropriate |
Enabled | Use this to disable associated controls (menu selections and buttons) |
OnExecute | This is the code that runs when the shortcut is pressed |
OnUpdate | This is run when the application is idle, use it to control Enabled |
The other properties mainly apply with pre-defined actions - Caption and ImageIndex are used to automatically configure buttons and menu selections. Visible, Enabled and Checked are used to automatically synchronize multiple controls at runtime (via the OnUpdate event). For instance, if the clipboard contains the wrong type of data, then you might want to disable a button that pastes data.
When an action is first associated with a button, action.caption is copied to button.caption. After that, if action.caption is edited, then button.caption automatically changes. However, if button.caption is manually changed after the action is assigned, then the new text will be kept and changes to action.caption will no longer affect button.caption. Similar synchronization occurs with the other properties and events - by default, changes to the action affect all associated controls .. unless the property or event is changed after the Action property is assigned.
Copy and Paste
copy | ctrl-C and ctrl-Ins |
paste | ctrl-V and shift-Ins |
Component | ShortCut | OnExecute |
---|---|---|
ImageCopy_Action1 | ctrl-C | ImageCopy_ActionExecute |
ImageCopy_Action2 | ctrl-Ins | |
ImagePaste_Action1 | ctrl-V | ImagePaste_ActionExecute |
ImagePaste_Action2 | shift-Ins |
procedure TForm1.ImageCopy_ActionExecute(Sender: TObject); begin if not Image1.Picture.Bitmap.Empty then Clipboard.Assign(Image1.Picture.Bitmap); end; procedure TForm1.ImagePaste_ActionExecute(Sender: TObject); begin if Clipboard.HasFormat(CF_BITMAP) then Image1.Picture.Bitmap.Assign(Clipboard); end; |
procedure TForm1.ImageCopy_ActionUpdate(Sender: TObject); begin (Sender as TAction).Enabled := not Image1.Picture.Bitmap.Empty; end; procedure TForm1.ImagePaste_ActionUpdate(Sender: TObject); begin (Sender as TAction).Enabled := Clipboard.HasFormat(CF_BITMAP); end; |
Sender
function TBasicAction.Execute: Boolean; begin if Assigned(FOnExecute) then begin FOnExecute(Self); Result := True; end else Result := False; end; function TBasicAction.Update: Boolean; begin if Assigned(FOnUpdate) then begin FOnUpdate(Self); Result := True; end else Result := False; end; |
Associating Sender with the control calling an event is the standard practice for events and, normally, I would not have questioned it. However, Actions are normally associated with a menu or button and, therefore, I think it is necessary to be explicitly clear what Sender refers to.
The association is explicitly documented in the help for TBasicAction.Execute and is implied in the TBasicAction.OnUpdate example. However, it is not documented in the help for these two events.
The corollary of this is that in Delphi 5 there is no way to determine how the action was triggered - hotkey, menu, button - not that anyone should care. In Delphi 6, the TBasicAction.ActionComponent property was added so that code in TBasicAction.OnExecute can determine which control initiated the action.
Additional controls