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
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 myselection
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. SinceListFields
returns a list of field objects I use list comprehension to turn that into a list of field names instead.
-
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?Son of a Beach– Son of a Beach2018年10月31日 00:37:35 +00:00Commented 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.2018年10月31日 00:44:44 +00:00Commented 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.Son of a Beach– Son of a Beach2018年10月31日 01:10:48 +00:00Commented Oct 31, 2018 at 1:10 -
@SonofaBeach You're right. I replaced
._hook
with.sharedInstance
and it worked just the same.2018年10月31日 01:18:04 +00:00Commented 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.Son of a Beach– Son of a Beach2018年10月31日 01:36:37 +00:00Commented Oct 31, 2018 at 1:36
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()