How to wire ArcObjects .NET events


Summary
An event is the way a Windows application receives notification. In a Windows application, many events occur at a particular instant—for example, Mouse Move, Mouse Out, and Mouse Click. In .NET, you hook events through delegates, which are function pointers (that is, hold references to functions). This topic explains how to wire ArcObjects .NET events.


Working with events

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 another 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.

Defining 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, provide an event handler (an event handling method) that executes program logic in response to the event and registers 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:
[C#]
delegate int SomeDelegate(string s, bool b); //A delegate declaration.
[VB.NET]
Delegate Function SomeDelegate(ByVal s As String, ByVal b As Boolean) As Integer 'A delegate declaration.
To imply 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, provide an event handler (an event handling method) that executes program logic in response to the event and registers 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.

Listening to an ArcObjects event

The following are the required steps to listen to an ArcObjects event:
  1. Cast the relevant event interface (you can also do this in line). See the following code example:
[C#]
IGlobeDisplayEvents_Event globeDisplayEvents = (IGlobeDisplayEvents_Event)
    m_globeDisplay;
[VB.NET]
Dim globeDisplayEvents As IGlobeDisplayEvents_Event = CType(m_globeDisplay, IGlobeDisplayEvents_Event)
  1. Register the event handler method. See the following code example:
[C#]
globeDisplayEvents.AfterDraw += new IGlobeDisplayEvents_AfterDrawEventHandler
    (OnAfterDraw);
[VB.NET]
AddHandler globeDisplayEvents.AfterDraw, AddressOf OnAfterDraw
  1. Implement the event handler method specified by the signature of the delegate. See the following code example:
[C#]
private void OnAfterDraw(ISceneViewer pViewer)
{
    //Your event handler logic.
}
[VB.NET]
Private Sub OnAfterDraw(ByVal pViewer As ISceneViewer)
    'Your event handler logic.
End Sub
  1. Unwire the event from your application when you no longer need to listen to it. See the following code example:
[C#]
((IGlobeDisplayEvents_Event)m_globeDisplay).AfterDraw -= new
    IGlobeDisplayEvents_AfterDrawEventHandler(OnAfterDraw);
[VB.NET]
RemoveHandler (CType(m_globeDisplay, IGlobeDisplayEvents_Event)).AfterDraw, AddressOf OnAfterDraw

Using Visual Studio

Alternatively, let Visual Studio do the work so you do not have to add the delegate signature. See the following steps:
  1. Cast the relevant event interface.
  2. 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:


  • Visual Studio adds the event handler method and automatically shows the event handler implementation code.
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.

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, it is almost the obvious choice to use the GlobeHookHelper class, which allows your command to work in ArcGlobe. In this case, the GlobeHookHelper is a class member. See the following code example:
[C#]
//Class members.
private IGlobeHookHelper m_globeHookHelper = null;

public override void 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 = Nothing

Public Overrides Sub OnCreate(ByVal hook As Object)
'Initialize the hook helper.
If m_globeHookHelper Is Nothing Then
    m_globeHookHelper = New GlobeHookHelper()
End If

'Set the hook.
m_globeHookHelper.Hook = hook

AddHandler (CType(m_globeHookHelper.GlobeDisplay, IGlobeDisplayEvents_Events)).AfterDraw, AddressOf OnAfterDraw
...
End Sub
Wiring of the AfterDraw event is done against the GlobeHookHelper.GlobeDisplay property.
When you run the previous code example, eventually, 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 the OnCreate method ends , the local IGlobeDisplay variable that was created by the .NET Framework gets out of scope and is collected by the garbage collector (eventually calls the Release() method). When this happens, events stop firing.
The correct way to program this situation is to keep a class member referencing the contained object and prevent the garbage collector from disposing 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 resembles the following code example:
[C#]
//Class members.
private IGlobeHookHelper m_globeHookHelper = null;
private IGlobeDisplay m_globeDisplay = null;

public override void 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 = Nothing
Private m_globeDisplay As IGlobeDisplay = Nothing

Public Overrides Sub OnCreate(ByVal hook As Object)
'Initialize the hook helper.
If m_globeHookHelper Is Nothing Then
    m_globeHookHelper = New GlobeHookHelper()
End If

'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
...
End Sub






Development licensing Deployment licensing
Engine Developer Kit Engine
ArcGIS for Desktop Basic ArcGIS for Desktop Basic