So, it worked. I found just enough spare time to try out the crazy idea I mentioned in the last post. It’s about control add-ins and events. In the last post I gave a tip about exposing the actual control as a property decorated with ApplicationVisible, which allowed you to directly access all properties and methods of the control.
However, if you wanted to do the same with the events, you had no other option but to manually create an event handler for each event type, then to add an event handler for the actual event from the original control, and finally to raise the event on your control add-in from the event handler for the actual control. You lost me already, so did I
This is what I am talking about. Imagine a button, and that you want to allow NAV to respond to its Click event. This is what you need to do:
Might not seem too much, but consider that you must do this for every single event that you want to use, that it requires some rewrite-restart-redeploy-redesign-rebind workout to implement support for another event you forgot last time, and then it becomes an problem. Probably the ugliest part of it all is the fact that for C/SIDE to properly insert the new event triggers for an updated class, you have to unbind the Control Add-in, and then to re-bind it. This loses any C/AL code in previously existing triggers. Add to the equation the human error variable, which kind of readily pops up whenever manual code duplication is involved, and you’ll have a lot of happy hours fixing the mess and juggling dlls and C/AL code back and forth.
How about this. How about not having to add anything at all to your C# source code, and not having to redeploy anything, and then to support any event you want just through a single line of C/AL code?
Yes. That’s what this blog post is about.
This is what I have created:
- A generic abstract control add-in base class that provides the functionality for subscribing to and unsubscribing from events of the inner control
- A demo descendant class that inherits this base class, and is based on the System.Windows.Forms.Button control
- A demo page in NAV that binds the control add-in to a field and binds three events
To make a long story short, if you simply want to use the framework I provided, you can download it here and simply start using it. If you want to understand how I did it and how it actually works, read on.
Let’s take a look at all of the above, step by step.
The generic abstract class
What I want to have is a generic class that accepts as the generic type any type that descends from System.Windows.Forms.Control. This class should instantiate the instance of generic type and expose it as a property (so that NAV can access it through the mechanism from my last blog post). This is the child control of my control add-in, for example a Button, a TextBox, a CheckBox, or whatever.
The class must have two methods: SubscribeToEvent and UnsubscribeFromEvent. These two methods will receive a single parameter of type string, which represents the name of the event I want to subscribe to on the child control. The class will also have a single event, I’ll call it OnAnyEvent, that fires for all subscribed events of the child control. This event must provide all the context of the original event that fired on the child control, plus the event name, so that C/AL code can know exactly which event was fired. Most of the events on most of the controls are simply of the EventHandler type, and you can’t tell which event was fired just by observing its type.
Therefore, we’ll need a new delegate type to support our new event type:
Then, let’s write the class skeleton:
There is a strong reason why I made it both generic and abstract. It’s generic so that you can end up having strong typing for any type of internal control that you want to use (even your own controls – all of them inherit from System.Windows.Forms.Control). It’s abstract because NAV cannot instantiate generic control add-ins (or any other type for that matter), and by making it abstract I am forcing you to use it to create a descendant, and base it on any Control descendant you want.
I decided to inherit from WinFormsControlAddInBase, even though I might have gone with a simpler structure, making you decide which actual interfaces to develop. Let’s leave it at this for now, maybe in the future I fix it and make it even more generic by providing the bare-bone structure that allows you absolute flexibility. Judging from the past and how observant I was about promises of this type, this may actually be never, but that’s the beauty of the community, isn’t it. You can take this class and then develop it further in whichever direction you please
I have exposed the InnerControl, which has a public getter and a protected setter. It is of generic type, which means that when you inherit from this class and specify the actual control type, the C# compiler will turn this property into a strongly typed whatever. The beauty of it is that C/AL will also recognize it as strongly typed control, allowing you full access to the inner (child) control. The setter is protected, so that you can influence this control from within your inheriting classes, but C/AL can’t mess it up by instantiating a new control and binding it to this property.
Since I am inheriting from the WinFormsControlAddInBase class, I’ve implemented the CreateControl method. I could have left it unimplemented, and make you implement it in your inheriting classes, but since I wanted to provide some common sure-fire behavior and make your life simpler, I have made this method instantiate a control of the generic type, assign it to the InnerControl property, and then return to the NAV extensibility framework. If you want, you can always override this method in your own class, and your method will be called instead of mine. If you do that, I won’t mind, just remember to assign the control to the InnerControl property.
Then we have the OnAnyEvent event, and the two methods used to (un)subscribe C/AL to/from any event your heart desires.
Now let’s implement the tricky part.
Binding a method of a fixed signature to an event by knowing its name is more of a rocket science than it seems. The problem is that all events work through delegates, and to bind a method as an event handler to an event, the handler method must match the signature of the event delegate. Since every event can have a different signature, you can’t just bind a single method to all of the events.
In a nutshell, what we need is a capability to create a matching delegate for the event on the fly, and this part is simple:
It starts off simple. It first retrieves the EventInfo instance from the child control type for the event that matches the requested name. It gets a bit grumpy if this event does not exist, through. That should be your problem to handle.
The last line of code binds the delegate d to the child control. What we are missing here is the logic that actually creates this delegate. This will take some more work.
Creating delegates on the fly is piece of cheesecake if you have an existing method that matches the delegate signature. However, since we want to enable subscribing to any event, it may mean subscribing to methods of any signature, which is kind of impossible. Yes, I know – there are very few things that are actually impossible, and that’s why I say “kind of impossible”
That’s why I’ve decided to allow subscribing not to any possible event type, but to allow subscribing to those event types that follow the convention of .NET control events – they should all have two arguments: sender of type System.Object, and arguments of EventArgs type or one of its descendants. This is still an unlimited number of possible delegate types to support, which means that I’ll have to again resort to generics.
So, let’s create a generic method:
Then, I can use reflection to create a delegate of an actual EventArgs type and bind it to this generic method. However, here I have a problem. To properly notify C/AL of the event name (see this mysterious EventName variable in OnAnyAvent method call?) I have somehow to provide this information to the delegated method, which I simply can’t. When an event is invoked, it simply calls the bound delegates, and cannot pass more information than that expected through delegate’s arguments.
To solve this problem, I’m creating a new class, and moving the generic OnEvent<T> method into this class:
This class will be instantiated once for each event C/AL subscribes to, and it will then be able to provide the event name to the OnAnyEvent method.
So far, so good. Last, but not least, I need to create an actual delegate to pass to the AddEventHandler method.
Let me start with this:
First, I need the list of all EventManager instances for all different event types. Then, I have the GetOnEventDelegate method that returns the correct delegate for the matching delegate type, based on the correct instance of the EventManager.
Then, I’ll change the SubscribeToEvent method to instantiate a matching EventManager instance, and then to call this GetOnEventDelegate method to retrieve the correct delegate:
As you can see, if the matching event manager is already found, the method immediately exits. This will occur only in the situation when you are trying to subscribe to an event to which you are already subscribed.
Okay, the last remaining part is to provide the body for the GetOnEventDelegate method:
This method pulls some tricks. First, it retrieves the type of the event arguments for the event. Since all events that I intend to support must follow the <sender, eventArgs> pattern, the only thing I need is the type of the eventArgs parameter. The EventHandlerType property of the EventInfo class represents the delegate type for the event handler, and all delegates have the method Invoke that .NET Framework uses to invoke the delegate dynamically. Since .NET is 0-based, I am interested in the parameter at the index 1 of this method, and I am first retrieving its type. Here, in my eventArgsType variable, I have the actual type for the correct descendant of the EventArgs type for my event.
Then, I take the method info for the OnEvent method from the EventManager type. I call it onEventTemplate, since this is a generic method, and method info for a generic method is still generic. Therefore I call the MakeGenericMethod on this template type to create a generic method for the eventArgsType I retrieved earlier.
Finally, I use this MethodInfo instance to create a delegate from it, for the EventManager instance I passed into this method.
Finally, I’ll complete the UnsubscribeFromEvent method. It will be very similar to the SubscribeToEvent, with just some minor differences:
And that’s it. The generic abstract class that you can use as the base for any kind of control add-ins that you want to allow C/AL to subscribe to its events is completed.
Demo Descendant Class: Button
The next step is to create an actual class and export it as a control add-in. As an example, I have chosen the Button.
So, if you want to have a control add-in that shows a button on a page, and allows you to subscribe to any of its events from C/AL, you just need to do this:
No, I am not kidding, that’s actually all. If you now build the solution and register the add-in, it will show a button on a page and allow you to access its properties and methods, and to subscribe to any of its events. All the logic is already contained in the base EventAwareControlAddInBase class, and if you need nothing more than this, you are actually good to go.
Demo NAV Code
So, let’s see how this thing works. This is the code I have in the demo files I provided earlier in this post:
Okay, I have cheated a bit – I have added the ControlAddInReady event to my generic abstract base class, but I didn’t describe it in this post because it is completely irrelevant for it. You have its implementation in the demo files.
So, this code subscribes to three different events. Then, from the OnAnyEvent event trigger it simply shows which event has fired, and then unsubscribes from it.
Simple, clean, and efficient. Do you like it?
There are limitations to this thing. If the event you are trying to subscribe to has different parameter pattern than (object sender, EventArgs eventargs) then this trick will not work. I don’t know exactly how it will fail, but it will fail. To make it work, you’ll have to expose this event manually. Now that we are talking about failures, there is no exception handling in the code I provided, which does not mean it could benefit from some. I’ve just provided this as a proof of concept, not as a bullet-proof production-grade code. I strongly advise you add exception handling at least in the SubscribeToEvent and UnsubscribeFromEvent methods, for example, having them return bool if they fail to subscribe to event, or something like that.
I’d definitely like to hear from you now. Do you find this useful? Of course, you are completely free to use this in your projects, and if you do, I’d just like to know if this thing helped you. Please, leave a comment below to let the world know