Click here to Skip to main content
Click here to Skip to main content

ActionList for .NET 2.0

, 30 Apr 2006
Rate this:
Please Sign up or sign in to vote.
An implementation of Borland's ActionList.

Sample Image - CradsActions.gif

Introduction

Everyone who has worked with Borland's Delphi knows how powerful Actions are. Actions are used to link together various UI elements, such as buttons, menu items, and toolbar buttons, making them behave consistently: linked items are checked/ unchecked/ enabled/ disabled at the same time, and they share the same Text and eventually Image property and, of course, execute the same code once clicked.

Using the code

This library is based on two main components: the ActionList class and the Action class. ActionList, which implements the IExtenderProvider interface, holds an Action's collection, and provides design time support for associating an Action with a ButtonBase or a ToolStripItem derived control. So, you first drop an ActionList from the Toolbox to your WinForm, then you can edit its Actions collection.

After adding and setting the desired values for the Action's properties, you can link it to a control, and you'll notice how its property values like Text, Image, Tooltip, shortcut key, etc., are replaced with the the connected Action's ones.

Each Action exposes two main events: Execute and Update. The Execute event is raised every time one of the linked controls is clicked, so people should trap this event instead of handling the control's Click directly. The Update event is raised while an application is in the idle state, and it's useful to enable/disable linked controls at runtime (you can use it for other purposes, like setting their CheckState, of course).

How actions work behind the scenes

I'm not going to explain every line of code: if you've got a little experience with the .NET Framework, everything is pretty simple and easy to understand, so I'll focus only on a couple of main topics.

First of all, every Action internally holds a collection of target controls. Once a control is added, its properties are refreshed according to the Action's ones, and its Click and CheckStateChanged events are handled (and, of course, once a target is removed, those handlers are removed too):

internal void InternalAddTarget(Component extendee)
{
    // we first add extendee to targets collection
    targets.Add(extendee);
    // we refresh its properties to Action's ones
    refreshState(extendee);
    // we add some handler to its events
    AddHandler(extendee);
    OnAddingTarget(extendee);
}

Let's first take a look on how the target's property setting is handled: all the work is performed by the private updateProperty method:

private void updateProperty(Component target, string propertyName, object value)
{
    WorkingState = ActionWorkingState.Driving;
    try
    {
        if (ActionList != null)
        {
            if (!SpecialUpdateProperty(target, propertyName, value))
                ActionList.TypesDescription[target.GetType()].SetValue(
                    propertyName, target, value);
        }
    }
    finally
    {
        WorkingState = ActionWorkingState.Listening;
    }            
}

As a first step, it changes the Action's working state to Driving (which causes the target's event handling being temporarily disabled), then it sets the target's property to the right value. The only matter is that it works by Reflection, which can be really slow, so I've built a special class named ActionTargetDescriptionInfo which caches the Types' PropertyInfo to speed up all the work.

About event handling: when a control is added to the target's collection, the Action traps its Click and CheckStateChanged events.

protected virtual void AddHandler(Component extendee)
{
    // Click event's handling, if present
    EventInfo clickEvent = extendee.GetType().GetEvent("Click");
    if (clickEvent != null)
    {
        clickEvent.AddEventHandler(extendee, clickEventHandler);
    }

    // CheckStateChanged event's handling, if present
    EventInfo checkStateChangedEvent = 
              extendee.GetType().GetEvent("CheckStateChanged");
    if (checkStateChangedEvent != null)
    {
        checkStateChangedEvent.AddEventHandler(extendee, 
                         checkStateChangedEventHandler);
    }
}

ClickEventHandler and checkStateChangedEventHandler are pretty simple: the first raises the Execute event, the latter updates every target's checkState property according to the Action's one.

private void handleClick(object sender, EventArgs e)
{
    if (WorkingState == ActionWorkingState.Listening)
    {
        Component target = sender as Component;
        Debug.Assert(target != null, "Target is not a component");
        Debug.Assert(targets.Contains(target), 
              "Target doesn't exist on targets collection");

        DoExecute();
    }
}

private void handleCheckStateChanged(object sender, EventArgs e)
{
    if (WorkingState == ActionWorkingState.Listening)
    {
        Component target = sender as Component;
        CheckState = (CheckState)ActionList.
            TypesDescription[sender.GetType()].GetValue("CheckState", sender);
            
    }
}

The last point to explain is how the Update event rising works: it is driven by the ActionList, which handles the Application.Idle and raises this event for every owned Action:

void Application_Idle(object sender, EventArgs e)
{
    OnUpdate(EventArgs.Empty);
}

public event EventHandler Update;
protected virtual void OnUpdate(EventArgs eventArgs)
{
    // we first raise ActionList's Update event
    if (Update != null)
        Update(this, eventArgs);

    // next, we raise child actions update
    foreach (Action action in actions)
    {
        action.DoUpdate();
    }
}

How to create your own custom action

By version 1.1.1.0, Crad's Actions library offers a better support to expandability. It's pretty easy to build your own custom Action: all you have to do is to create a new class which inherits from Crad.Windows.Forms.Actions.Action and mark it with the StandardAction attribute. Designer support is provided by a new implementation of the internal ActionCollectionEditor class, which is able to inspect the current project references, looking for custom Actions.

This behaviour is achieved by the ITypeDiscoveryService designer service, which makes simple solving a really hard assembly-inspecting problem: looking for types at design time can be a rather complex task, indeed, because the current project could not have been built yet, and the corresponding assembly could not exist at all. Using the ITypeDiscoveryService, everything becomes easier, so, right now, ActionCollectionEditor has a private method that does the job; it looks like the following code snippet:

private Type[] getReturnedTypes(IServiceProvider provider)
{
    List<Type> res = new List<Type>();

    ITypeDiscoveryService tds = (ITypeDiscoveryService)
        provider.GetService(typeof(ITypeDiscoveryService));
    
    if (tds != null)
        foreach (Type actionType in tds.GetTypes(typeof(Action), false))
        {
            if (actionType.GetCustomAttributes(typeof(
                StandardActionAttribute), false).Length > 0 &&
            !res.Contains(actionType))
                res.Add(actionType);
        }

    return res.ToArray();
}

First of all, it recovers a reference to the designer's ITypeDiscoveryService, then it calls its GetTypes method to recover every code-reachable type that inherits from Action and has been marked with the StandardAction attribute.

Points of interest

Actions can help a Windows Forms developer to coordinate the behaviour of various UI elements in a pretty simple and efficient way. I wanted them be linkable to a lot of .NET Framework 2.0 WinForms controls (they work with every ButtonBase or ToolStripItem derived control), so the only way to handle this requirement was using Reflection. However, the performance drop is compensated using a PropertyInfo caching system, which is able to reuse the metadata information for an object of the same type.

Crad's Actions library ships with some purpose-specific actions too: for example, some of them are helpful to handle clipboard-related operations, such as cut, copy, or paste, while others provide formatting features when applied to a RichTextBox. According to Borland's naming, they're called Standard Actions, and I'm still working on them, so... expect some more for the next releases.

To better understand how easy it is to implement complex user interfaces using Actions, you can check out the simple RTF Editor provided as a demo application.

History

  • 04/30/2006: Crad's Actions 1.1.1.0 released.
    • Support for creating custom Actions added (with a brief description in this article).
    • Some new StandardActions added, like ListView actions and About action.
  • 04/22/2006: First version.

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

Share

About the Author

Cradle77
Web Developer
Italy Italy
Marco De Sanctis is an italian developer and a software consultant. His interests are focused on object-oriented architecture and data-driven applications.

Comments and Discussions

 
NewsCrad's ActionList 1.2 released on NuGet PinmemberLex Li13-Oct-12 0:58 
QuestionCrad's ActionList packages available on NuGet Pinmemberlextm6-May-12 1:16 
QuestionOemplus doesn't work Pinmemberaishar12-Oct-11 22:53 
AnswerRe: Oemplus doesn't work Pinmemberlextm6-May-12 1:11 
Questionchange shortcutkeys dynamically Pinmemberaishar10-Oct-11 1:18 
AnswerRe: change shortcutkeys dynamically Pinmemberlextm6-May-12 1:06 
QuestionHow to add it in runtime Pinmemberaishar10-Oct-11 1:01 
GeneralMy vote of 5 Pinmemberaishar10-Oct-11 0:46 
QuestionActions inaccessible for ToolStripDropDownButton [modified] Pinmembersdr11-Jan-11 2:36 
GeneralMy vote of 5 Pinmemberl0ng764-Nov-10 0:37 
QuestionDual license possible? PinmemberChristian Vogt22-Jun-10 21:51 
AnswerRe: Dual license possible? Pinmemberlextm23-Apr-12 2:28 
GeneralMemory leak problem PinmemberSergey Semyonov28-Dec-09 6:54 
GeneralRe: Memory leak problem Pinmemberlextm6-May-12 1:24 
GeneralAmazing work! PinmemberFlorian.Witteler10-Mar-09 5:14 
GeneralExtenderProvider reference PinmemberBoFabio18-Feb-09 3:51 
QuestionUsing with Mono PinmemberKeithRJacobs5-Dec-08 4:29 
AnswerRe: Using with Mono Pinmemberlextm6-May-12 1:33 
GeneralI got "The Action you selected is owned by another ActionList" error. Pinmemberdiviner9-Jun-08 21:36 
GeneralRe: I got "The Action you selected is owned by another ActionList" error. Pinmemberdiviner9-Jun-08 23:54 
GeneralRe: I got "The Action you selected is owned by another ActionList" error. Pinmembersdr12-Jan-11 4:09 
GeneralLicense PinmemberJohn Gunnarsson28-Sep-07 2:35 
GeneralRe: License PinmemberCradle771-Oct-07 12:13 
GeneralFix for setting up ToolTipText PinmemberIgor Velikorossov29-Aug-07 14:43 
GeneralUpdate for ToolStrip item's ToolTipText Pinmembersooggie24-Apr-07 7:30 
GeneralLife saver!!! Pinmembersooggie14-Apr-07 3:07 
GeneralRe: Life saver!!! PinmemberCradle7714-Apr-07 3:40 
GeneralNullReferenceException in Action.handleCheckStateChanged Pinmemberujos15-Mar-07 6:07 
GeneralRe: NullReferenceException in Action.handleCheckStateChanged Pinmemberlextm6-May-12 1:50 
GeneralExpose Action.ActionList PinmemberIgor Velikorossov16-Jan-07 15:33 
GeneralRe: Expose Action.ActionList Pinmemberlextm6-May-12 1:53 
QuestionOne ActionList for many Forms PinmemberPashec24-Dec-06 3:24 
AnswerRe: One ActionList for many Forms Pinmembermarkeric5-Feb-07 10:34 
AnswerRe: One ActionList for many Forms PinmemberWideWeide27-Apr-07 15:38 
GeneralRe: One ActionList for many Forms Pinmemberbgnt4421-Apr-11 14:00 
GeneralUse the WPF ICommand interface instead PinmemberKlinkby22-Oct-06 1:15 
GeneralPb with collection editor Pinmembersyl7419-Oct-06 4:27 
GeneralShortcutList PinmemberSalarSoft26-Sep-06 5:19 
Questionchanging action.checked does not trigger DoExecute() Pinmemberfelix at home31-Aug-06 14:00 
AnswerRe: changing action.checked does not trigger DoExecute() Pinmemberfelix at home31-Aug-06 14:37 
QuestionCan't Add new actions to actionlist PinmemberWayne Sawyer10-Aug-06 2:13 
AnswerRe: Can't Add new actions to actionlist PinmemberRicMan27-Dec-06 10:36 
AnswerRe: Can't Add new actions to actionlist PinmemberShuriken8717-Aug-07 2:35 
QuestionAction names Pinmemberbjp7774-Aug-06 0:31 
AnswerRe: Action names PinmemberCradle774-Aug-06 10:11 
AnswerRe: Action names Pinmemberbjp7775-Aug-06 0:12 
GeneralDoes not work for UserControls PinmemberKarel Kral18-Jul-06 3:01 
AnswerRe: Does not work for UserControls PinmemberCradle774-Aug-06 10:02 
GeneralRe: Does not work for UserControls PinmemberJohn Gunnarsson2-Oct-07 0:37 
GeneralRe: Does not work for UserControls PinmemberJohn Gunnarsson2-Oct-07 0:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141022.2 | Last Updated 30 Apr 2006
Article Copyright 2006 by Cradle77
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid