Action lists were introduced back in Delphi 4. However, this highly powerful feature of Delphi is still widely underused and in many cases misused by developers. This
paper describes, in detail, just what actions and action lists can do for an application. However, the most important aspect of this
paper is a set of guidelines for effectively using actions in an application.
CD Player: Take 1
The sample application that we will be converting in this article is the CD
Player program shown in Figures 1 and 2. Well, it doesn't really play CDs, but
the two list boxes are completely functional and so are all the buttons and menu
items. This is fine because it's the list boxes that we are interested in and
the actions that can be performed on them. By the way, the display in the upper
right corner of the window is just an image.

Figure 1: The CD Player manages two list boxes of song titles.

Figure 2: The CD Player provides multiple ways to manipulate the list
boxes.
Since our CD player does not actually read CDs, we have to assume that when a
new CD is loaded the Tracks list box is automatically populated with the song
titles of that CD. For our purposes, I've simply populated the Tracks list at
design-time with the tracks from a-ha's first album.
When the player is running, the user selects the songs to be played by
manipulating the Play List. Clicking the Add button adds all selected song
titles in the Tracks list to the Play List. The Select All and Unselect All
buttons are self-explanatory. Once the Play List contains some tracks, the user
can change the order in which the tracks are played by selecting a track and
then clicking the Move Up or Move Down buttons. Of course, tracks can be removed
from the Play List by selecting the track and clicking the Delete button.
Clicking the Clear button clears the entire list. In addition, Figure 2 shows
that there are popup menus associated with each list box that provide access to
this same set of commands-oh, I mean "actions."
MainForm1.pas is the form unit for the first version of our CD player. This
first take does not use a TActionList. Instead, it is built using traditional
Delphi techniques. That is, event handlers are written for each popup menu item
to perform the desired actions. The OnClick event handlers of all the
buttons simply point to the corresponding menu item event handler. This is
straightforward Delphi coding and the ability to have multiple events handled by
the same event handler reduces the amount of code we write.
However, sharing event handlers does not address the problem of updating
command status. For example, in Figure 1, the Play List is empty and therefore
none of the Play List commands are enabled. Likewise, in Figure 2, since the
selected track in the Play List is the last track, the Move Down command is
disabled.
In traditional Delphi applications, command enabling is handled in a manner
similar to what is shown in MainForm1.pas. In this example, the UpdateStatus
method takes care of updating the command status of all the buttons and menu
items. However, there are two important issues regarding this approach. First,
the enabling code for each command must be duplicated for each control that
invokes the command. Second, the UpdateStatus method must be called at
various times throughout the program.
A New Script
Delphi 4 provides a new approach to handling command implementation and
command enablement through the use of action lists and actions. An action list
is a nonvisual component that contains a set of TAction components. An
action list provides design-time access to the individual action components,
much like a menu component provides access to a set of menu items.
A TAction component implements a command, such as deleting a selected
item, on a target, such as a list box. An action is invoked by a client control
in response to some user command, typically a mouse click. The most common
client controls are buttons and menu items. A client control is connected to a
particular action by setting the control's Action property. This results in a
link between the client control and the action component.
Figure 3 shows an example of how client controls are connected to actions.
For instance, the EditCut1 action is assigned to SpeedButton1's
Action property. When the assignment is made, several of the speedbutton's
properties are changed based on the corresponding properties of the EditCut1
action. For example, the button's Caption is automatically changed to
'Cu&t'. When the user clicks the Cut button, the command implemented by the EditCut1
action is invoked.

Figure 3: Client controls are connected to actions via action links.
CD Player: Take 2
So now, let's take a look at the steps required to convert our CD player to
use actions. First, we drop a TActionList component on the form. Next, we
need to add new actions for each command associated with the list boxes. We
accomplish this by invoking the Action List Editor and clicking the New Action
button for each command. Figure 4 shows the Action List Editor along with the
Object Inspector. As you can see, when you select an action in the editor, the
Object Inspector shows all the published properties associated with it. For each
action, set the desired properties.

Figure 4: The Action List Editor.
Next, we create and event handler for the ActionList1.OnUpdate event.
The event handler will use the same code as the UpdateStatus method from
Take 1, but instead of updating the Enabled property of both the buttons
and menu items, only the Enabled property of each action is updated. We
also need to set Handled to True at the end of this event handler (I'll
explain why later). Next, we can remove the UpdateStatus method and all
references to it.
The next step involves writing an event handler for each action's OnExecute
event. You can generate the event by double clicking the action in the Action
List Editor. In each method, simply move the code from the old menu or button OnClick
event handler to the new action OnExecute event handler.
The final step is to connect all of the client controls to the appropriate
action. For example, the BtnMoveUp.Action property is set to ActMoveUp.
It is also important to remember to set the Action property of each menu
item as well.
At this point, we can rebuild the application and run it. You will notice
that the application behaves identically to the first version, but with fewer
lines of code. In addition, the new version is also much easier to maintain. In
addition, we were even able to add some custom hint processing for the delete
action because TAction defines an OnHint event.
The Backstage Tour
As you can see, it is pretty simple to use actions. However, for a truly
Oscar winning performance you need to have a much better understanding of how
actions work. For example, if you performed the steps described in the previous
section, you probably noticed that both TActionList and TAction
define OnExecute and OnUpdate events. Unfortunately, it is not
clear from the Delphi documentation when each event should be used. So, let's go
on a backstage tour of the inner workings of the key classes that support
actions.
The Update Building
Delphi 4's Application object knows about actions and action lists, and if
your application uses any, the Application object will generate OnUpdate
events whenever the application is idle.
For each action list, the TActionList.OnUpdate event gets generated
first, and the handler for this event receives two parameters. The Action
parameter represents the action that is being updated. The Handled parameter is
used to control whether the OnUpdate event for the passed in action gets
generated. If you do not want the action specific OnUpdate event to be
generated, set Handled to True in the TActionList.OnUpdate event handler.
It is important to note that TActionList.OnUpdate is generated for
each client control connected to any action contained in the list. For example,
suppose ActionList1 contains Action1 and Action2. Now
suppose Button1's and SpeedButton1's Action properties are
set to Action1 while SpeedButton2.Action is set to Action2. Under
these circumstances, the ActionList1.OnUpdate event will be generated a
total of three times for each update cycle. The OnUpdate event handler is
called twice with the Action parameter set to Action1 and the
handler is called a third time with the Action parameter set to Action2.
Unfortunately, there are no real guidelines for when to use which OnUpdate
event. However, the TActionList.OnUpdate event is often easier to manage
because all of the status enabling code is located in one place and not in
separate event handlers, and command enabling code is generally short.
You may have noticed that you can drop multiple action lists on a form. For
example, the RichEdit demo application that comes with Delphi 4 and 5 uses two
action lists. Why use separate action lists? For performance reasons. Because of
the way action lists are updated, if you have a set of actions that do not need
to be enabled/disabled, then it makes sense to put these actions in a separate
action list that does not have an OnUpdate event handler. Remember that
the OnUpdate event fires once for each client control connected to any
action contained in the list. Therefore, even if an action does not have to be
enabled and disabled, as long as a client control is linked to the action, a
separate OnUpdate event for the action list will be generated.
It is also very important to realize that any OnUpdate event handler
gets called a lot. Therefore, do not include code that takes a long time to
execute in your OnUpdate handlers. If you do, you will notice degradation
in your application's performance.
The Execution Offices
Executing an action is handled in a similar way to updating an action. When a
client control instructs the associated action to be executed (e.g. on a mouse
click), the action list containing the action generates its OnExecute
event. Within the handler for this event, you could write the code that performs
the action.
However, you must be sure to test the action that is passed to the event
handler so that you perform the correct action at the appropriate time. The
reason this is important is because the TActionList.OnExecute event gets
generated whenever any action contained in the list needs to be executed.
As a result, I recommend creating an event handler for the OnExecute
event of the action object instead. The benefit is that this event is only
generated when the specific action needs to be performed. You might consider
writing an event handler for a TActionList.OnExecute to perform some
preprocessing before each action is executed, but I would not recommend this.
Instead, I would suggest that each action's OnExecute handler call a
common method. The result is a solution that is much easier to follow and does
not rely on a subtle behavior of TActionList.
Unfortunately, the figure in the Delphi online help describing the execution
process of actions is a bit misleading with respect to the order in which events
are generated. Figure 5 shows a similar figure but shows the order of events
more clearly. Notice that the action's OnExecute event occurs after the action
list and Application objects get a chance to process the action.

Figure 5: Executing an action involves several steps.
The Wardrobe Department
In addition to OnUpdate and OnExecute, TActionList
defines an OnChange event, which in my opinion is a rather strange event.
This event is generated only when the Category property of an action in
the list is changed or the Images property of the list is changed. What makes
this event strange is that I'm not sure why this event is even available. The Category
property is simply used at design-time to group actions together. There is no
reason to change the Category of an action at runtime and besides, what
would you do in response to this change in an event handler.
The Cue Card Department
The OnHint event of TAction is much more interesting. Providing
an event handler for this event gives you the opportunity to customize the hint
that is displayed on a client control connected to the action. There are two
parameters passed to an OnHint event handler. The first is HintStr,
which is used to customize the hint string. In our example, we handle the ActDelete.OnHint
event by customizing the hint to show the contents of the currently selected
item. For example, if the mouse were positioned over the Delete button in Figure
2, the hint would be:
Delete "The Sun Always Shines on T.V. "
Also notice that the second parameter, CanShow, is used to only show
the hint if an item is selected. Unfortunately, there is a little problem with
the OnHint event and the CanShow parameter when used with controls
that do not take the input focus, such as TSpeedButton. In this
situation, the CanShow parameter will only work correctly if the Hint
property for the action is not set (that is, an empty string). If the Hint
property is set, then this "default" hint string is displayed
regardless of what you set CanShow to.
Moving Pictures
As you can see in Figure 2, the CD Player utilizes Delphi 4's ability to
include glyphs next to menu items. Unfortunately, there are some issues that you
must be aware of when using action lists and menus with images. First, it is not
sufficient to simply assign the image list to the TActionList.Images
property. You must also assign the image list to the menu's Images
property.
The second issue involves the connection, or lack thereof, between an image
list and client controls connected to actions in an action list. For example,
let's suppose you have an action list with one action, and there is an image
list connected to the action list. Consider a speed button that is connected to
the action and as a result shows the glyph associated with the action. The glyph
is stored in the image list, of course.
Now, let's suppose you want to change the image associated with the action.
One would expect to be able to simply modify the image list component. Although
modifying the image list is required, it is unfortunately not sufficient to
accomplish this task. Unlike the other action properties (such as Caption
and ShortCut), changes to an action's associated glyph are not propagated
to all client controls connect to an action. As a result, after modifying the
image in the image list, you must clear the Glyph property of the client
control and then re-select the desired action using the client control's Action
property. This will reset the control's Glyph property to match the image
stored in the image list.
Unfortunately, there is yet another problem with actions and speed buttons
and images. Specifically, if you do not want an action's image to appear on a
speed button, you have to clear the speed button's Glyph property at
runtime (e.g. the form's OnCreate event). It is not sufficient to clear
the Glyph property at design-time because the change does not appear to
be streamed out correctly. The result is that although the speed button does not
have the glyph showing at design-time, when the form is loaded at runtime, the
action connected to the speed button resets the Glyph property to the
image associated with the action.
Summary
Actions and action lists were introduced back in Delphi 4. However, this highly powerful feature of Delphi is still widely underused and in many cases misused by developers.
One of the challenges of incorporating actions into an application is that the Delphi documentation does not provide any guidelines on how best to utilize them.
Unfortunately, make the wrong choice and your application could suffer performance penalties.
Throughout this paper, specific attention was given to the way actions work behind-the-scenes. For example, new diagrams that completely (and accurately) describe the update process and the execution process are presented. With
a better understanding of the way in which actions are implemented, it was possible to present a set of guidelines that developers can apply when utilizing actions in their own applications.
Contact Information
About the Author
Ray Konopka, Raize Software, Inc.
Ray Konopka is the founder of Raize Software, Inc. and the chief architect for their Raize Components and CodeSite products. Ray specializes in Delphi component development and is a frequent speaker at developer conferences.
Paper originally presented at the 11th Annual Borland Conference, July 2000.