It’s Such a Drag

So, in my day job as a GIS developer, I’ve been making an addin for ArcGIS. Nothing fancy, just a data loader to make it easier for our users to pick out the exact item they want from the many datasets we provide. Choose a few categories, select some items from the list, click the load button, done! But what if you want to just drag the items onto the map? ArcObjects doesn’t appear to offer drag and drop straight out of the box, but it is possible. If you’re also developing ArcGIS addins, I’ll save you some time and explain how it is done.

Implementing drag and drop in .NET is actually pretty straightforward. There are two parts to the transaction – the drag source (the control you are dragging from) and the drop target (the control you are dragging to). All you have to do is add an ItemDrag event to your source control (which calls DoDragDrop to start the drag action), then enable the AllowDrop property on your target control, and add the DragDrop event to handle the dropped item. There are some other events that can be handled, including DragEnter, DragLeave, DragOver, GiveFeedback, and QueryContinueDrag, but that is the basic procedure.

When developing addins for ArcGIS, the drop target is already handled for you – the main window in ArcMap is listening for drop events, such as dropping mxds or datasets from the Catalog window or from ArcCatalog. All you have to do is package your drag source into the correct format and you’re good to go. It couldn’t be easier, right?

Well, it all depends on being able to format the dragged item correctly. As this post from The Sandpit states, ArcMap accepts two kinds of input. When dragging from ArcCatalog, an ESRI Names object is created, which is an enumerator that provides the names of the datasets to be imported. Dragging from ArcMap on the other hand, creates an ESRI Layers object, which is a collection of layers and tables. Apparently setting the drag source to an ESRI Layers object is not supported, and in my addin we are just loading datasets from the database by name, so it looks like I need to use ESRI Names.

Incidentally, if you’re doing any development with clipboard objects, I found two helpful applications. Clipboard Viewer shows whatever is currently on the clipboard and DataObjectViewer shows whatever has just been dragged onto it. You can use either of these to drag a selection of layers or datasets from ArcMap or ArcCatalog to confirm which object type is being used.

So, the example code on the Sandpit post went some way towards constructing an ESRI Names object and setting it as the drag source, but it relied on a third-party dll which does not work on recent versions of ArcGIS. However, according to this Geonet post, you can instead use a MemoryStream to pass the IEnumName to a DataObject, then use the DataObject as the drag source.

I did this, and it worked! The dataset name dropped onto the map and was loaded as a layer. However, the debugger showed a log of exceptions with the message, “A first chance exception of type ‘System.Runtime.InteropServices.COMException’ occurred in System.Windows.Forms.dll”. This is not ideal – I always try to get my code to run as cleanly as possible – so I took a closer look at the DataObjectViewer page above, as it goes into a lot of detail about DataObjects and how to construct one from scratch. I managed to do this, but it didn’t make any difference unfortunately. The consensus among developers on Stack Overflow seems to be that first chance exceptions can be ignored as users don’t see them. Hey ho, as long as it works, right?

The full code is shown below. lvDatasets is the ListView that contains the dataset names to be dragged. To keep this example simple, it has been set up so all the items added to it are IDatasetNames. It may be more useful though, to create a custom class to hold the IDatasetName, the alias, and other related information.

    using System;
    using System.IO;
    using System.Windows.Forms;
    using ESRI.ArcGIS.Carto;
    using ESRI.ArcGIS.esriSystem;
    using ESRI.ArcGIS.Geodatabase;
    using DataObject = System.Windows.Forms.DataObject;

    private void LoadSelectedLayers()
    {
        // declare name objects to method scope and populate if dragging
        INameFactory nameFactory = null;
        IEnumName enumName = null;
        IEnumNameEdit enumNameEdit = null;
        DragDropEffects dropStatus = DragDropEffects.All;
        nameFactory = new NameFactoryClass();
        enumName = new NamesEnumeratorClass();
        enumNameEdit = (IEnumNameEdit)enumName;

        // iterate backwards over selected items so they are added in reverse order (so TOC order is same as list view order)
        for (int i = lvDatasets.SelectedItems.Count - 1; i >= 0; i--)
        {
            IDatasetName datasetName = (IDatasetName)lvDatasets.SelectedItems[i];
            IName tempName = (IName)datasetName;
            enumNameEdit.Add(tempName);
        }

        // Create a new data object.
        DataObject myDataObject = new DataObject();

        // Add the names to the DataObject.
        MemoryStream memoryStream = new MemoryStream((byte[])nameFactory.PackageNames(enumName));
        myDataObject.SetData("ESRI Names", memoryStream);

        // set the data as a drop source
        dropStatus = lvDatasets.DoDragDrop(myDataObject, DragDropEffects.All);

        if (dropStatus != DragDropEffects.None)
        {
            // reset list view
            ClearSelectedItems();
        }
    }

 

Could not load file or assembly

So, I changed jobs recently* and have been mostly brushing off my C# skills to write ArcGIS Addins. This has been largely frustrating, as Esri and Microsoft have joined forces to make things as difficult as possible. For example:

 

 

This error message was particularly annoying, as it is fairly ambiguous. The full text isn’t much more enlightening:

Could not load file or assembly xxxx or one of its dependencies. The system cannot find the file specified. C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\TeamTest\Microsoft.TeamTest.targets

If only there was a way for the system to tell my which dependency it was having a problem with. Never mind, I’ll go through them all one by one and get there eventually.

For the record, the culprit was ESRI.ArcGIS.Desktop.AddIns, and the solution is to change the CopyLocal flag to true. Makes sense that if the file isn’t copied locally, it can’t be found.

The question is, why was CopyLocal set to false in the first place? I’m sure it was true last time I looked. Not only that, but this bug has come back a few times. At least I know what the fix is this time. I still didn’t know why that flag kept changing, even though I’ve done many searches.

Finally today, I worked out what was going on – the CopyLocal flag is reset every time you update the file Config.esriaddinx. Why should it do this? Who knows, but at least I know the solution now, and I’ve written it down, so when I start searching for the answer in three months time, I’ll find it!

*Actually four months ago, but geologically speaking, that’s just a blink of an eye.