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

AttachedPropertyEvent Pattern: Part 2

, , 13 Jul 2009
Rate this:
Please Sign up or sign in to vote.
Pattern for publishing an event by using an attached property and IEventAggregator. This time it's generic.

Introduction

In part 1 of the AttachedPropertyEvent pattern article, we described how to create an attached property and publish an IEventAggregator event from the property. The reason for this was to avoid any code-behind in our views, and also to make sure there aren't any dependencies between our views and viewmodels.

One drawback with the implementation described in part 1 is that you need to implement an attached property for each and every event in your project. We wanted to do this in a generic way, and that's what we're going to describe here.

With our implementation, there are two ways to publish events. One is to publish an event (e.g., KeyDown), while the other is to publish an event when some property changes.

Current limitations

With the current implementation, it is only possible to publish one event per control. This is because it's only possible to set a value to an attached property once in a control. We're working on a way to solve this. If you have a suggestion, shoot!

Implementation

First, we've implemented a class named AttachedPropertyEvent. This class is in our Infrastructure project so that it can be referenced by whomever. Here, we have three attached properties:

The first attached property is named "CompositeEventName". This is the string representation of a composite event which has to be attached to each and every event that will be published. We have to have this property in order to publish the correct event. This attached property doesn't need a callback method.

public static DependencyProperty CompositeEventNameProperty = 
       DependencyProperty.RegisterAttached("CompositeEventName",
                                           typeof(string),
                                           typeof(AttachedPropertyEvent));

The next attached property is named "ListenForEvent". Using this property, you can publish events, by specifying the name of the event.

public static DependencyProperty ListenForEventProperty = 
   DependencyProperty.RegisterAttached("ListenForEvent",
                                       typeof (string),
                                       typeof (AttachedPropertyEvent),
                                       new PropertyMetadata(RaiseListenForEventCallback));

The callback method for this event is slightly complicated using Intermediate Language, but have a look at MSDN for a more detailed explanation: http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx.

We've created a method which we call from IL, instead of writing all the code in IL, named "HandleEvent". HandleEvent takes the composite event name and an object. It then gets the current event aggregator and uses Reflection to publish the event. We need to use Reflection here for "MakeGenericMethod" since we only have the string representation of the composite event and not a reference to the type itself.

public static void HandleEvent(string compositeEventName, object eventArgs)
{
    string compositeEventString = 
      string.Format("_10100.Infrastructure.Events." + compositeEventName);
    Type compositeEventType = Type.GetType(compositeEventString);

    IEventAggregator eventAggregator = 
      ServiceLocator.Current.GetInstance<IEventAggregator>();

    Type eventAggregatorType = eventAggregator.GetType();
    MethodInfo getEvent = eventAggregatorType.GetMethod("GetEvent");
    MethodInfo method = getEvent.MakeGenericMethod(new[] { compositeEventType });
    object compositeEventAggregator = method.Invoke(eventAggregator, null);

    foreach (MethodInfo methodInfo in compositeEventAggregator.GetType().GetMethods())
    {
        Console.WriteLine(methodInfo.Name);
    }

    MethodInfo publishMethod = compositeEventAggregator.GetType().GetMethod("Publish");
    publishMethod.Invoke(compositeEventAggregator, new[] {eventArgs});
}

Back to the callback method of the ListenForEvent attached property. As said above, look at the MSDN link in order to understand fully what's going on here, but what we've added is the call to "HandleEvent".

The reason for creating an assembly during runtime is because we need to call EventInfo.AddEventHandler. This method takes a delegate, and to create this delegate, we need to know something about the event. So, by doing it this way, the delegate is being dynamically created, and we don't need to worry about what kind of event it is, what kind of EventArg it has, and how many parameters there are.

private static void RaiseListenForEventCallback(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue == null) return;

    string listenForEvent = (string) GetListenForEvent(d);
    string compositeEventName = (string) GetCompositeEventName(d);

    Type control = d.GetType();
    EventInfo eventInfo = control.GetEvent(listenForEvent);
    Type eventHandlerType = eventInfo.EventHandlerType;

    MethodInfo invokeMethod = eventHandlerType.GetMethod("Invoke");
    ParameterInfo[] parms = invokeMethod.GetParameters();
    Type[] parmTypes = new Type[parms.Length];
    for (int i = 0; i < parms.Length; i++)
    {
        parmTypes[i] = parms[i].ParameterType;
    }

    AssemblyName aName = new AssemblyName { Name = "DynamicTypes" };
    AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, 
                         AssemblyBuilderAccess.Run);
    ModuleBuilder mb = ab.DefineDynamicModule(aName.Name);
    TypeBuilder tb = mb.DefineType("Handler", 
                     TypeAttributes.Class | TypeAttributes.Public);

    MethodBuilder handler = 
      tb.DefineMethod("DynamicHandler",
                      MethodAttributes.Public | MethodAttributes.Static,
                      invokeMethod.ReturnType, parmTypes);

    ILGenerator il = handler.GetILGenerator();
    il.EmitWriteLine(string.Format("{0} event was raised!", 
                     eventHandlerType.Name));

    // Here's our call to the HandleEvent method. 
    Type apeMethodsType = typeof(AttachedPropertyEventMethods);
    MethodInfo handleEventMethodInfo = apeMethodsType.GetMethod("HandleEvent");
    il.DeclareLocal(typeof (string));
    il.Emit(OpCodes.Ldstr, compositeEventName);
    il.Emit(OpCodes.Stloc_0);
    il.Emit(OpCodes.Ldloc_0);
    il.Emit(OpCodes.Ldarg_1);
    il.EmitCall(OpCodes.Call, handleEventMethodInfo, 
                new[] { typeof(string), typeof(object) });
    // End of code for calling HandleEvent

    il.Emit(OpCodes.Ret);

    Type finished = tb.CreateType();
    MethodInfo eventHandler = finished.GetMethod("DynamicHandler");

    Delegate del = Delegate.CreateDelegate(eventHandlerType, eventHandler);
    eventInfo.AddEventHandler(d, del);
}

The last attached property is "ListenForProperty". Using this attached property, you can publish events when a property changes.

public static DependencyProperty ListenForPropertyProperty = 
   DependencyProperty.RegisterAttached("ListenForProperty",
                                       typeof (object),
                                       typeof (AttachedPropertyEvent),
                                       new PropertyMetadata(
                                           RaiseListenForPropertyCallback));

The callback method for "ListenForProperty" uses the HandleEvent method which is described above, giving it the composite event name and the new value of the control's property you're listening to.

private static void RaiseListenForPropertyCallback(DependencyObject d, 
                    DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue == null) return;

    AttachedPropertyEventMethods.HandleEvent(
      (string) GetCompositeEventName(d), e.NewValue);
}

How to publish an event

After the above code is implemented, you're ready to publish events and subscribe to them. There's two things you need to do:

  1. Create a composite event.
  2. Add two lines in your XAML code.

We're creating our composite events in our namespace _10100.Infrastructure.Events. Right now, it's hardcoded in the "HandleEvent" method; that's where our composite events are. The only thing you need to do to create a composite event is:

public class MyCompositeEvent : CompositePresentationEvent<KeyEventArgs> {}

Now, to publish this event from XAML, do the following in a control:

<Button x:Name="btnSomeButton"
    Content="My Button"
    Events:AttachedPropertyEvent.CompositeEventName="MyCompositeEvent"
    Events:AttachedPropertyEvent.ListenForEvent="KeyDown"
    />

Do the same when you want to listen to some property, but instead of using the attached property "ListenForEvent", use "ListenForProperty".

Note, you need to always have the "CompositeEventName" before any of the two others!

Conclusion

That's it for now. It works, but it has a major drawback that you can only have one attached property per control. So, if you have any suggestions on how to get around this issue, please don't hesitate to contact us.

Another things is that we haven't done any performance testing on this yet. It seems to be running fine, but there're no guarantees. Note also that exception handling is removed from the code in this article.

Again, please give us feedback, ideas, criticism, etc... to our way of handling this. We really wish to improve it as much as we can.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Authors


Björn Asplund
Software Developer Diversify Consulting Group AB
Sweden Sweden
No Biography provided

Comments and Discussions

 
GeneralAbout current limitations Pinmemberdisore20-Jul-09 22:15 
GeneralRe: About current limitations PinmemberJan-Erik Romoeren10-Aug-09 2:35 

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
Web03 | 2.8.140827.1 | Last Updated 13 Jul 2009
Article Copyright 2009 by Jan-Erik Romoeren, Björn Asplund
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid