2

Last night a student from my Udemy course about writing Python AddIns asked for:

some tips for using a combo box to filter another combo box. What I am trying to do is use a combo box to select my [feature class], which I've managed to do. Once that combo box is selected, I want the second box to show all the fields in that [feature class], which I can then go ahead and select to perform further analysis on.

I think the key to doing this may be found in code from @sur's answer (with help from Freddie Gibson of Esri) to Dynamically adding items into one python addin Combobox from another?:

import arcpy
import pythonaddins

class ComboBoxClass1(object):
    """Implementation for TestCombo_addin.combobox (ComboBox)"""
    def __init__(self):
        self.items = ["cb1item1", "cb1item2"]
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'WWWWWW'
        self.width = 'WWWWWW'
        ComboBoxClass1._hook=self
    def onSelChange(self, selection):
        pass

class ComboBoxClass2(object):
    """Implementation for TestCombo_addin.combobox_1 (ComboBox)"""
    def __init__(self):
        self.items = ["cb2item1", "cb2item2"]
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'WWWWWW'
        self.width = 'WWWWWW'
    def onSelChange(self, selection):
        ComboBoxClass1._hook.items.append(selection)

but how do I use the _hook property of a ComboBoxClass to achieve what my student has asked for?

2 Answers 2

2

The code that I used to solve this was:

import arcpy
import pythonaddins

class ComboBoxClass1(object):
    """Implementation for ComboBox_addin.combobox1 (ComboBox)"""
    def __init__(self):
        arcpy.env.workspace = r"C:\polygeo\QGIS"
        self.items = arcpy.ListFeatureClasses()
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'WWWWWWWWWWWWWWWWWWWWWWWWWWWW'
        self.width = 'WWWWWWWWWWWWWWWWWWWWWWWWWWWW'
    def onSelChange(self, selection):
        ComboBoxClass2._hook.items = [x.name for x in arcpy.ListFields(selection)]
    def onEditChange(self, text):
        pass
    def onFocus(self, focused):
        pass
    def onEnter(self):
        pass
    def refresh(self):
        pass

class ComboBoxClass2(object):
    """Implementation for ComboBox_addin.combobox2 (ComboBox)"""
    def __init__(self):
        self.items = []
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'WWWWWWWWWWWW'
        self.width = 'WWWWWWWWWWWW'
        ComboBoxClass2._hook=self
    def onSelChange(self, selection):
        pass
    def onEditChange(self, text):
        pass
    def onFocus(self, focused):
        pass
    def onEnter(self):
        pass
    def refresh(self):
        pass

To understand the code:

  • when initializing the first combo box I set its items to be all the feature classes in a workspace that I set. Since that workspace is a folder this results in a list of shapefiles that gets presented to me as a pulldown.
  • when initializing the second combo box I set its items to be an empty list and I also provide a hook (which seems to be an arbitrary property that could be named anything) to that list using ComboBoxClass2._hook=self
  • when I choose a shapefile from the first combo box its onSelChange function takes my selection and uses that to set the items in my hook to the second combo box to be the fields from the shapefile that I choose. Since ListFields returns a list of field objects I use list comprehension to turn that into a list of field names instead.

enter image description here

enter image description here

5
  • I'm currently doing something similar but using global variables instead of a Class._hook. I think I like the way you've done it better. But could you please explain the ._hook a bit further. Ie, is _hook just an arbitrarily named class property that you made up, or is it a built-in Python name? Commented Oct 31, 2018 at 0:37
  • @SonofaBeach I've not seen any documentation on it, the only source for it being available came from the earlier Q&A that I linked to in the question. I'm not enough of a Pythonista to be sure but I think it must be a property that either ArcPy or Python provides.
    – PolyGeo
    Commented Oct 31, 2018 at 0:44
  • What happens if you replace ._hook with something arbitrary like, .sharedInstance? (If you have a chance to try it out.) I can't find any documentation on _hook, and the only examples I can find for it make it look like it's just an arbitrary property. Commented Oct 31, 2018 at 1:10
  • @SonofaBeach You're right. I replaced ._hook with .sharedInstance and it worked just the same.
    – PolyGeo
    Commented Oct 31, 2018 at 1:18
  • Hey, thanks for trying this out. Looks like it's just a simple class attribute, which makes sense. I might go and update my own Add-In now to use a similar strategy instead of the global variables I'm currently using. Commented Oct 31, 2018 at 1:36
1

I think using a class property to set a hook to an instance of that class is bad practice and while it works fine here will cause you grief if you use this construct in other code. E.g. you write a class and create more than one instance of it or you have a superclass with this hook and you create multiple subclasses or multiple instances of a subclass. The hook property of the class will get overwritten each time an instance is created.

What I do is use the instances of the classes, not the classes themselves.

For example, in config.xml you will have something like:

<?xml version="1.0"?>
<ESRI.Configuration etc...>
  <etc...>
  <AddIn language="PYTHON" library="ComboBox_addin.py" namespace="ComboBox_addin">
    <ArcMap>
      <Commands>
          <ComboBox caption="CB1" category="Blah" class="ComboBoxClass1" id="ComboBox_addin.combobox1" etc...>etc...</ComboBox>
          <ComboBox caption="CB2" category="Blah" class="ComboBoxClass2" id="ComboBox_addin.combobox2" etc...>etc...</ComboBox>
      </Commands>
      <Extensions></Extensions>
      <Toolbars>
        <Toolbar caption="Blah blah" category="Blah" id="ComboBox_addin.toolbar" showInitially="true">
          <Items>
            <ComboBox refID="ComboBox_addin.combobox1"/>
            <ComboBox refID="ComboBox_addin.combobox2"/>
          </Items>
        </Toolbar>
      </Toolbars>
      <Menus></Menus>
    </ArcMap>
  </AddIn>
</ESRI.Configuration>

Note the id and refID are the same. These will refer to instances of these classes.

Then you just replace references to ComboBoxClass2 to combobox2 in your code:

import arcpy
import pythonaddins

class ComboBoxClass1(object):
    """Implementation for ComboBox_addin.combobox1 (ComboBox)"""
    def __init__(self):
        arcpy.env.workspace = r"C:\polygeo\QGIS"
        self.items = arcpy.ListFeatureClasses()
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'WWWWWWWWWWWWWWWWWWWWWWWWWWWW'
        self.width = 'WWWWWWWWWWWWWWWWWWWWWWWWWWWW'
    def onSelChange(self, selection):
        combobox2.items = [x.name for x in arcpy.ListFields(selection)] # <================
    def onEditChange(self, text):
        pass
    def onFocus(self, focused):
        pass
    def onEnter(self):
        pass
    def refresh(self):
        pass

class ComboBoxClass2(object):
    """Implementation for ComboBox_addin.combobox2 (ComboBox)"""
    def __init__(self):
        self.items = []
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'WWWWWWWWWWWW'
        self.width = 'WWWWWWWWWWWW'

    def onSelChange(self, selection):
        pass
    def onEditChange(self, text):
        pass
    def onFocus(self, focused):
        pass
    def onEnter(self):
        pass
    def refresh(self):
        pass

If you run into a NameError (an issue with earlier ArcGIS versions, not sure if still a problem), put the following at the bottom of ComboBox_addin.py: :

##Workaround for 'NameError: global name 'combobox2' is not defined' - https://geonet.esri.com/thread/76833
combobox1 = ComboBoxClass1()
combobox2 = ComboBoxClass2()

Your Answer

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

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