How to wire ArcObjects .NET events


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

Development licensing Deployment licensing
Engine Developer Kit Engine Runtime
ArcView ArcView

In this topic


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 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:
 

[C#]
delegate int SomeDelegate(string s, bool b); //A delegate declaration.
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

  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 once 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
Alternatively, let Visual Studio do the work so you don't have to add the delegate signature:
  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:
 
 
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;

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, 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;

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