Key Ideas
[Part 4 of 12]


In this part, we looked at three issues: editing, inter-addin communication, and event-based programming.

  1. Editing isn't a especially complicated in itslef, but involves quite a lot of getting hold of the right kinds of object to work with, which isn't always as simple as it sounds in Arc. Arc quite often has proxy objects in replacement for the objects you're expecting. Quite often you won't notice the difference, but occasionally it does 'throw a spanner into the works'. Here is the code (minus extra casts and try-catch blocks) for getting hold of the current data and adding a column of data to it...

    @Override
    public void init(IApplication app){

       // Get gateway objects.
       IMxDocument mxDoc = (IMxDocument)app.getDocument();
       IMap map = mxDoc.getFocusMap();
       IGeoFeatureLayer iGeoFeaturelayer = (IGeoFeatureLayer) map.getLayer(0);


       // Get workspace.
       IFeatureDatasetProxy ifdp = iGeoFeaturelayer.getFeatureClass().getFeatureDataset();
       IWorkspaceProxy iwp = ifdp.getWorkspace();
       IWorkspaceFactory wf = iwp.getWorkspaceFactory();
       IWorkspace ws = wf.openFromFile(iwp.getPathName(), 0);
       IFeatureWorkspace ifw = (IFeatureWorkspace)iWorkspace;


       // Set up an editing session.
       IWorkspaceEdit iwe = (IWorkspaceEdit) ifw;
       iwe.startEditing(true);
       iwe.startEditOperation();


       // Set up a new column.
       IField field = new Field();
       IFieldEdit fieldEdit = field;
       fieldEdit.setName("Population");
       fieldEdit.setType(esriFieldType.esriFieldTypeInteger);


       // Add new column and find location index.
       IFeatureClass fClass = ((FeatureLayer)iGeoFeaturelayer).getFeatureClass();
       IFields fields = fClass.getFields();
       fClass.addField(field);
       int index = fClass.findField("Population");


       // Get all current features.
       IFeatureCursor fCursor = fClass.IFeatureClass_update(null,false);
       IFeature feature = pFCursor.nextFeature();


       // Add value to column and fix.
       while(feature != null) {
          feature.setValue(index,10000);
          fCursor.updateFeature(feature);
          feature = pFCursor.nextFeature();
       }


       // End editing session and release resources.
       Cleaner.release(cursor);
       iwe.stopEditOperation();
       iwe.stopEditing(true);

    }

  2. Secondly we looked at inter-addin communication using the singleton pattern. We saw that the singleton pattern is useful when we want one, and only one, static object made from a class. The singleton pattern allows us to access this object, but also prevents anyone else from making an object of that type. Here's the classic singleton:

    class Singleton {
       private static Singleton single = null;

       private Singleton() {}

       public static Singleton getInstance() {
          if (single = null) {
             single = new Singleton();
          }
          return single;
       }
       // other methods.
    }

    The constructor is present but set to private so no one outside the class can use it. This means no one else can make an object of this class. However, we can get hold of an object made from the class by calling the getInstance() method. This makes an object if necessary, and returns the static Singleton-type object for use. This method is also set as static, meaning it can be called directly from the class.

    In Arc, the plugin GUI components are held as static objects inside the system (this stops people creating more than one object per addin class). This complicates matters if we want to talk to them, as we have no idea where these objects are created or which class we might ask in order to access them. Fortunately, the singleton pattern comes to our aid. As we're writing the addin class, and we know it will be a static object, we can build it as a singleton, allowing us to use the class' static getInstance() method to get hold of the object wherever it is. Here's the adaptation of the singleton pattern we'd use:

    class AddIn{
       private static AddIn addIn = null;

       public AddIn() {
          addIn = this; // Grab our static
       } // variable as Arc makes it.

       public static AddIn getInstance() {
          return addIn;
       }
       // other methods.
    }

    Note the change in the constructor to allow us to grab the static object made from this class (this) as its created. Of course, we could do other stuff in the constructor as well if we wanted.

    These additions to our addin class then allows us to get hold of the static object created by Arc from anywhere in our code by calling:

    AddIn addIn = AddIn.getInstance();

  3. Finally, we looked at event-based programming in Arc. We saw that some objects have built in methods for adding listener class objects:

    MxDocument: addIDocumentEventsDispListener
    Map: addIActiveViewEventsListener
    Map: addIMapEventsListener
    FeatureLayer: addIFeatureLayerSelectionEventsListener
    TIN/Raster/Feature/NetworkLayer: addILayerEventsListener

    and we saw that there were adapter classes that were already set up to use as such listeners. You can extend these classes as normal, or you can embed them directly into the object you are listening to by writing the code directly into the method call as an anonymous inner class:

    ((MxDocument)mxDoc).addIDocumentEventsListener(
       new IDocumentEventsAdapter(){

          @Override
          public void newDocument (IDocumentEventsNewDocumentEvent e){
             // Do something when new document.
          }

          @Override
          public boolean beforeCloseDocument (IDocumentEventsBeforeCloseDocumentEvent e){
             // Do something when closing document.
          }

          }
       );

    We also saw that you can force the user to say whether they'd like to save any changes to a document when they close it, thus:

    IDocumentDirty doc = (IDocumentDirty)app.getDocument();
    doc.setDirty();


You can find an example of some code that uses all of these approaches on the Full Examples page. The BoundaryTypeAnalysis example uses these ideas in multiple places, but a good starting point are the BoundaryTypeAnalysis and DisplayType classes.

Communication:
In general with Arc, you can't guarantee the order in which addins will be added, so it is hard to call on one addin to initialise the system for the others. What we do here is have an addin extension (BoundaryTypeAnalysis) which acts as a central processor for the other addins. When it loads, it works out various properties, such as the workspace being used. As there is no guarantee this loads before another addin is initialised, we wait until the user clicks on the other addin before requesting data from BoundaryTypeAnalysis (rather than, for example, calling BoundaryTypeAnalysis from the init method of the other addins). Unless an addin is doing something very complicated, you're pretty much guaranteed that all the addins will be initialised prior to the user being able to click on one of them, so calling BoundaryTypeAnalysis from the onClick method should be reasonably safe. BoundaryTypeAnalysis is set up as a singleton, so the onClick method of the button addins, including DisplayType, can call the BoundaryTypeAnalysis object produced by Arc by using:

BoundaryTypeAnalyst extension = BoundaryTypeAnalyst.getInstance();
Object[] variables = extension.getVariables();

Event-based programming:
The BoundaryTypeAnalysis extension class uses an anonymous inner class (in its init method) to cope with a new map being opened. Note that this is embedded in the document, so can occur at some later point in time, but also that this event listener enacts when Arc is opened with a map, as the addins are loaded before the map and this code is therefore up and running before this.

Editing:
Finally, DisplayType does a wide variety of editing jobs, some within EditOperation blocks as it is dealing with a Topology.