Click here to Skip to main content
15,868,164 members
Articles / Programming Languages / C#
Article

ActionList for .NET 2.0

Rate me:
Please Sign up or sign in to vote.
4.89/5 (52 votes)
30 Apr 20065 min read 187.2K   2.5K   122   82
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.

Image 2

Image 3

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.

Image 4

Image 5

Image 6

Image 7

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):

C#
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:

C#
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.

C#
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.

C#
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:

C#
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:

C#
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


Written By
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 Pin
Lex Li13-Oct-12 0:58
professionalLex Li13-Oct-12 0:58 
You can now download the 1.2 release from NuGet,

http://nuget.org/packages/ActionListWinForms[^]

As the design time support is moved to a separate assembly, you need to run an installer to register design time support to Visual Studio,

https://github.com/lextm/ActionListWinForms/downloads[^]

Regards,

Lex
http://lextm.com

QuestionCrad's ActionList packages available on NuGet Pin
Lex Li6-May-12 1:16
professionalLex Li6-May-12 1:16 
QuestionOemplus doesn't work Pin
Ali Habib12-Oct-11 22:53
Ali Habib12-Oct-11 22:53 
AnswerRe: Oemplus doesn't work Pin
Lex Li6-May-12 1:11
professionalLex Li6-May-12 1:11 
Questionchange shortcutkeys dynamically Pin
Ali Habib10-Oct-11 1:18
Ali Habib10-Oct-11 1:18 
AnswerRe: change shortcutkeys dynamically Pin
Lex Li6-May-12 1:06
professionalLex Li6-May-12 1:06 
QuestionHow to add it in runtime Pin
Ali Habib10-Oct-11 1:01
Ali Habib10-Oct-11 1:01 
GeneralMy vote of 5 Pin
Ali Habib10-Oct-11 0:46
Ali Habib10-Oct-11 0:46 
QuestionActions inaccessible for ToolStripDropDownButton [modified] Pin
sdr11-Jan-11 2:36
sdr11-Jan-11 2:36 
GeneralMy vote of 5 Pin
l0ng764-Nov-10 0:37
l0ng764-Nov-10 0:37 
QuestionDual license possible? Pin
Christian Vogt22-Jun-10 21:51
professionalChristian Vogt22-Jun-10 21:51 
AnswerRe: Dual license possible? Pin
Lex Li23-Apr-12 2:28
professionalLex Li23-Apr-12 2:28 
GeneralMemory leak problem Pin
Sergey Semyonov28-Dec-09 6:54
Sergey Semyonov28-Dec-09 6:54 
GeneralRe: Memory leak problem Pin
Lex Li6-May-12 1:24
professionalLex Li6-May-12 1:24 
GeneralAmazing work! Pin
Florian.Witteler10-Mar-09 5:14
Florian.Witteler10-Mar-09 5:14 
GeneralExtenderProvider reference Pin
BoFabio18-Feb-09 3:51
BoFabio18-Feb-09 3:51 
QuestionUsing with Mono Pin
KeithRJacobs5-Dec-08 4:29
KeithRJacobs5-Dec-08 4:29 
AnswerRe: Using with Mono Pin
Lex Li6-May-12 1:33
professionalLex Li6-May-12 1:33 
GeneralI got "The Action you selected is owned by another ActionList" error. Pin
diviner9-Jun-08 21:36
diviner9-Jun-08 21:36 
GeneralRe: I got "The Action you selected is owned by another ActionList" error. Pin
diviner9-Jun-08 23:54
diviner9-Jun-08 23:54 
GeneralRe: I got "The Action you selected is owned by another ActionList" error. Pin
sdr12-Jan-11 4:09
sdr12-Jan-11 4:09 
GeneralLicense Pin
John Gunnarsson28-Sep-07 2:35
John Gunnarsson28-Sep-07 2:35 
GeneralRe: License Pin
Cradle771-Oct-07 12:13
Cradle771-Oct-07 12:13 
GeneralFix for setting up ToolTipText Pin
Igor Velikorossov29-Aug-07 14:43
Igor Velikorossov29-Aug-07 14:43 
GeneralUpdate for ToolStrip item's ToolTipText Pin
Cnight Stalker24-Apr-07 7:30
Cnight Stalker24-Apr-07 7:30 

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

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