Into action with Delphi

Most developers agree that it’s desirable to separate presentation code from application logic. A good informal test is whether you could easily extract code from your Windows application to run behind a web application instead. Naturally, this will not apply if you are working on the next PhotoShop or another application where what happens in the GUI is the main part of the software, but even in these cases a modular approach to code is a great benefit when it comes to maintenance and upgrade. In this respect, a weakness of visual form designers like those in Delphi and Visual Basic is that they encourage developers to double-click a control, such as a button or menu and then type code. In the worst case, all the code ends up in a unit that really should only be concerned with the design and layout of the form.
Delphi is ahead of Visual Basic here, thanks to a well-established feature called ‘Actions’. The TAction components first appeared in Delphi 4, but not everyone has discovered how valuable they are. The key feature is that they centralise functions called by the user interface, but they also address issues like keeping the user interface up-to-date according to the state of the application. They can also be used to create a user-customisable toolbar or menu. Finally, they provide a set of standard actions for common tasks.
Action Basics
In a previous tutorial (PC Plus 241, Cross platform Delphi , page 173) we showed you how to use the sqlite database library in the form of a simple demonstration application. Then we showed you how to port it to the Mac using Lazarus. Now here’s how to refactor it to use Actions. The current version of the application has three buttons on a form and a menu item. All the code is where it should not be, in click event handlers. With Actions you can clean up all this code.
The first step is to remove the code from the button click event handlers into separate procedures. I created a new unit called Actions with four procedures matching the four features of the application: CreateDatabase, LoadImage, DisplayImage and VacuumDatabase. Next I added a TActionList component to the form, right-clicked to display its Action List Editor, and added four Actions to match up with the four procedures. For each Action, I set the Name, Caption and Hint, then implemented the OnExecute event handler to add a call to the appropriate procedure.
The next step is to set the Action property of each button and menu item to its matching TAction object. Delphi automatically hooks up the OnExecute event to the click event handler, and the caption property is derived from the Action object. Now imagine that you want menu items which duplicate the functionality of the buttons. This could not be easier. Simply open the menu designer, add new menu items, and set their Action properties. Everything else happens automatically.
Another flaw in the demo application is that all the buttons are always enabled, even when they cannot be used. For example, you cannot display an image before it has been loaded into the database. The relevant controls should be disabled. To fix this, I added an initialisation routine called in the FormCreate event handler. However, rather than disabling the controls themselves, this routine adjusts the Enabled property of the TAction objects. Delphi ensures that all the controls attached to those Actions are enabled or disabled.
Customisable toolbars
Now it’s time to get more ambitious and create a customisable toolbar. Users will be able to select their favourite actions at runtime, and put them on a toolbar. The application will remember their selections, so they will still be there next time the application runs. Here is how to do it.
First, drop a TActionManager and a TCustomizeDlg from the Additional palette onto the main form. Select the TActionManager. Currently it has no Actions to manage, but you can hook it up to the existing TActionList. Select the LinkedActionLists property and click the small button to open its collection editor. Add a new TActionListItem and set its ActionList property to your existing TActionList. Alternatively, you could delete your existing ActionList and simply add individual Actions to the ActionManager. The next step is to complete the ActionManager’s FileName property, so that customisations to be saved at runtime. I entered Toolbar.cfg.
Next, right-click the TActionManager and choose ‘Customize’. On the Toolbars tab, click ‘New’ to add a toolbar to the form. Then select the Actions tab. The existing Actions will be listed here, drag them to the toolbar. You will also need a standard Action to enable customisation. Select ‘New Standard Action’, and from the Tools section choose TCustomizeActionBars. This adds a Customize Action to the manager. Drag this to the toolbar as well, and then close the Customize dialog. Finally, select the TCustomizeDlg component and hook up the ActionManager property to your ActionManager.
Your users now have a customisable toolbar, accomplished with hardly any lengthy code. Admittedly, there’s a flaw in this design. Users could remove the Customize action and find themselves unable to get it back. To fix this, you can select the Customize action on the toolbar, and remove caDelete from ChangesAllowed. Unfortunately, there appears to a bug in the version I tried (Delphi 2006). If caMove is enabled, the user can still drag the item off the toolbar. Even if all the ChangesAllowed options are removed, the user could delete the item by clicking ‘Reset’ in the Customize dialog, and then removing it. It would be wise to add the Customize option to a fixed menu as a precaution.
Adding images
Toolbars look quite bad with plain text buttons. Here’s how you can use images instead. Add a TImageList control, then right-click to open the Image List Editor. The tricky bit is finding suitable images. Some example images are installed with Delphi, and these can usually be found in the Program Files\Common Files\Borland Shared folder. The button images will do for a test. These buttons include two images for an enabled/disabled state, but you can crop them in the Image List Editor to show only the first image. Once the ImageList is populated, you can set it as the Images property of the ActionManager and ActionList. Next, set the ImageIndex of each Action to the appropriate image. If you use TBitBtn or TSpeedBtn in place of standard buttons, you can have these displayed on buttons as well.
More about events
When the user fires off an OnExecute event, such as by clicking a button, the event fires in several places. The first event to fire is the OnExecute that is attached to the containing ActionList or ActionManager. Next, if you have an ApplicationEvents component on your form, the OnExecute for the Application fires. Finally, OnExecute fires for the Action itself. Each of these events has a Handled argument, and if you can set this to True then the chain of events stops.
Another event that you’ll find useful is OnUpdate. This fires at regular intervals when the application is idle. As the name suggests, it’s designed to let you respond to application state by enabling or disabling buttons. It can be easier and much simpler process to handle OnUpdate than to change the state of your Actions elsewhere in the application. Therefore, it’s important not to have slow code in OnUpdate event handlers, as the event fires frequently. Like OnExecute, OnUpdate fires at several levels in the Action chain.


