Introduction
Part 11 of this series of articles gave a general introduction to events in the CAB. This article investigates what we can do with these events in a little more detail.
Parameters of the Fire Method
As shown in part 11, the Fire method has four parameters:
workItem.EventTopics["MyEvent"].Fire(this, EventArgs.Empty, null, PublicationScope.Global);
The first two are easily understood: they are the parameters that will be passed into the EventSubscription method. The first is an object, and is intended to contain the sender of the event. The second is an EventArgs parameter, and can be used to pass data into the EventSubscription method as with normal EventArgs classes.
The third and fourth parameters control the scope that the CAB will use for searching for appropriate EventSubscription methods to be called. The third parameter is a WorkItem, and the fourth an item from the CAB PublicationScope enum. It is expected that you will pass in the WorkItem that contains the code firing the event, although you don’t have to. For these purposes the EventSubscription is treated as being contained in the WorkItem that its parent subscriber object is in (remember it has to be in the Items collection of a WorkItem for the eventing to work).
The PublicationScope enum has the following possible values:
- Global: the WorkItem parameter is ignored and ANY EventSubscription method with the correct name in the entire WorkItem hierarchy will be called.
- WorkItem: ONLY EventSubscription methods with the correct name in the WorkItem passed in as the third parameter will be called. If no WorkItem is passed in no EventSubscription method will be called.
- Descendants: EventSubscription methods with the correct name in the WorkItem passed in and any child WorkItems of that WorkItem will be called.
Code Example
A code example that shows these possibilities is available. This defines a hierarchy of WorkItems. Each WorkItem has its own Subscriber class to an event (“MyEvent”). The EventSubscriptions in each Subscriber class display an appropriate message when the event is fired. We then have three buttons that fire the event. The calls to the Fire method pass in as parameters a WorkItem in the middle of the hierarchy, with different PublicationScopes depending on which button is clicked.
Thus clicking the buttons shows how the PublicationScope affects which EventSubscriptions get called.
The EventTopics Collection
There are actually two WorkItems in the call to the Fire method:
workItem.EventTopics["MyEvent"].Fire(this, EventArgs.Empty, childWorkItem1, PublicationScope.WorkItem);
‘childWorkItem1’ is used to control the scope of the Fire method as discussed above.
‘workItem’ is used to access the EventTopics collection and hence our specific ‘MyEvent’ EventTopic.
Note that ANY WorkItem in the hierarchy can be used here to access the EventTopics collection, and the same EventTopic will be returned. In fact, behind the scenes there is only one EventTopics collection, and this is stored on the RootWorkItem. Any other WorkItem using the syntax workItem.EventTopics gets the same collection returned.
Thus the first WorkItem in the call does not affect the scope of the EventSubscriptions called at all.
Invoking onto the user interface thread
If you are familiar with .NET events you will know that one problem with them is that they can be fired on threads other than the user interface thread but may need to access user interface components. Threads that are not on the user interface thread should not interact with Microsoft’s GUI components as these are not thread-safe. As a result with .NET eventing it is quite common to ‘invoke’ back onto the user interface thread in an event handler as the first thing you do:
private void RunIt() { if (((ISynchronizeInvoke)this).InvokeRequired) this.Invoke(new MethodInvoker(RunIt)); else this.label1.Text = "Hello"; }
Don’t worry if you don’t recognize and understand this syntax: just accept that we may need to get code running back on the user interface thread in certain circumstances, and that this is the way we do it.
Clearly CAB events can suffer from the same problem. Once again we have a very neat solution to this, however. We simply add a parameter to our EventSubscription attribute as below:
[EventSubscription("MyEvent", ThreadOption.UserInterface)] public void MyEventHandler(object sender, EventArgs e) { MessageBox.Show("Hello from the CAB event handler"); }
This has the effect of invoking the code onto the user interface thread when the event is fired and the code is called. It’s somewhat easier than the .NET code in the previous example.
The ThreadOption Enumeration
There are two other values in the ThreadOption enumeration that we can use here (other than ThreadOption.UserInterface as above):
- ThreadOption.Publisher: forces the code to run on the same thread as the one the EventTopic was fired on. This is the default if we don’t specify a ThreadOption on our EventSubscription.
- ThreadOption.Background: forces the code to run asynchronously on a background thread. This means the code in the EventSubscription does not block the thread in the class the calls ‘Fire’. With normal .NET events we would normally have to explicitly start a second thread to get this behaviour, so again the syntax is much simpler.
AddSubscription/RemoveSubscription
The syntax shown above for setting up a subscription to an EventTopic using the EventSubscription attribute is very clean. However, there will be times when we want to dynamically add or remove subscriptions in code rather than using attributes. This is analogous to the use of the ‘+=’ and ‘-=’ syntax for hooking up our usual .NET events to event handlers.
To support this EventTopic has AddSubscription and RemoveSubscription methods. Obviously we use AddSubscription to add an EventSubscription to an EventTopic, as below:
RootWorkItem.EventTopics["MyEvent"].AddSubscription(subscriber, "MyEventHandler", workItem1, ThreadOption.UserInterface);
This should be fairly self-explanatory: we are setting up an EventSubscription for the method MyEventHandler in our subscription object. We are setting up this subscription in workItem1, and when the event handler is called it will run on the user interface thread.
Similarly we use RemoveSubscription to remove an EventSubscription from an EventTopic:
eventTopic.RemoveSubscription(subscriber, "MyEventHandler");
Here we simply need to identify the object and the event handler name that we are trying to remove.
We are only permitted to have one subscription to a given event handler on a given object. This is why RemoveSubscription only needs the two parameters to uniquely identify the subscription to be removed. If we try to add a subscription that already exists then the CAB won’t throw an exception, nor will it add a second subscription. Similarly we can try to remove a subscription that doesn’t exist and the CAB won’t throw an exception (but won’t actually do anything of course).
An example that demonstrates AddSubscription and RemoveSubscription is available.
Note that in the CAB if we want to prevent our EventSubscriptions from running when an event is fired we don’t have to remove them entirely. The EventTopic has an Enabled property that can be used. There are more details on this later in this article.
AddPublication/RemovePublication
The AddPublication method of an EventTopic is used to add .NET events as ‘Publications’ into a CAB EventTopic. What this means is that we can fire a .NET event and have CAB EventSubscriptions run without the need to set up .NET event handlers directly ourselves, or to explicitly call the Fire method of the EventTopic. Similarly we have a RemovePublication event to disable this behaviour.
AddPublication: what is a ‘Publication’?
The ‘Publication’ nomenclature is a little confusing. As we have seen the CAB eventing mechanism uses ‘Subscriptions’ to an EventTopic, which are methods that run when the associated EventTopic is fired.
However, in general the CAB eventing mechanism doesn’t use ‘Publications’. The Subscriptions will run without an explicit ‘Publication’ being set up at all: we can just Fire the EventTopic when we need to.
The method containing the Fire event code can be thought of as a ‘Publication’. However, if we look at the PublicationCount of an EventTopic after it has been fired directly with the ‘Fire’ method we see that it is zero: normally we don’t need a Publication for a CAB event to work.
A code example that shows this is available. It also shows how to use the ContainsSubscription method of an EventTopic (which is straightforward).
With the AddPublication method we ARE explicitly creating a Publication, and in the example below the PublicationCount will be one when an EventTopic is fired. But this is only for the special case where we want to hook .NET events up to CAB event subscriptions.
AddPublication: code example
A code example of how to use AddPublication is available. In this example we have a button on the Shell form that fires our CAB event, but there is NO .NET event handler set up for the click event of that button. Instead at when the application starts up we hook up the EventTopic to the button directly:
RootWorkItem.EventTopics["MyEvent"].AddPublication(Shell.cabEventFirerButton, "Click", RootWorkItem, PublicationScope.Global);
Here Shell.cabEventFirerButton is the button on the Shell form, and obviously ‘Click’ is the name of the .NET event that we want to be a Publication in our MyEvent EventTopic. Once this code has been run if we click the button the EventTopic will fire and any associated EventSubscriptions will run. We don’t need to explicitly call the Fire event of the EventTopic.
In the section ‘Parameters of the Fire Method’ we saw that when we call the ‘Fire’ method we can specify the scope that the CAB will use to search for EventSubscriptions. If we use AddPublication as shown here we are not calling the ‘Fire’ method directly. Instead we can specify the scope parameters in the AddPublication call: they are the final two parameters to the call as shown above. These are a WorkItem and a member of the PublicationScope enum as before, and work in the same way.
Issues with AddPublication
The AddPublication syntax is a very powerful way of hooking up .NET events to CAB EventSubscriptions. However, it needs to be used with care. Developers expect there to be a .NET event handler for a .NET event, and it can be very confusing if code is running as a result of an AddPublication call.
For example, in the code above if you were trying to work out what happens when you click the button you could easily think there’s no code going to run at all. There’s no easy way to find out that the click event is a CAB publication and what the associated EventTopic is.
As a result my feeling is that direct use of AddPublication as shown in the example above should be used sparingly. It’s clearer to hook up the .NET event handler and then call the ‘Fire’ event of your EventTopic directly in the handler.
EventTopic Enabled Property
The EventTopic class has an ‘Enabled’ property. By default this is set to true, meaning that when the EventTopic is fired all the associated EventSubscriptions will run. However, we can simply set this property to false to disable all the EventSubscriptions of the EventTopic.
Once again this can be useful and there’s no easy way of doing it with traditional .NET eventing.
An example showing this is available. This modifies the example above used to demonstrate changing PublicationScope. The example is set up to have multiple EventSubscriptions to one EventTopic. All of these normally get called when a button is clicked and the EventTopic is fired.
The example uses a checkbox. When the checkbox is checked the EventTopic is enabled and firing the EventTopic runs all the EventSubscriptions, when it is cleared the EventTopic is disabled and the EventSubscriptions do not run. This is achieved with the code below in the CheckedChanged event of the checkbox:
private void eventsEnabledCheckbox_CheckedChanged(object sender, EventArgs e) { rootWorkItem.EventTopics["MyEvent"].Enabled = eventsEnabledCheckbox.Checked; }
Conclusion
Events in the CAB are syntactically cleaner and are easier to use than normal .NET events, and can give us greater control over the scope of what runs when they are fired.