Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Action Support in the VCF

3.63/5 (12 votes)
5 May 20068 min read 1   233  
An article on adding action support to your VCF application.

Actions screenshot

Introduction

Have you ever had a single piece of functionality or function, and multiple UI elements that could trigger this function? Ever written code to update all those elements so they stay in sync? Ever wished that you could easily consolidate the updating and the function in one place that was not tied down to a window class? The Visual Component Framework (VCF) solves this through the use of its Action class, sort of like MFC's ON_COMMAND/ON_UPDATE_COMMAND_UI macros on steroids.

Action makes it easy to have multiple "targets", all linked to one object that can determine their state and respond to the request to "perform" the action. For small applications that have only limited functionality, this may be slight overkill, but for larger applications, it makes it much easier to consolidate the code that controls this behavior in one place, and potentially easier to maintain as well.

What Action does

Action does two things: it provides a way to determine the update state of one or more "targets", and provides a way for a target to "trigger" the action, causing the action to perform or execute some specific functionality. This could include things like opening a file open dialog, or printing a page, or whatever appropriate function you can think of.

Updates

Actions are linked to one or more "targets". A target is, simply, any object that derives from VCF::Component, either directly or indirectly. Every component can, in turn, have an Action that is assigned to it. The Action provides a delegate that is fired when the component linked to that Action needs to be updated. Event handlers which are connected to this delegate are then notified, and can take the appropriate action and modify the action event, which in turn is used by the component to update its state.

Updates are driven by the main UI's event loop in the UIToolkit. During the run loop's idle time, the toolkit checks its list of components to update. This list is modified when a component's Action is set. If the Action is non-NULL, then the component adds itself to the toolkit's list of components to update. This ensures that we are only updating the components that could possibly react to an update event, as opposed to all possible components. MFC handles this differently: in MFC, the update is also driven during the idle time handler, but it sends a message to all children of the main window which, for a complex UI, wastes a lot of time doing nothing.

If the toolkit finds that it has components to update, it goes through and sends an update event to the component. The component then tells its associated Action to update itself. The Action then fires an ActionEvent on its Update delegate which notifies event handlers. The handlers can then modify a number of different values on the ActionEvent. The text state values can be set, and you can also toggle things like whether the event is "checked", "enabled", and so on. Once the delegate has fired the event, the Action then goes through its list of targets and calls the target's Component::handleEvent() function, passing it the ActionEvent instance. This allows the component to process the action event and make use of its settings. For example, a menu item might change its text, or enable/disable itself depending on the values in the action event. Most of the common classes like menu items, buttons, toolbar items, and so on, already have built-in support for handling updates in this fashion. Here's a sample of some update code:

void onUpdateViewSidebar( ActionEvent* e ) {
    if ( sideBar_->getVisible() ) {
        e->setState( e->getState() | ToolbarItem::tisPressed );
    }
    else {
        e->setState( e->getState() & ~ToolbarItem::tisPressed );
    }
    e->setChecked( sideBar_->getVisible() );
}

This code checks to see if the side bar panel (sideBar_) is visible, and then sets the state and the checked value of the ActionEvent variable.

Performing the action

Besides providing a framework for handling updates, an Action instance also "performs" some "action". This happens when the Action's perform() function is called with some event instance, which in turn passes this event to the Action's Performed delegate. Event handlers attached to an Action's Performed delegate can then respond accordingly. Here's an example of performing an action:

C++
void onViewSidebar( Event* e ) {
    sideBar_->setVisible( !sideBar_->getVisible() );
}

When the action is triggered, this function is called, which in turn toggles the side bar panel's visibility. Changing the panel's visibility will, in turn, change the state of the toolbar item (and any other targets that need updating).

The following screenshots demonstrate this:

Image 2

This shot shows the side bar invisible, and the menu items and toolbar item un-checked.

Once the toolbar item is clicked (or the menu item clicked on), the above action is triggered, resulting in the following update change:

Image 3

Notice that now the toolbar item and the menu item's state reflect the state change!

Several of the common UI elements like command buttons, toolbar items, radio and checkbox buttons, and menu items, already have built-in functionality to check their associated Action instance and call the perform() function when necessary, such as on a button click. This means that a button, a menu item, and possibly a toolbar button might all be targets of the same Action. Clicking on any one of these items will cause it to check if the component has an Action, and if so, call the Action's perform() function. Then, a single event handler can be used to respond and perform the desired functionality, like opening a new file.

How to Wire up Actions and Targets

The basic steps are to create an Action, add an event handler to the Action's Performed delegate to handle the specific task you want to accomplish, and add an event handler to the Action's Updated delegate to manage the appropriate state for update notifications. Then, create your UI elements, like buttons, menu items, and so forth, and add each element to the appropriate Action as a target. The framework will take care of the rest, and all you need to do to complete this is to fill in your event handler's code. If you have custom components or controls, then you may need to add support to them to properly handle updates and to call the Action's perform() function. We'll cover that later. Let's look at a simple example:

Action* fileOpenAction = new Action();

//menu
MenuBar* menuBar = new <A href="http://vcf-online.org/docs/src_manual/classVCF_1_1MenuBar.html" target=_blank>MenuBar</A>();
MenuItem* root = menuBar->getRootMenuItem();
MenuItem* fileMnu = 
    DefaultMenuItem( "&File", root, menuBar );
MenuItem* fileOpenMnu = 
    new <A href="http://vcf-online.org/docs/src_manual/classVCF_1_1DefaultMenuItem.html" target=_blank>DefaultMenuItem</A>( "&Open", fileMnu, menuBar );

//toolbar
<A href="http://vcf-online.org/docs/src_manual/classVCF_1_1Toolbar.html" target=_blank>Toolbar</A>* toolbar = new Toolbar();
<A href="http://vcf-online.org/docs/src_manual/classVCF_1_1ToolbarItem.html" target=_blank>ToolbarItem</A>* openTBitem = 
    toolbar->addToolBarButton( "Open" );
openTBitem->setTooltip( "Open from file" );

//wire up handlers
fileOpenAction->Performed += //some function to perform
                             //the action of opening a file...
fileOpenAction->Update += //some function to perform
                          //the updates...

//add targets
fileOpenAction-><A href="http://vcf-online.org/docs/src_manual/classVCF_1_1Action.html#da839daeef32cc949734727e4ca8d3cf" target=_blank>addTarget</A>(fileOpenMnu);
fileOpenAction->addTarget(openTBitem);

This creates a menu bar, a "File" menu item, and a "Open" menu item underneath. The next bit of code creates a toolbar and adds a file open item. The Action's Performed and Update delegates then have event handlers attached to them. Finally, the menu item and toolbar item are added as targets to the Action. The framework will take care of the rest, all we need to do is write our event handlers.

Update Handlers

The code for our update handler is simple (note that for this example, it's declared as a static function, but it can easily be a class member function):

C++
void SomeUpdateFunction( ActionEvent* e )
{
       if ( SomeConditionIsTrue ) {
               e->setEnabled( true );
       }
       else {
               e->setEnabled( false );
       }
}

We could do more, such as change the text (ActionEvent::setText()), or set the checked state, and so forth.

Action Performed Handlers

Finally, we get to implement the code that actually does something! In this example, we'll just open up a file open dialog.

C++
void SomeActionPerformedFunction( Event* e )
{
       <A href="http://vcf-online.org/docs/src_manual/classVCF_1_1CommonFileOpenDialog.html" target=_blank>CommonFileOpenDialog</A> dlg;
       if ( dlg.execute() ) {
               String fileName = dlg.getFileName();
       }
}

Writing Custom Event Handling for Updates

It's entirely possible that we would want to write a custom component or control that needs to support update events. Doing so is fairly straightforward, we just need to override the Component::handleEvent() function. You should first call the super class' handleEvent(). Then, check the event's type and make sure it's an Action::UpdateEvent type of event. If it is, then read the various state settings of the event, for example, you may want to check the text of the event. If you have a control, you don't need to check the enabled state, the Control class already does this for you and will automatically set the enabled state of the control accordingly. A simple example:

C++
class MyCoolLabel: public <A href="http://vcf-online.org/docs/src_manual/classVCF_1_1Label.html" target=_blank>Label</A> {
public:
       virtual void handleEvent( Event* e ) {
               Label::handleEvent( e );

               switch ( e->getType() ) {
                       case Action::UpdateEvent : {
                               ActionEvent* ae = (ActionEvent*)e;

                               setCaption( ae->getText() );
                       }
                       break;
               }
       }
};

That's it! The Label will automatically become enabled or disabled (because of the Control class' implementation of handleEvent()), and we set the caption based on whatever the action event's text is set to.

When to Call the Action's perform() Function

You can call the Action's perform() function whenever you have an event that should trigger this kind of behavior. For example, the CommandButton calls the perform() function on its Action when the framework notifies it that a click event has occurred. A toolbar item does so when it detects that it has been clicked on.

Notes on Building the Examples

You'll need to have the most recent version of the VCF installed (at least, 0-9-0 or better), and you'll need to make sure you have built the static libraries for the VCF (as opposed to the DLL version). The examples are configured to link to the VCF statically. For more documentation on building the VCF, see: Building the VCF, at the VCF online documentation.

Conclusion

We've covered the basics of using Actions and how to link up multiple targets to them. We've also covered how to respond to the various events that the framework generates as a result of having Actions and Action targets. Hopefully, this will show you how to make good use of Actions in your next VCF based application!

Questions about the framework are welcome, and you can post them either here, or in our forums. If you have suggestions on how to make any of this better, we'd love to hear them!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here