An event is a message sent by an object to signal the occurrence of an action. The action can be caused by user interaction, such as a mouse click, or it can be triggered by some other program logic. The object that raises (triggers) the event is the event sender. The object that captures the event and responds to it is the event receiver.
In event communication, the event sender class does not know which object or method receives (handles) the event it raises. An intermediary or pointer mechanism is necessary between the source and the receiver. The .NET Framework defines a special type (delegate) that provides the functionality of a function pointer.
Delegates
A delegate is a class that holds a reference to a method. Unlike other classes, a delegate class has a signature and can hold references only to methods that match its signature. A delegate is equivalent to a type-safe function pointer or a callback.
To consume an event in an application, you must provide an event handler (an event handling method) that executes program logic in response to the event and register the event handler with the event source. The event handler must have the same signature as the event delegate. This process is event wiring.
When you create an instance of a delegate, you pass in the function name (as a parameter for the delegate's constructor) that this delegate references.See the following code example:
Saying that this delegate has a signature means it returns an int type and takes two parameters: string and bool. For that reason, when you are about to hook an event (for example, IGlobeDisplayEvents.AfterDraw), the event handler method must match the delegate signature declared by the event interface delegate. To consume an event in an application, you must provide an event handler (an event handling method) that executes program logic in response to the event and register the event handler with the event source. This process is event wiring.
In ArcObjects.NET, event interfaces (also known as outbound interfaces) have an _Event suffix, because they are automatically suffixed with _Event by the type library importer.
Required steps to start listening to an ArcObjects event
Cast the relevant event interface (you can also do this in line). See the following code example:
Alternatively, let Visual Studio do the work so you don't have to add the delegate signature:
Cast the relevant event interface.
Start the registration of the requested event. Use the Visual Studio integrated development environment (IDE) Intellisense capabilities to do most of the work for you. Once you add the += operator, Visual Studio allows you to add the delegate by pressing the Tab key. Visual Studio automatically adds the event handler method. Press the Tab key twice, and Visual Studio registers the event handler and adds the event handler method. See the following screen shot:
Before pressing the Tab key the second time, you can modify the default event handler method name by using the arrow keys to scroll and change the name. Do notuse the mouse to accomplish this.
Visual Studio adds the event handler method and automatically shows the event handler implementation code.
Wiring events of member objects
In many cases, you will need to wire events of objects that are members of container objects and are accessed through a property. For example, it is possible that you will need to implement a command for a GlobeControl application and listen to one of the GlobeDisplayEvents (for example, AfterDraw). Whether or not you use the IDE integration or write the command yourself, it is almost the obvious choice to use GlobeHookHelper class, which allows your command to work in ArcGlobe as well. In this case, the GlobeHookHelper is a class member. See the following code example:
[C#]
//Class members.private IGlobeHookHelper m_globeHookHelper = null;
publicoverridevoid OnCreate(object hook)
{
//Initialize the hook helper.if (m_globeHookHelper == null)
m_globeHookHelper = new GlobeHookHelper();
//Set the hook.
m_globeHookHelper.Hook = hook;
((IGlobeDisplayEvents_Event) m_globeHookHelper.GlobeDisplay).AfterDraw += new IGlobeDisplayEvents_AfterDrawEventHandler(OnAfterDraw);
…
}
[VB.NET]
'Class members.Private m_globeHookHelper As IGlobeHookHelper = NothingPublicOverridesSub OnCreate(ByVal hook AsObject)
'Initialize the hook helper.If m_globeHookHelper IsNothingThen
m_globeHookHelper = New GlobeHookHelper()
EndIf'Set the hook.
m_globeHookHelper.Hook = hook
AddHandler (CType(m_globeHookHelper.GlobeDisplay, IGlobeDisplayEvents_Events)).AfterDraw, AddressOf OnAfterDraw
...
EndSub
Wiring of the AfterDraw event is done against the GlobeHookHelper.GlobeDisplay property.
When you run the previous code, after awhile, the event stops and fires without any exception or warning. This is because internally, when you use the GlobeDisplay property of the GlobeHookHelper to wire the event, the hook helper gets a reference to the actual GlobeDisplay and calls AddRef() before passing it back to the client. The .NET Framework creates a local variable for the returning object and wires the event. However, once method OnCreate is ended, the local IGlobeDisplay variable that was created by the .NET Framework gets out of scope and is collected by the garbage collector at some point (meaning that it will eventually call the Release() method). Once this happens, events stop firing.
The correct way to program this situation is to keep a class member referencing the contained object and thus prevent the garbage collector from disposing of it. This way, you are guaranteed the event you are listening to continues to fire throughout the lifetime of your class (do not forget to unwire the event once you are done listening).
Eventually, your code looks something like the following example:
[C#]
//Class members.private IGlobeHookHelper m_globeHookHelper = null;
private IGlobeDisplay m_globeDisplay = null;
publicoverridevoid OnCreate(object hook)
{
//Initialize the hook helper.if (m_globeHookHelper == null)
m_globeHookHelper = new GlobeHookHelper();
//Set the hook.
m_globeHookHelper.Hook = hook;
//Get the GlobeDisplay from the hook helper.
m_globeDisplay = m_globeHookHelper.GlobeDisplay;
((IGlobeDisplayEvents_Event)m_globeDisplay).AfterDraw += new IGlobeDisplayEvents_AfterDrawEventHandler(OnAfterDraw);
…
}
[VB.NET]
'Class members.Private m_globeHookHelper As IGlobeHookHelper = NothingPrivate m_globeDisplay As IGlobeDisplay = NothingPublicOverridesSub OnCreate(ByVal hook AsObject)
'Initialize the hook helper.If m_globeHookHelper IsNothingThen
m_globeHookHelper = New GlobeHookHelper()
EndIf'Set the hook.
m_globeHookHelper.Hook = hook
'Get the GlobeDisplay from the hook helper.
m_globeDisplay = m_globeHookHelper.GlobeDisplay
AddHandler (CType(m_globeDisplay, IGlobeDisplayEvents_Events)).AfterDraw, AddressOf OnAfterDraw
...
EndSub