8

I'm creating a utility to run in ArcGIS for Desktop using ArcObjects (9.3.1 SDK) and C#.Net. My prototype involves a toolbar with two comboboxes and a tool. The first combo selects a layer in the TOC, and the second selects a field from the selected layer. The tool will be used to interact with the map.

Basically I want to select a layer, select a valid field, then click a feature in the map and get its value for the chosen field. Here's an image of the toolbar, if it helps:

enter image description here

[question almost entirely re-worded from here down]

The problem I am having is passing state between the native COM UI parts and my custom .Net controls. For example, I want to catch the DropDownClosed event on the Layer combobox, assemble a valid list of columns relative to that layer, then apply the list of field names (via IFields) to the Fields combobox.

After applying some the initial comments by RagiYaserBurham and blah238, and merging them with details from this page, the following DropDownClosed event handler does go from the combobox back to the toolbar (ICommandBar), but I don't understand how to cast from ICommandItem back to my implementation of the Fields combobox in a UserControl:

private void layerSelectCBO_DropDownClosed(object sender, EventArgs e)
{
    _completionNotify.SetComplete();

    string layerName = (sender as ComboBox).SelectedItem as string;

    // These two lines are a combination of initial commenter suggestions.
    ICommandItem toolbar = _iApp.Document.CommandBars.Find("ArcProject.UI.AngryToolbar", false, false);
    ICommandItem fieldsItem = (toolbar as ICommandBar).Find("ArcProject.UI.FieldSelectUC", false);

}

So.. now that I'm here.. how do I cast fieldsItem to FieldSelectUC ?

[The Solution]

As blah238 suggested, I tried casting ICommandItem.Command to my custom UserControl implementation and that did the trick.

First, I had to add a public accessor to my FieldSelectUC UserControl to return a reference to its ComboBox; that simple accessor looks like this:

// fieldSelectCBO is the name of the combobox control in the design view..
public ComboBox FieldsComboBox { get { return fieldSelectCBO; } }

With that modification in place, here's a DropDownClosed event handler that will populate the Fields combobox with all the fields of the selected layer:

private void layerSelectCBO_DropDownClosed(object sender, EventArgs e)
{
    _completionNotify.SetComplete();

    string layerName = (sender as ComboBox).SelectedItem as string;

    // get the toolbar..
    ICommandItem toolbar = _iApp.Document.CommandBars.Find("ArcProject.UI.AngryToolbar", false, false);

    // get my fields combo by way of CommandItem.Command..
    ICommandItem fieldsCI = (toolbar as ICommandBar).Find("ArcProject.UI.FieldSelectUC", false);
    FieldSelectUC fieldsUC = fieldsCI.Command as FieldSelectUC;
    ComboBox fieldsComboBox = fieldsUC.FieldsComboBox;

    // get the fields for the selected layer..
    IFields fields = null;
    int layerCount = _iDoc.FocusMap.LayerCount;
    int i;
    for (i = 0; i < layerCount; i++)
    {
        if (_iDoc.FocusMap.get_Layer(i).Name == layerName)
        {
            if (_iDoc.FocusMap.get_Layer(i) is FeatureLayer)
            {
                fields = (_iDoc.FocusMap.get_Layer(i) as FeatureLayer).FeatureClass.Fields;
            }
        }
    }

    // Build a list of field names for the combobox items..
    List<string> fieldNameList = new List<string>();
    if (fields != null)
    {
        int fieldCount = fields.FieldCount;
        int j;
        for (j = 0; j < fieldCount; j++)
        {
            string oneFieldName = fields.get_Field(j).Name;
            fieldNameList.Add(oneFieldName);
        }
    }

    // Populate the combobox items..  
    if (fieldNameList.Count > 0)
    {
        fieldsComboBox.Items.Clear();

        foreach (string fieldName in fieldNameList)
        {
            fieldsComboBox.Items.Add(fieldName);
        }

        fieldsComboBox.SelectedItem = fieldsComboBox.Items[0];
    }
    else
    {
        fieldsComboBox.Items.Add("Error: No fields!");
    }
}

This is still a dirty testbed (hence AngryToolbar). But the solution shows how to start from an extended UserControl that implements ICommand and IToolControl and drill back down to a .Net component. I really appreciate the assistance of everyone who offered suggestions. Thanks so much. :)

4
  • How about making your layerName variable public static scope?
    – artwork21
    Mar 12, 2012 at 17:12
  • @artwork21, that sounds good, but I'm still not sure how to access the instance of either combobox from its counterpart on the toolbar? Know what I mean? I suspect it's a simple fundamental thing I'm just not aware of.
    – elrobis
    Mar 12, 2012 at 17:29
  • This sounds like a different question. I'm not clear why you need to do that. It seems to me the Fields combobox should be cleared and repopulated based on the Layer combo. The Layer combobox should be populated based on the document event listener. Mar 12, 2012 at 17:44
  • @RichWawrzonek that is exactly right. But I'm not sure how to get to the existing instance of the Fields combo from the Layers combo.. For that matter, the tool needs to read both of their values.
    – elrobis
    Mar 12, 2012 at 17:50

3 Answers 3

4

As I understand it, you have two .NET ComboBoxes on a UserControl that implements ICommand and IToolControl, and you want to get a reference to one of the combo boxes from the other. As long as they are in the same scope you should just be able to refer to them by their variable names (check your UserControl designer for the names of your controls).

If the two combo boxes are on separate UserControls, then try casting ICommandItem.Command to your other UserControl.

See this sample in the 9.3 help for some examples: Recently used files - Command, MultiItem, and ToolControl

Also here is an ESRI forum post discussing this issue: http://forums.esri.com/Thread.asp?c=93&f=993&t=170088

3
  • Bingo. This line did the trick to get from the instance of ICommandItem back into the UserControl class I implemented: FieldSelectUC fs = fieldsItem.Command as FieldSelectUC; I can now see all it's props in the debugger. Tremendous thanks to you.
    – elrobis
    Mar 12, 2012 at 23:11
  • Hooray! I've been using add-ins exclusvely for a while so I had to dig through some old stuff to remember how it all worked :) This kind of thing is much easier (though admittedly less flexible) with add-ins in 10 since there is a specific ComboBox type and you can just refer to other add-in components with static variables and methods.
    – blah238
    Mar 12, 2012 at 23:16
  • 1
    Yup, it definitely seemed easier through the new add-ins. In researching this I found add-ins this and add-ins that but they just weren't available to my implementation. The GraphicsLayerToolControl example in the .Net help (on my system that address is C:\Program Files (x86)\ArcGIS\DeveloperKit\SamplesNET\Desktop\GraphicsLayerToolControl\CSharp\GraphicsLayerToolControl2008.sln) that helped me get going with the UserControl and events stuff, but I just couldn't figure out how to punch a hole through to .Net from the COM control. I can't exaggerate how thankful I am. Sincerely.
    – elrobis
    Mar 12, 2012 at 23:31
2

Whenever I do this sort of thing I store the layer and field names in a static property set contained in the toolbar. Then I use a document event handler to check if layers are added/removed from ArcMap or if the document is changed. The layer and field properties get updated whenever they are changed in the drop-down by the user. If the layer is removed from ArcMap or the document is closed they are reset to null. Then you can just check for null values before your program runs.

Get a reference to the combobox through the ICommandItem interface:

ICommandItem toolbar = _iApp.Document.CommandBars.Find("ArcProject.UI.AngryToolbar", false, false);
ICommandItem fieldsItem = (toolbar as ICommandBar).Find("ArcProject.UI.FieldSelectUC", false); IComboBox cbo = (IComboBox) fieldsItem; //Needs a reference to ESRI.ArcGIS.SystemUI;

8
  • +1 you make it sound so easy. :) But the problem I have is accessing the properties of an already instantiated control from the point of view of any other control. I like your idea of putting their shared properties on the toolbar, could you update your answer to show how I can actually read the properties of the containing toolbar from the point of view of one of the comboboxes? 'Cos that's basically what I'm after. I already know how to use document events to listen for changes to the TOC, so I don't need help with that aspect. Thanks for your response.
    – elrobis
    Mar 12, 2012 at 17:46
  • @elrobis You can also put the shared state in the ICommand itself (since there will always only be one instance) and then have the controls get a handle to the command through help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/… Mar 12, 2012 at 18:34
  • Ragi is right. Since you are using COM toolbar you can just pass the uid of your combobox to the ICommandBar.Find method to get a reference. His link explains it all. Mar 12, 2012 at 18:40
  • I like Ragi's idea too. There is something fundamental I am missing, though. For instance, this line returns a null toolbar (where this is a UserControl with label and combobox): ICommandBar toolbar = this.Parent as ICommandBar; It's this sort of fundamental UI object traversal that is killing me. I don't know how to get back to the toolbar to implement either of your suggestions. (And actually i like the idea of these vars on the toolbar a little better. I'd probably do it by adding a public getter to the toolbar that applies Ragi's idea). Thanks for your continued help.
    – elrobis
    Mar 12, 2012 at 18:45
  • 2
    I don't think this.Parent is valid for COM interfaces -- that is a .NET/Windows Forms concept. You don't want to "traverse the UI", you want to access your ICommands by their IDs.
    – blah238
    Mar 12, 2012 at 19:15
1

I had a similar problem with a custom tool. I have a custom form which gets opened by a button on an AddIn-Toolbar in ArcGis 10.x. On this form there is a button that should retreive the coordinates of a click in the map, snapping included. I could start the tool and handle the click in the map, but I could not get the value back to my form, because the cast to the custom-tool always failed. The solution was to use AddIn from ESRI.ArcGIS.Desktop.AddIns. With this it was easy to get access to every property and methode of my custom tool. ESRIs documentation resides here: http://resources.arcgis.com/en/help/arcobjects-net/conceptualhelp/index.html#/Add_in_coding_patterns/0001000000zz000000/

Here is the code-snippet from the OnClick-Event of the button on the custom form:

//DESCRIPTION:
//Connect a tool embedded in a Windows Form with the ArcGIS Application Framework.

ESRI.ArcGIS.esriSystem.IUID UIDCls = new ESRI.ArcGIS.esriSystem.UIDClass();
UIDCls.Value = "MyNamespace_MyCustomTool";
IDocument actDoc = (IDocument)ArcMap.Document;
//Finding the customTool
ESRI.ArcGIS.Framework.ICommandItem commandItem = actDoc.CommandBars.Find(UIDCls, false, false); 

if (commandItem == null) 
{ 
   return; 
}

//This cast would fail:
//MyCustomTool_Class actCustomTool2 = (MyCustomTool_Class)commandItem.Command;

MyCustomTool_Class actCustomTool = AddIn.FromID<MyCustomTool_Class (ThisAddIn.IDs.MyCustomTool);
actCustomTool.actFrm = this;

ArcMap.Application.CurrentTool = commandItem;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.