Parameter as Dropdown/Combobox in Python toolbox

6702
6
Jump to solution
09-05-2014 11:49 AM
KenCarrier
Occasional Contributor III

I originally wrote this as a python add-in but I could really use some type of GUI or user interface. Since Tkinter and wx are not supported nor stable when used inside ArcMap I am trying to either build the entire functionality in a python toolbox or python add-in although it looks like I will have to use a combination of both.

All I want is to be able to do is loop through a directory and return a list of folders within that directory. These folders all contain different layer files(*.lyr)

When the user would choose a folder, a list of values would be presented to the user in something like a listbox or pick list. After user selects layer it adds the layer to the TOC.

How can I get the code below to return a list in a python toolbox and present it to the end user as a drop down or combobox.

I would have thought there would have been some type of parameter to have a list presented to the end user but I cannot seem to find one that will work for this scenario.

wrkspc = '//someserver/GIS/Layers/'

folders = []

for dirpath, dirnames, filenames in arcpy.da.Walk(wrkspc,topdown=True):

     if '.' not in str(dirpath):

          folder = str(os.path.basename(dirpath))

          folders.append(folder)

return folders

0 Kudos
1 Solution

Accepted Solutions
AlexanderNohe1
Occasional Contributor III

We already answered this offline as well but to not leave the geonet community in the dark, we did the following:

1)  Add a button to the addin.

2)  Add the PYT to the install folder in the addin.

3)  Use the pythonaddins.GPToolDialog command to add the tool wired to the relative path of the file using syntax to that found below:

x = os.path.dirname(sys.argv[0])

z = os.path.join(x, "KenFold.pyt")

I hope this helps anyone else running into this issue.

View solution in original post

0 Kudos
6 Replies
AlexanderNohe1
Occasional Contributor III

Hey Ken!

I have written a quick sample that should do what you are looking for it to do.

Enter in a directory in the first combo box and then press the drop down in the second to select the layer.  Finally, press the button to add the layer to the map document.

Does this help?

Sample Code:

import arcpy

import pythonaddins

import os

global layerlist

global dataLocation

global buttonSelection

class ButtonClass5(object):

    """Implementation for Ken3_addin.button (Button)"""

    def __init__(self):

        self.enabled = True

        self.checked = False

    def onClick(self):

        mxd = arcpy.mapping.MapDocument("CURRENT")

        lyrToAdd = buttonSelection

        indexPosition = layerList.index(lyrToAdd)

        lyr = arcpy.mapping.Layer(dataLocation[indexPosition])

        arcpy.mapping.AddLayer(mxd.activeDataFrame, lyr)

class ComboBoxClass1(object):

    """Implementation for Ken3_addin.combobox (ComboBox)"""

    def __init__(self):

        self.items = []

        self.editable = True

        self.enabled = True

        self.dropdownWidth = 'WWWWWW'

        self.width = 'WWWWWW'

    def onSelChange(self, selection):

        pass

    def onEditChange(self, text):

        path = text

        global layerList

        global dataLocation

        layerList = []

        dataLocation = []

        for root, dirs, files in os.walk(path):

            for name in files:

                if name.endswith(".lyr"):

                    layerList.append(name)

                    dataLocation.append(os.path.join(root,name))

        print layerList

        print dataLocation

    def onFocus(self, focused):

        pass

    def onEnter(self):

        pass

    def refresh(self):

        pass

class ComboBoxClass4(object):

    """Implementation for Ken3_addin.combobox_1 (ComboBox)"""

    def __init__(self):

        self.items = []

        self.editable = True

        self.enabled = True

        self.dropdownWidth = 'WWWWWW'

        self.width = 'WWWWWW'

    def onSelChange(self, selection):

        global buttonSelection

        buttonSelection = selection

    def onEditChange(self, text):

        pass

    def onFocus(self, focused):

        self.items = layerList

    def onEnter(self):

        pass

    def refresh(self):

        pass

AlexanderNohe1
Occasional Contributor III

Hi Ken,

If you were interested in doing this as a Python Toolbox, here is another way to do the same functionality as listed above.

Thanks!

import arcpy

import os

import sys

global layerList

global dataLocation

class Toolbox(object):

    def __init__(self):

        self.label =  "Folder Toolbox"

        self.alias  = "folders"

        # List of tool classes associated with this toolbox

        self.tools = [findFolders]

class findFolders(object):

    def __init__(self):

        self.label       = "Finds Layer Files"

        self.description = "This tool will find layer files in a folder and will update that folder with those files."

    def getParameterInfo(self):

        #Define parameter definitions

        # Input Features parameter

        in_workspace = arcpy.Parameter(

            displayName="Input Workspace",

            name="in_workspace",

            datatype="DEFolder",

            parameterType="Required",

            direction="Input")

        # Sinuosity Field parameter

        layers = arcpy.Parameter(

            displayName="Layers",

            name="layers",

            datatype="String",

            parameterType="Optional",

            direction="Input",

            multiValue = True)

       

        parameters = [in_workspace, layers]

       

        return parameters

    def isLicensed(self): #optional

        return True

    def updateParameters(self, parameters): #optional

        if parameters[0].altered:

            global layerList

            global dataLocation

            path = str(parameters[0].value)

            layerList = []

            dataLocation = []

            for root, dirs, files in os.walk(path):

                for name in files:

                    if name.endswith(".lyr"):

                        layerList.append(name)

                        dataLocation.append(os.path.join(root,name))

            parameters[1].filter.list = layerList

        return

    def updateMessages(self, parameters): #optional

        return

    def execute(self, parameters, messages):

        for i in parameters[1].values:

            arcpy.AddMessage("Adding: " + str(i))

            mxd = arcpy.mapping.MapDocument("CURRENT")

            lyrToAdd = i

            indexPosition = layerList.index(lyrToAdd)

            lyr = arcpy.mapping.Layer(dataLocation[indexPosition])

            arcpy.mapping.AddLayer(mxd.activeDataFrame, lyr)

0 Kudos
KenCarrier
Occasional Contributor III

Alexander,

Thank you for the help

Is it possible to populate the value or list for this parameter dynamically?


# Input Features parameter  


in_workspace = arcpy.Parameter(  


displayName="Input Workspace",  


name="in_workspace",  


datatype="DEFolder",  


parameterType="Required",  


direction="Input")  

I have tried doing this but when passing in a list I get an error. When I set the list to be hard coded it works just fine.


import arcpy, os
global folders


class Toolbox(object):
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""
        self.label = "Toolbox"
        self.alias = ""


        # List of tool classes associated with this toolbox
        self.tools = [Tool]


class Tool(object):
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        self.label = "Tool"
        self.description = ""
        self.canRunInBackground = False
        self.getLyrFolders()


    def getParameterInfo(self):
        global folders
        #Define parameter definitions


        # First parameter
        param0 = arcpy.Parameter(displayName="Input Features",name="in_features",datatype="GPFeatureLayer",parameterType="Required",direction="Input")


        # Second parameter
        param1 = arcpy.Parameter(displayName="Sinuosity Field",name="sinuosity_field",datatype="GPString",parameterType="Required",direction="Input")


        param1.filter.type = "Value List"
        #param1.filter.list = ['test','test1','test2']
        param1.filter.list = folders


        # Third parameter
        param2 = arcpy.Parameter(displayName="Output Features",name="out_features",datatype="GPFeatureLayer",parameterType="Derived",direction="Output")


        param2.parameterDependencies = [param0.name]
        param2.schema.clone = True


        params = [param0, param1, param2]


        return params


    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return True


    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed."""
        return


    def updateMessages(self, parameters):
        """Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation."""
        return


    def execute(self, parameters, messages):
        """The source code of the tool."""
        return


    def getLyrFolders(self):
        global folders
        wrkspc = '//bauerdb1/GIS/GIS/Layers/'
        folders=[]
        for dirpath, dirnames, filenames in arcpy.da.Walk(wrkspc,topdown=True):
            if '.' not in str(dirpath):
                folder = os.path.basename(dirpath)
                folders.append(folder)

0 Kudos
KenCarrier
Occasional Contributor III

I think I may have gotten it to work as I was hoping, working off of Alexander's example I finally got this to work by ensuring no null values were in my list. I was noticing that the first item in my list was an empty string. I added some logic to getLyrFolders function, which ensures only strings that were not null were appended to the list. After doing this the tool began working. I can only assume this is expected behavior from a GP perspective but I think the error being returned could be more descriptive as it mentioned nothing about null values.

Originally I thought it might have something to do with encoding in utf-8 but in testing, it appears a null string or item in a list was causing the issue, I did not test whether an empty string somewhere other than in the beginning of the list would cause issues but in my case it was the first string in the list.

Here is the code I was able to get working, not a finished product but merely an example.


import arcpy, os
global folders


class Toolbox(object):
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""
        self.label = "Toolbox"
        self.alias = ""


        # List of tool classes associated with this toolbox
        self.tools = [Tool]


class Tool(object):
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        self.label = "Tool"
        self.description = ""
        self.canRunInBackground = False
        self.getLyrFolders()


    def getParameterInfo(self):
        global folders
        #Define parameter definitions


        # First parameter
        param0 = arcpy.Parameter(displayName="Input Features",name="in_features",datatype="GPFeatureLayer",parameterType="Required",direction="Input")


        # Second parameter
        param1 = arcpy.Parameter(displayName="Sinuosity Field",name="sinuosity_field",datatype="GPString",parameterType="Required",direction="Input")


        param1.filter.type = "Value List"
        #param1.filter.list = ['test','test1','test2']
 b = [str(item) for item in folders]
        param1.filter.list = b


        # Third parameter
        param2 = arcpy.Parameter(displayName="Output Features",name="out_features",datatype="GPFeatureLayer",parameterType="Derived",direction="Output")


        param2.parameterDependencies = [param0.name]
        param2.schema.clone = True


        params = [param0, param1, param2]


        return params


    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return True


    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed."""
        return


    def updateMessages(self, parameters):
        """Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation."""
        return


    def execute(self, parameters, messages):
        """The source code of the tool."""
        return


    def getLyrFolders(self):
        global folders
        wrkspc = '//bauerdb1/GIS/GIS/Layers/'
        folders=[]
        for dirpath, dirnames, filenames in arcpy.da.Walk(wrkspc,topdown=True):
            if '.' not in str(dirpath):
                folder = os.path.basename(dirpath)
                if len(folder) != 0:
                    folders.append(folder)



0 Kudos
KenCarrier
Occasional Contributor III

Alexander,

I guess the next big question is how to marry or get python toolboxes to work with add-ins, I think I have seen it is possible but if you have any suggestions on how to call this tool from a add-in button that would be helpful.

0 Kudos
AlexanderNohe1
Occasional Contributor III

We already answered this offline as well but to not leave the geonet community in the dark, we did the following:

1)  Add a button to the addin.

2)  Add the PYT to the install folder in the addin.

3)  Use the pythonaddins.GPToolDialog command to add the tool wired to the relative path of the file using syntax to that found below:

x = os.path.dirname(sys.argv[0])

z = os.path.join(x, "KenFold.pyt")

I hope this helps anyone else running into this issue.

0 Kudos