How to write a TransformEvents class


With ArcGIS Engine you can listen for events on the ArcGIS controls such as the PageLayoutControl. For example, you can implement custom application responses to mouse clicks, keyboard input, and view refreshes. However, there are some events that are not handled by the PageLayoutControlEvents. In this scenario, you will write some simple code to interact with such an event. Here, you will look at when the extent of the map within the data frame changes. To receive that information, you will use the ITransformEvents interface of the PageLayoutControl’s focus map. Implementing a class--TransformEvents--that extends ITransformEvents accomplishes this.
This document will take you through writing an event class for a PageLayoutControl, which will cause a message to be printed to the terminal window each time the map's extent is updated in the PageLayoutControl.
Control events should not be used in the place of a custom command or tool. For example, to write a custom zoom, you should write a custom tool. If instead you listen for mouse clicks on the control, you will always have to use your zoom with the mouse and will not be able to turn it off, as you can with a custom tool. For further details on custom commands and tools, see How to create a custom tool using AoToolBase and How to create a custom command using AoCommandBase.
Since events are used to interact with ArcGIS controls, they only work on Solaris and Linux. If you want to use your application on Windows, you will need to use a different API such as Visual C++.

TransformEvents example: New Map Extent message box

When you change the extent of a map in a PageLayoutControl, these events will simply display a message to the terminal window informing you of the change in extent.
Creating the Transform Event class
  1. Create a new file--TransformEvents.h--in your text editor.
  2. In TransformEvents.h, create a new class, TransformEvents. You will need to include iostream to print the message and ArcSDK.h for the Engine.
[Motif C++]
#ifndef __TRANSFORMEVENTS_H_
  #define __TRANSFORMEVENTS_H_

  #include <iostream>

  // ArcObjects Headers
  // Engine
  #include <ArcSDK.h>

  class TransformEvents{}
  ;

#endif // __TRANSFORMEVENTS_H_
  1. Update the TransformEvents class so that it inherits from ITransformEvents. Add the functions from ITransformEvents, which you need to implement, as well as the definition for IUnknown.
[Motif C++]
class TransformEvents: public ITransformEvents
{
  public:
    // IUnknown
    IUNKNOWN_METHOD_DEFS 

    // ITransformEvents
    HRESULT BoundsUpdated(IDisplayTransformation *sender);
    HRESULT DeviceFrameUpdated(IDisplayTransformation *sender, VARIANT_BOOL
      sizeChanged);
    HRESULT ResolutionUpdated(IDisplayTransformation *sender);
    HRESULT RotationUpdated(IDisplayTransformation *sender);
    HRESULT UnitsUpdated(IDisplayTransformation *sender);
    HRESULT VisibleBoundsUpdated(IDisplayTransformation *sender, VARIANT_BOOL
      sizeChanged);
};

Adding code to the members of ITransformEvents

  1. Create a new file in your text editor: TransformEvents.cpp.
  2. Include TransformEvents.h.
[Motif C++]
#include "TransformEvents.h"
  1. Following that include stub out all the functions that you placed into the header file.
[Motif C++]
HRESULT TransformEvents::BoundsUpdated(IDisplayTransformation *sender)
{
  return E_NOTIMPL;
}

HRESULT TransformEvents::DeviceFrameUpdated(IDisplayTransformation *sender,
  VARIANT_BOOL sizeChanged)
{
  return E_NOTIMPL;
}

HRESULT TransformEvents::ResolutionUpdated(IDisplayTransformation *sender)
{
  return E_NOTIMPL;
}

HRESULT TransformEvents::RotationUpdated(IDisplayTransformation *sender)
{
  return E_NOTIMPL;
}

HRESULT TransformEvents::UnitsUpdated(IDisplayTransformation *sender)
{
  return E_NOTIMPL;
}

HRESULT TransformEvents::VisibleBoundsUpdated(IDisplayTransformation *sender,
  VARIANT_BOOL sizeChanged)
{
  return E_NOTIMPL;
}
  1. Now begin implementing the functions you will need for your event actions. Since you just want to display a message when the map's extent is updated, you only need to implement VisibleBoundsUpdated.
[Motif C++]
HRESULT TransformEvents::VisibleBoundsUpdated(IDisplayTransformation *sender,
  VARIANT_BOOL sizeChanged)
{
  std::cout << "New Visible Map Extent" << std::endl;
  return S_OK;
}

Using the Events with a PageLayoutControl

Your next step is to listen for your events from an application. For the point of this walkthrough, it is assumed that you have a working application with a PageLayoutControl. If you do not have one, you can use one of the sample applications. In particular, try PageLayout. To use this sample, copy its files (either the Motif or GTK version) to your folder with the files you have created in this walkthrough. For this walkthrough, you will use the PageLayout sample given above as the base application.
For additional details on the calls used here, see the document on Event Handling on ArcGIS Controls.
  1. Include the header file for your class in the header file for the control application—in this case, PageLayout.h.
[Motif C++]
#include "TransformEvents.h"
  1. In PageLayout.cpp, create a global variable of your class type, along with one for IEventListenerHelperPtr. They will need to be deleted when the application is closed; making them globals will make that easier to do.
[Motif C++]
TransformEvents *g_transformEvents;
IEventListenerHelperPtr g_ipTransformEventHelper;
  1. Listen for the Transform events from within main. This needs to be done before the top-level widget is realized.
[Motif C++]
// Listen for TransformEvents
g_transformEvents = new TransformEvents();
g_ipTransformEventHelper.CreateInstance(CLSID_TransformEventsListener);
g_ipTransformEventHelper->Startup(static_cast < TransformEvents * > 
  (g_transformEvents));

// Start the application running
XtRealizeWidget(g_topLevel);
  1. Since the data that you want to listen for can change within the PageLayoutControl, you need to advise on the events each time that data is changed. To do this, you need to call AdviseEvents from within the PageLayoutControl's events. Details will not be given on the creation of the PageLayoutControlEvents class, as those details are already in Writing a MapControl Event class. Instead, the base code will be provided for you, and only details relating to the TransformEvents class will be discussed.
    1. Create a new file—PageLayoutControlEvents.h—in your text editor. Copy the following code into that file. Notice that extern statements are provided to give access to both the PageLayoutControl and the TransformEvents listener.
[Motif C++]
#ifndef __PAGELAYOUTCONTROLEVENTS_H_
  #define __PAGELAYOUTCONTROLEVENTS_H_

  // ArcObjects Headers
  // Engine
  #include <ArcSDK.h>
  // Controls
  #include <Ao/AoControls.h>

  extern IPageLayoutControlPtr g_ipPageLayoutControl;
  extern IEventListenerHelperPtr g_ipTransformEventHelper;

  class PageLayoutControlEvents: public IPageLayoutControlEventsHelper
  {
    public:
      // IUnknown
      IUNKNOWN_METHOD_DEFS 

      // IPageLayoutControlEvents
      void OnAfterDraw(VARIANT display, long viewDrawPhase);
      void OnAfterScreenDraw(long hdc);
      void OnBeforeScreenDraw(long hdc);
      void OnDoubleClick(long button, long shift, long x, long y, double mapX,
        double mapY);
      void OnExtentUpdated(VARIANT displayTransformation, VARIANT_BOOL
        sizeChanged, VARIANT newEnvelope);
      void OnFullExtentUpdated(VARIANT displayTransformation, VARIANT
        newEnvelope);
      void OnKeyDown(long keyCode, long shift);
      void OnKeyUp(long keyCode, long shift);
      void OnFocusMapChanged();
      void OnPageLayoutReplaced(VARIANT newPageLayout);
      void OnPageSizeChanged();
      void OnMouseDown(long button, long shift, long x, long y, double mapX,
        double mapY);
      void OnMouseMove(long button, long shift, long x, long y, double mapX,
        double mapY);
      void OnMouseUp(long button, long shift, long x, long y, double mapX,
        double mapY);
      void OnOleDrop(esriControlsDropAction dropAction, VARIANT
        dataObjectHelper, long *effect, long button, long shift, long x, long y)
        ;
      void OnSelectionChanged();
      void OnViewRefreshed(VARIANT ActiveView, long viewDrawPhase, VARIANT
        layerOrElement, VARIANT envelope);
  };

#endif // __PAGELAYOUTCONTROLEVENTS_H_
    1. Create another new file, PageLayoutControlEvents.cpp, and stub out the functions:
[Motif C++]
#include "PageLayoutControlEvents.h"
void PageLayoutControlEvents::OnAfterDraw(VARIANT display, long viewDrawPhase){}

void PageLayoutControlEvents::OnAfterScreenDraw(long hdc){}

void PageLayoutControlEvents::OnBeforeScreenDraw(long hdc){}

void PageLayoutControlEvents::OnDoubleClick(long button, long shift, long x,
  long y, double mapX, double mapY){}

void PageLayoutControlEvents::OnExtentUpdated(VARIANT displayTransformation,
  VARIANT_BOOL sizeChanged, VARIANT newEnvelope){}

void PageLayoutControlEvents::OnFullExtentUpdated(VARIANT displayTransformation,
  VARIANT newEnvelope){}

void PageLayoutControlEvents::OnKeyDown(long keyCode, long shift){}

void PageLayoutControlEvents::OnKeyUp(long keyCode, long shift){}

void PageLayoutControlEvents::OnFocusMapChanged(){}

void PageLayoutControlEvents::OnPageLayoutReplaced(VARIANT newPageLayout){}

void PageLayoutControlEvents::OnPageSizeChanged(){}

void PageLayoutControlEvents::OnMouseDown(long button, long shift, long x, long
  y, double mapX, double mapY){}

void PageLayoutControlEvents::OnMouseMove(long button, long shift, long x, long
  y, double mapX, double mapY){}

void PageLayoutControlEvents::OnMouseUp(long button, long shift, long x, long y,
  double mapX, double mapY){}

void PageLayoutControlEvents::OnOleDrop(esriControlsDropAction dropAction,
  VARIANT dataObjectHelper, long *effect, long button, long shift, long x, long
  y){}

void PageLayoutControlEvents::OnSelectionChanged(){}

void PageLayoutControlEvents::OnViewRefreshed(VARIANT ActiveView, long
  viewDrawPhase, VARIANT layerOrElement, VARIANT envelope){}
    1. Next you need to trap for ITransformEvents. This way, when the data within the PageLayoutControl changes, the TransformEvents will still be listening to the correct data. You will do this in OnPageLayoutReplaced.
[Motif C++]
void PageLayoutControlEvents::OnPageLayoutReplaced(VARIANT newPageLayout)
{
  // Get the focus map's active view
  IActiveViewPtr ipActiveView;
  g_ipPageLayoutControl->get_ActiveView(&ipActiveView);
  IMapPtr ipFocusMap;
  ipActiveView->get_FocusMap(&ipFocusMap);
  IActiveViewPtr ipMapActiveView(ipFocusMap);

  // Trap focus map's ITransformEvents 
  IScreenDisplayPtr ipScreenDisp;
  ipMapActiveView->get_ScreenDisplay(&ipScreenDisp);
  IDisplayTransformationPtr ipDisplayTrans;
  ipScreenDisp->get_DisplayTransformation(&ipDisplayTrans);
  CComBSTR bsGUID;
  ::StringFromIID(IID_ITransformEvents, &bsGUID);
  IUIDPtr ipUID(CLSID_UID);
  ipUID->put_Value(CComVariant(bsGUID));
  g_ipTransformEventHelper->AdviseEvents(ipDisplayTrans, ipUID);
}
    1. Include the header file in the header file for the control application--in this case, PageLayout.h.
[Motif C++]
#include "PageLayoutControlEvents.h"
    1. In PageLayout.cpp, create a global variable of your class type, along with one for IEventListenerHelperPtr. They will need to be deleted when the application is closed; making them globals will make that easier to do.
 
[Motif C++]
PageLayoutControlEvents *g_pageEvents;
IEventListenerHelperPtr g_ipPageLayoutControlEventHelper;
    1. Listen for the PageLayoutControlEvents in main before listening for the transform events.
[Motif C++]
// Listen for PageLayoutControlEvents
g_pageEvents = new PageLayoutControlEvents();
g_ipPageLayoutControlEventHelper.CreateInstance
  (CLSID_PageLayoutControlEventsListener);
g_ipPageLayoutControlEventHelper->Startup(static_cast <
  IPageLayoutControlEventsHelper * > (g_pageEvents));
g_ipPageLayoutControlEventHelper->AdviseEvents(g_ipPageLayoutControl, NULL);
    1. Stop listening for the events when the application closes. The following lines of code should be placed at the start of the CloseAppCallback function.
[Motif C++]
// Event and globals cleanup
g_ipPageLayoutControlEventHelper->UnadviseEvents();
g_ipPageLayoutControlEventHelper->Shutdown();
g_ipPageLayoutControlEventHelper = 0;
delete g_pageEvents;
  1. Make sure to also stop listening for the transform events when your application closes. These lines of code will follow the ones in the previous step.
[Motif C++]
g_ipTransformEventHelper->UnadviseEvents();
g_ipTransformEventHelper->Shutdown();
g_ipTransformEventHelper = 0;
delete g_transformEvents;
  1. To update the extent of the data in the PageLayoutControl, you can use the MapZoomIn command, so add it to the PageLayoutControl's toolbar in PageLayout.cpp (at the end of the AddToolbarItems function at the end of the file).
[Motif C++]
varTool = L "esriControlCommands.ControlsMapZoomInTool";
g_ipToolbarControl->AddItem(varTool, 0,  - 1, VARIANT_FALSE, 0,
  esriCommandStyleIconOnly, &itemIndex);

Trying it out

Start with the appropriate makefile provided with the PageLayout sample: either Makefile.SolarisMotif, Makefile.LinuxMotif, Makefile.SolarisGTK, or Makefile.LinuxGTK. You will need to update the makefile to get this command to work. First add PageLayoutControlEvents.cpp and TransformEvents.cpp as sources:
CXXSOURCES = PageLayout.cpp PageLayoutControlEvents.cpp TransformEvents.cpp
 
Then add PageLayoutControlEvents.h to the dependencies list for PageLayout, and write its dependencies list:
 
PageLayout.o: PageLayout.cpp PageLayout.h PageLayoutControlEvents.h
 $(CXX) $(CXXFLAGS) -c -o PageLayout.o PageLayout.cpp
 
PageLayoutControlEvents.o: PageLayoutControlEvents.cpp PageLayoutControlEvents.h
 $(CXX) $(CXXFLAGS) -c -o PageLayoutControlEvents.o PageLayoutControlEvents.cpp
 
Next add TransformEvents.h to the dependencies list for PageLayoutControlEvents and PageLayout, and write its dependencies list:
 
PageLayout.o: PageLayout.cpp PageLayout.h PageLayoutControlEvents.h TransformEvents.h
 $(CXX) $(CXXFLAGS) -c -o PageLayout.o PageLayout.cpp
 
PageLayoutControlEvents.o: PageLayoutControlEvents.cpp PageLayoutControlEvents.h TransformEvents.h
 $(CXX) $(CXXFLAGS) -c -o PageLayoutControlEvents.o PageLayoutControlEvents.cpp
 
TransformEvents.o: TransformEvents.cpp TransformEvents.h
 $(CXX) $(CXXFLAGS) -c -o TransformEvents.o TransformEvents.cpp
 
Now that your makefile is set, compile and run your application. Load data into the PageLayoutControl and zoom in on it. Your event will be activated and a message box displayed.