1

I am developing a Python Add-in for ArcMap 10.4 that, upon a click on a map, opens a wxPython GUI that contains a combo box listing the layers in the map document.

Here's a current snippit from my script:

class ToolClass2(object):
"""Implementation for DesignTool.tool (Tool)"""
dlg= None   
def __init__(self):       
    self.enabled = True
    self.shape = "NONE" # Can set to "Line", "Circle" or "Rectangle" for interactive shape drawing and to activate the onLine/Polygon/Circle event sinks.
    self.cursor=3 #set cursor to crosshair        
def onMouseDownMap(self, x, y, button, shift):
   global xy 
   xy = (x,y)
   global layerlist
   layerlist=[]
   mxd=arcpy.mapping.MapDocument("CURRENT")
   for lyr in arcpy.mapping.ListLayers(mxd):
       layerlist.append(lyr.name)
   del mxd

I then reference layerlist within my wxpython class to have the layers display as choices within the combo box. When I run it for the first time after opening ArcMap, it runs correctly and displays all of the layers within the map document. However, when I attempt to run it again after adding or deleting layers, it does not update the list - even if I start a new blank document.

How can I write my script to update upon each run of the tool?

I am just assuming that this is an issue with my map document, but perhaps it is an issue with my WxPython dialog?

UPDATE: I now believe that this issue is coming from the fact that the self.Destroy() command in wxPython does not work in ArcMap. As a workaround for this, I was using self.Show(False) to close the UI (similar to an example shown here: https://geonet.esri.com/thread/38336), but when run again, self.Show(True) displays the same UI, as it was never actually destroyed. Are there any known alternatives to self.Destroy() that will actually get rid of the UI and not just prevent it from being displayed?

import arcpy
import pythonaddins
import wx

class ToolClass2(object):
    """Implementation for DesignTool.tool (Tool)"""
    dlg= None   
    def __init__(self):       
        self.enabled = True
        self.shape = "NONE" # Can set to "Line", "Circle" or "Rectangle" for interactive shape drawing and to activate the onLine/Polygon/Circle event sinks.
        self.cursor=3 #set cursor to crosshair        
    def onMouseDownMap(self, x, y, button, shift):
        global laylist
        laylist=[]
        mxd=arcpy.mapping.MapDocument("CURRENT")
        for lyr in arcpy.mapping.ListLayers(mxd):
           laylist.append(lyr.name)
        del mxd
        if self.dlg is None:  
            self.dlg = TestDialog()  
        else:  
            self.dlg.Show(True)  
        return  

class TestDialog(wx.Frame):  
    def __init__(self):  
        wxStyle = wx.CAPTION | wx.RESIZE_BORDER | wx.MINIMIZE_BOX |wx.CLOSE_BOX | wx.SYSTEM_MENU | wx.CB_DROPDOWN 
        wx.Frame.__init__(self, None, -1, "Design Form", style=wxStyle, size=(330, 370))  
        self.SetMaxSize((330, 370))  
        self.SetMinSize((330, 370))  
        self.Bind(wx.EVT_CLOSE, self.OnClose)  
        panel = wx.Panel(self, -1)  
        wx.StaticText(panel, -1, "Choose Layer:", pos=(8,64))  
        self.LayerCombo = wx.ComboBox(panel, -1, value=laylist[1], pos=(180, 64), size=(120,21), choices=laylist)
        self.Bind(wx.EVT_BUTTON, self.OnSet, id=self.btnSet.GetId())  
        self.Show(True)  

    def OnClose(self, event):  
        self.Show(False) # self.Destroy() doesn't work          

    def OnSet(self, event):  
        Layerpath= str(self.LayerCombo.GetValue())  
        self.Show(False)

app = wx.PySimpleApp()  
app.MainLoop()  
  • My first suspect would be the wxPython interaction. I think you should try to separate the two parts for testing purposes to satisfy yourself that each works as documented on its own. – PolyGeo Jul 20 '16 at 21:36
  • 1
    I agree with @PolyGeo...There is no need to make a combobox in wxPython, you can simply use a combobox on the same toolbar you are making. – crmackey Jul 20 '16 at 21:41
1

As per my comment, why not just use a combobox for the layer list within the AddIn itself?

Here is an example of how I have done this before in a Python AddIn:

class LayerList(object):
    """Implementation for ClipAndShip_addin.combobox (ComboBox)"""
    def __init__(self):
        self.items = []
        self.editable = True
        self.enabled = True
        self.dropdownWidth = 'W' * 12
        self.width = 'W' * 12

    def onSelChange(self, selection):
        pass
    def onEditChange(self, text):
        pass
    def onFocus(self, focused):
        if focused:
            self.mxd = arcpy.mapping.MapDocument('current')
            self.df = self.mxd.activeDataFrame
            lyrs = [l.name for l in arcpy.mapping.ListLayers(self.mxd, '*', self.df)
                    if not l.isBroken and l.supports('DATASOURCE')]
            self.items = sorted(lyrs)
            del self.mxd
    def onEnter(self):
        pass
    def refresh(self):
        pass
  • The combobox is just one component of a larger UI that I've created for a user to input a list of parameters. This is why I'm using wxPython. I think you and @polygeo are definitely correct in that the issues are stemming from the wxPython interaction. I'm going to try and narrow it down from there. – Ashton Jul 21 '16 at 16:07
  • Yeah, I have heard it wxPython doesn't play well with ArcGIS. I think the same is true for tkinter. – crmackey Jul 21 '16 at 16:18
  • It looks like the issue is a direct result of the two not playing well together. I'm not able to destroy the UI in ArcGIS, only to edit whether it is actually being displayed or not by changing its visibility from True to False. This is fine when it's only run once, but when it's opened a second time, it's just changing that status back from False to True - opening the exact same UI, not creating a new, updated one. – Ashton Jul 21 '16 at 19:46
0

As pointed out by @Ali here, while self.Destory() does not work, the issue can be solved by making sure that a new instance of self.Dialog is created each time the tool is run. The following if/then statement causes the creation of the dialog to be bypassed if it already exists:

if self.dlg is None:  
    self.dlg = TestDialog()  
else:  
    self.dlg.Show(True)  

The if/then statement can be removed and replaced with:

self.dlg = TestDialog() 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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