Python script Tool Multi-Value parameter re-ordering buttons

1486
8
Jump to solution
03-14-2019 02:25 PM
TadHammer3
New Contributor

Hi-

I'm trying to set the values of one control (Multi-value, data type: string <via checkboxes>) to another parameter control (Multi-value, data type: "any value" which produces a drop down with grid for items entered or selected with the add, delete, and re-ordering (up/down) buttons).

The goal is to use the reorder buttons on the 2nd control (of the auto-populated items) to reorganize the list by moving rows up and down, then taking the values out (as text type is fine) and processing them further.

The issue is that I can get the items to populate into the 2nd control (via the ToolValidator class, updateParameters method) but I can't change their order.  If I use the text entry box to populate the grid, it will allow me to change the order of the items.  

Here's the updated code snip:

  def __init__(self):
    """Setup arcpy and the list of tool parameters."""
    self.params = arcpy.GetParameterInfo()
    global PriorParam
    PriorParam = ""
 
  def initializeParameters(self):
    """Refine the properties of a tool's parameters.  This method is
    called when the tool is opened."""
    global PriorParam
    PriorParam = ""
    return

  def updateParameters(self):
     global PriorParam, selected_items_list
     if self.params[2].value:
         domains = arcpy.da.ListDomains(self.params[2].value)
         self.params[3].filter.list = [domain.name for domain in domains if domain.domainType == "CodedValue"]
     if self.params[3].value:
         domains = arcpy.da.ListDomains(self.params[2].value)
#Probably a more direct way to grab the actual domain object but this works for now.
         for domain in domains:
            if domain.name == self.params[3].value:
                coded_values = domain.codedValues
         self.params[5].filter.list = [val.encode('utf-8') for val,desc in coded_values.items()]
     if self.params[5].values  <> PriorParam:
        PriorParam = self.params[5].values
        IncomingParam = arcpy.GetParameterAsText(6)
        working_param = self.params[5].values
        self.params[6].values =  working_param # .params[5].values
     else:
        arcpy.AddMessage("Param5 didn't change after validate review.")
     return

Here's what the parameter page looks like:

Even though the Up/Down arrows are enabled, they don't actually re-order the items.  Am I using the wrong Data Type or is there some other storage location I should manually be moving the items from the "Code(s) to move"  to get them into the SequenceTheMoveCodes parameter control?

Please help!

Thanks!
Tad Hammer

0 Kudos
1 Solution

Accepted Solutions
RandyBurton
MVP Regular Contributor

You have a puzzler here.  I've always had problems using global variables in the ToolValidator class.  In addition, the .altered property doesn't always work as expected (at least for me).  The trick seems to be: don't refresh a parameter if a proceeding one hasn't really changed. I think that is the basic problem you're having, and your globals weren't remembering any previous values correctly.  Thus, every updateParameters check found a change, and the tool interface seemed as if it was locked up. It was actually being constantly refreshed to its previous state.

For my testing, I created a simple script tool with two input variables.  The first used a multi-value string input from a value list filter "A;B;C;D" which gave me the check boxes.  I used the multi-value "Any value" input to match your "SequenceTheMoveCodes" section.  I did not use any globals in the ToolValidator.  The operation seemed a bit quirky, but I could alter the order of the items, and delete some.  This section would immediately refresh to new values when I deleted all values.  Here's the code section from the ToolValidator:

    def updateParameters(self):
        if self.params[0].altered: # a checkbox has changed
            if self.params[0].values: # something is checked
                if self.params[1].values: # the select section has values
                    for v in self.params[0].values: # add new values
                         if v not in self.params[1].values:
                            self.params[1].values.append(v)
                else: # populate select section with checkbox items
                    self.params[1].values = self.params[0].values
        return
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

As I mentioned, the operation seemed a bit quirky.  I am also wondering if there is any value in adding the list of checkboxes when items can be deleted and reordered in the "sequence" section if it is working correctly.  So I tested the following code.  The first parameter was set to "workspace" so I could select a geodatabase.  The second parameter was a "string" which could be populated with a dropdown list of domain names. The final parameter was the multi-value "any value" option to match your "sequence" section.  These parameters seem to be the most important of what you are working with.  Again, I did not use any global variables.

    def updateParameters(self):

        if self.params[0].altered: # geodatabase parameter has changed
            if self.params[0].value: # it has a value
                # do checks on params[0] if required - confirm it is geodatabase
                # get domain information
                domains = arcpy.da.ListDomains(self.params[0].value)
                # populate parameter with coded value domain names
                self.params[1].filter.list = sorted([d.name for d in domains if d.domainType == "CodedValue"])                     
                if self.params[1].value not in self.params[1].filter.list: # set domain selection if needed
                    self.params[1].value = self.params[1].filter.list[0]

        if self.params[1].altered: # selected domain has changed
            if self.params[1].value: # it has a value
                domains = arcpy.da.ListDomains(self.params[0].value)
                for d in domains: # get the coded values
                    if d.name == self.params[1].value:
                        coded_values = d.codedValues
                        dVals = [val for val, desc in coded_values.items()]
                if not self.params[2].values: # the select list has no values in it
                    self.params[2].values = dVals # insert new values
                else: # check the current values
                    for v in self.params[2].values:
                        if v not in dVals: # if not in dVals, then domain selection has changed 
                            self.params[2].values = dVals # reset choices
                        break

        return‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Basically, if a geodatabase and a domain name have been selected, the sequence section of the tool is populated with the codes from the first coded-value domain found.  When a domain code is highlighted, the "x" button will remove it from the list.  The up and down arrows should move the code in the list.  If the domain name is changed, the script checks to see that all values in the sequence area are in the codes used by the newly selected domain.  If a value exists that is not in the domain codes, it is assumed the domain name was changed, and the sequence section is refreshed.  As noted in the code, I do not check that the workspace is an actual database.  Also there is no error processing if the selected database does not have coded value domains.

Here's what my test tool interface looks like

Domain Tool test

Hope this helps.

View solution in original post

8 Replies
Luke_Pinner
MVP Regular Contributor
Can you provide some more code, i.e a tool class that implements just the bit that you are asking about. 
Are you selecting a value in the SequenceTheMoveCodes parameter control before trying to use the up/down arrows?
0 Kudos
TadHammer3
New Contributor

Hi Luke-

Thanks for getting back with me.  I've updated the code snippet to include almost all the of ToolValidator (except the Messages part that I haven't added anything to).  I am selecting a row before I try to use the buttons (the row turns blue).  Then when I hit an arrow button, say I hit "Up" the item doesn't move up, but a grey bar (like a selected row) moves upward.  It's like it doesn't take the data value along with it.

I just noticed something else that I think might be connected.  I can either make manual entries to the SeqTheMoveCodes (once all have been cleared) or have them populated by the UpdateParameters code, but not both.  For instance, I can't add values manually through the + button to the list once they are have pre-populated values in them. 

Thanks!

BTW:  I'm using ArcMap 10.6 against MS SQL Server

0 Kudos
RandyBurton
MVP Regular Contributor

Could you describe the properties/settings you are using for the "SequenceTheMoveCodes" control?

0 Kudos
TadHammer3
New Contributor

Hi Randy-

SequenceTheMoveCodes is Required, Input, Multi-Value, "Any Value" data type-- that data type allowed me to have the re-order grid as well as a data entry box-- even though I don't need the drop down.

Thanks,

Tad

0 Kudos
RandyBurton
MVP Regular Contributor

You have a puzzler here.  I've always had problems using global variables in the ToolValidator class.  In addition, the .altered property doesn't always work as expected (at least for me).  The trick seems to be: don't refresh a parameter if a proceeding one hasn't really changed. I think that is the basic problem you're having, and your globals weren't remembering any previous values correctly.  Thus, every updateParameters check found a change, and the tool interface seemed as if it was locked up. It was actually being constantly refreshed to its previous state.

For my testing, I created a simple script tool with two input variables.  The first used a multi-value string input from a value list filter "A;B;C;D" which gave me the check boxes.  I used the multi-value "Any value" input to match your "SequenceTheMoveCodes" section.  I did not use any globals in the ToolValidator.  The operation seemed a bit quirky, but I could alter the order of the items, and delete some.  This section would immediately refresh to new values when I deleted all values.  Here's the code section from the ToolValidator:

    def updateParameters(self):
        if self.params[0].altered: # a checkbox has changed
            if self.params[0].values: # something is checked
                if self.params[1].values: # the select section has values
                    for v in self.params[0].values: # add new values
                         if v not in self.params[1].values:
                            self.params[1].values.append(v)
                else: # populate select section with checkbox items
                    self.params[1].values = self.params[0].values
        return
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

As I mentioned, the operation seemed a bit quirky.  I am also wondering if there is any value in adding the list of checkboxes when items can be deleted and reordered in the "sequence" section if it is working correctly.  So I tested the following code.  The first parameter was set to "workspace" so I could select a geodatabase.  The second parameter was a "string" which could be populated with a dropdown list of domain names. The final parameter was the multi-value "any value" option to match your "sequence" section.  These parameters seem to be the most important of what you are working with.  Again, I did not use any global variables.

    def updateParameters(self):

        if self.params[0].altered: # geodatabase parameter has changed
            if self.params[0].value: # it has a value
                # do checks on params[0] if required - confirm it is geodatabase
                # get domain information
                domains = arcpy.da.ListDomains(self.params[0].value)
                # populate parameter with coded value domain names
                self.params[1].filter.list = sorted([d.name for d in domains if d.domainType == "CodedValue"])                     
                if self.params[1].value not in self.params[1].filter.list: # set domain selection if needed
                    self.params[1].value = self.params[1].filter.list[0]

        if self.params[1].altered: # selected domain has changed
            if self.params[1].value: # it has a value
                domains = arcpy.da.ListDomains(self.params[0].value)
                for d in domains: # get the coded values
                    if d.name == self.params[1].value:
                        coded_values = d.codedValues
                        dVals = [val for val, desc in coded_values.items()]
                if not self.params[2].values: # the select list has no values in it
                    self.params[2].values = dVals # insert new values
                else: # check the current values
                    for v in self.params[2].values:
                        if v not in dVals: # if not in dVals, then domain selection has changed 
                            self.params[2].values = dVals # reset choices
                        break

        return‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Basically, if a geodatabase and a domain name have been selected, the sequence section of the tool is populated with the codes from the first coded-value domain found.  When a domain code is highlighted, the "x" button will remove it from the list.  The up and down arrows should move the code in the list.  If the domain name is changed, the script checks to see that all values in the sequence area are in the codes used by the newly selected domain.  If a value exists that is not in the domain codes, it is assumed the domain name was changed, and the sequence section is refreshed.  As noted in the code, I do not check that the workspace is an actual database.  Also there is no error processing if the selected database does not have coded value domains.

Here's what my test tool interface looks like

Domain Tool test

Hope this helps.

TadHammer3
New Contributor

Thanks Randy, I appreciate it!!!

0 Kudos
TadHammer3
New Contributor

Randy-

Do you know if I were to write a Python toolbox, could I take closer "control" of the checkbox control?  Meaning, could I run validation code  every time a checkbox was checked or unchecked?  Right now, it's only validated after another control gains "focus".  I didn't see where that was possible, but was wondering if you knew.

Thanks,

Tad

0 Kudos
RandyBurton
MVP Regular Contributor

Although I haven't worked with check boxes in a Python toolbox, I would suspect that you would have a little bit more control.  I do know that global variables work a little better in the Python toolbox provided they are created in getParameterInfo.  See: Python toolbox tool objects do not remember instance variables between function calls? and Using Global Variables in Python Toolbox?  From the discussion, I noticed the use of the pythonaddins.MessageBox that can be very useful in debugging a script tool or Python toolbox.

import arcpy
import pythonaddins

class ToolValidator(object):
    ....

    def updateParameters(self):

        # MessageBox( message, boxtitle)
        pythonaddins.MessageBox('Altered:\n{}'.format(self.params[0].altered),'self.params[0]')
        pythonaddins.MessageBox('Value:\n{}'.format(self.params[0].value),'self.params[0]')‍‍‍‍‍‍‍‍‍‍‍‍

By using the pythonaddins MessageBox I discovered that the .altered and .value property are very similar.  The .altered appears to be a Boolean indicating if the parameter has a value (the parameter has been altered from being None to having some value).  Once a parameter has a value, it will always test as being .altered = True.  If you delete the parameter value, the it will test as false.  I have not found a way to set .altered to false after a parameter has been checked/validated.

Once you are done with debug/testing, you would want to remove the pythonaddins as the MessageBox is extremely annoying for normal operation of a tool.

0 Kudos