Hi folks,
with help of https://community.esri.com/people/xander_bakker/blog/2016/07/19/implementing-cascading-drop-down-lis... I gained some understanding how I can make my script tool take conditions and additional information to fill the tool.
I have three input parameters (a directory, a min and a max value). I would like to have a multiple choice list of those indicators in my txt file (in that directory) that are not empty.
My validator should check the first txt file in that directory, read the field names, if they are not within a list of excluded field names then read the first value, if it is not None than add the field name to a list. Works perfectly in pyscripter.
And I also get the correct list displayed in my tool script. Buuut, the tool says the parameter's field type is invalid after displaying the field names. I could imagine that it has something to do with the filter in the parameter which is set to DataType=Field but of course it is not really a field name any longer after returning it from my script. But what else could I use? Or can I somehow tell the self.params that it is still a (list of) field name(s)?
This is my updateParameters.
def updateParameters(self):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
if filedir:
arcpy.env.workspace = filedir
list_of_files = arcpy.ListFiles("*.txt")
firsttable=list_of_files[0]
print(firsttable)
print(filedir)
complete_filename = str(filedir)+"\\"+firsttable
print(complete_filename)
table=arcpy.MakeTableView_management(complete_filename, "kivu_tview")
desc = arcpy.Describe(table)
#fieldnamesoriginal=[field.name.encode("utf-8") for field in desc.fields] ##collects all fieldnames
fieldselectlist=[]
for ofields in desc.fields:
ftype=ofields.type
fname=ofields.name.encode("utf-8")
if ftype=="Double" or ftype=="Long":
print(fname)
if fname!="Profile_No" and fname!="Longitude (degrees_east)" and fname!="Latitude (degrees_north)" and fname!="Bot. Depth (m)" and fname!="Depth (m)":
with arcpy.da.SearchCursor(table, fname) as calcursor:
for calcrow in calcursor:
if calcrow[0] is not None:
fieldselectlist.append(fname)
break
print(fieldselectlist)
self.params[3].filter.list =fieldselectlist
self.params[3].value=fieldselectlist
return
Any hint would be highly appreciated.
Solved! Go to Solution.
I put your code in a debugging template found on this page: Debugging a ToolValidator class. I only made a few changes, mostly using self.params[0], etc. to access the parameters. I kept the MakeTableView because it aided in identifying the field types. It might be possible to read the text file's first line directly to get the field names, and the second line to determine field types. Print statements will work in the debug script, but they will not display once it is placed in the tool's validator section (and could cause errors). For this reason, they have been commented out.
My first tool parameter was a folder type, the second was a string (for the field names). Here's my debug test code:
import arcpy
# Load the toolbox and get the tool's parameters, using the tool
# name (not the tool label).
#
arcpy.ImportToolbox(r"C:\Path\to\tool\box\dropdown.tbx")
params = arcpy.GetParameterInfo("DropdownTest")
# refresh toolbox in Catalog or ArcMap if changes made to tool/parameters
# also close and restart Python IDE
# Set required parameters
# input, folder
params[0].value = r"C:\Users\Randy\Documents\ArcGIS\PythonScripts\test\text2field\encoding"
# input, string ['Temperature (\xc2\xb0C)', 'pH', 'Chl_A (\xc2\xb5g/L)']
# params[1].value = 'Chl_A (\xb5g/L)' # for 1 value
params[1].values = [ 'pH', 'Chl_A (\xb5g/L)' ] # for multiple values
# ToolValidator class block ------------------------------------------------------------
#
class ToolValidator(object):
"""Class for validating a tool's parameter values and controlling
the behavior of the tool's dialog."""
def __init__(self):
"""Setup arcpy and the list of tool parameters."""
self.params = arcpy.GetParameterInfo()
def initializeParameters(self):
"""Refine the properties of a tool's parameters. This method is
called when the tool is opened."""
return
def updateParameters(self):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
if self.params[0].altered:
if self.params[0].value: # a folder has been entered in first parameter
arcpy.env.workspace = filedir = self.params[0].value
list_of_files = arcpy.ListFiles("*.txt")
firsttable=list_of_files[0]
# print(firsttable)
# print(filedir)
complete_filename = str(filedir)+"\\"+firsttable
# print(complete_filename)
if arcpy.Exists("kivu_tview"): # if tableview exists then delete it
arcpy.Delete_management("kivu_tview")
table=arcpy.MakeTableView_management(complete_filename, "kivu_tview")
desc = arcpy.Describe(table)
# fieldnamesoriginal=[field.name for field in desc.fields] # collects all fieldnames
# print "Table view field names: "+str(fieldnamesoriginal)
fieldselectlist=[]
for ofields in desc.fields:
ftype=ofields.type
fname=ofields.name
if ftype in ["Double","Integer","Long"]:
# print fname
if fname not in ["Profile_No","Longitude (degrees_east)","Latitude (degrees_north)","Bot. Depth (m)","Depth (m)"]:
with arcpy.da.SearchCursor(table, fname) as calcursor:
for calcrow in calcursor:
if calcrow[0] is not None:
fieldselectlist.append(fname)
break
# print fieldselectlist
arcpy.Delete_management("kivu_tview","Table View") # delete table view
# for multiple choice
try:
if self.params[1].values: #if this parameter has seleted values
oldValues = self.params[1].values #set old values to the selected values
except Exception:
pass
self.params[1].filter.list = sorted(fieldselectlist) #set the filter list equal to the sorted values
newValues = self.params[1].filter.list
try:
if len(oldValues): # if some values are selected
self.params[1].values = [v for v in oldValues if v in newValues] # check if seleted values in new list,
# if yes, retain the seletion.
except Exception:
pass
# for single choice
# self.params[1].filter.list = sorted(fieldselectlist) # set second parameter filter list
# if self.params[1].value not in self.params[1].filter.list:
# self.params[1].value = self.params[1].filter.list[0] # default to first item in list
return
def updateMessages(self):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
return
# Call routine(s) to debug ------------------------------------------------------------------------------------------
#
validator = ToolValidator()
validator.updateParameters()
validator.updateMessages()
# testing results
print(arcpy.GetMessages())
print u"Folder: {}".format(params[0].value)
# print u"Fields: {}".format(params[1].value) # for 1 value
print u"Fields: {}".format(params[1].values) # for multiple values
Once I was happy with the debug test, I pasted the ToolValidator section into the tool's validator.
Hope this helps.
I want to make sure I understand your work flow. You are selecting the first text file in a directory. Is this the only text file in the directory, or do you plan to process other files in the same directory? I'm curious why you are not setting up the tool to use a text file as the first parameter in place of a folder location.
For the "calcrow[0] is not None" test, is this for just the first row, or for any row in that field?
Can it be assumed that the min and max value will be selected from one of the fields that is in the field selected list?
Thanks for looking into it Randy.
I only select the first file in the directory because the structure should be for all files inside the directory the same. They are all then processed within a seperate step (in the main python script). I could also use the table as a parameter and take its path to find the directory to process all the files, that is correct. [Is there a way to avoid the table view to be added to the map document and to be stored as a seperate process in the results window?]
I only need to check for the first row because if the value of the first row is none the whole column will be empty.
The min and max values are specified by the user, they are not derived from the parameters but mean the depth range for the parameters to be extracted.
From the error message I get, I assume I would have either to store my list of values in a different "format" than a list OR I have to set the input Data Type (which is field at the moment) differently but I can't figure out either way.
I put your code in a debugging template found on this page: Debugging a ToolValidator class. I only made a few changes, mostly using self.params[0], etc. to access the parameters. I kept the MakeTableView because it aided in identifying the field types. It might be possible to read the text file's first line directly to get the field names, and the second line to determine field types. Print statements will work in the debug script, but they will not display once it is placed in the tool's validator section (and could cause errors). For this reason, they have been commented out.
My first tool parameter was a folder type, the second was a string (for the field names). Here's my debug test code:
import arcpy
# Load the toolbox and get the tool's parameters, using the tool
# name (not the tool label).
#
arcpy.ImportToolbox(r"C:\Path\to\tool\box\dropdown.tbx")
params = arcpy.GetParameterInfo("DropdownTest")
# refresh toolbox in Catalog or ArcMap if changes made to tool/parameters
# also close and restart Python IDE
# Set required parameters
# input, folder
params[0].value = r"C:\Users\Randy\Documents\ArcGIS\PythonScripts\test\text2field\encoding"
# input, string ['Temperature (\xc2\xb0C)', 'pH', 'Chl_A (\xc2\xb5g/L)']
# params[1].value = 'Chl_A (\xb5g/L)' # for 1 value
params[1].values = [ 'pH', 'Chl_A (\xb5g/L)' ] # for multiple values
# ToolValidator class block ------------------------------------------------------------
#
class ToolValidator(object):
"""Class for validating a tool's parameter values and controlling
the behavior of the tool's dialog."""
def __init__(self):
"""Setup arcpy and the list of tool parameters."""
self.params = arcpy.GetParameterInfo()
def initializeParameters(self):
"""Refine the properties of a tool's parameters. This method is
called when the tool is opened."""
return
def updateParameters(self):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
if self.params[0].altered:
if self.params[0].value: # a folder has been entered in first parameter
arcpy.env.workspace = filedir = self.params[0].value
list_of_files = arcpy.ListFiles("*.txt")
firsttable=list_of_files[0]
# print(firsttable)
# print(filedir)
complete_filename = str(filedir)+"\\"+firsttable
# print(complete_filename)
if arcpy.Exists("kivu_tview"): # if tableview exists then delete it
arcpy.Delete_management("kivu_tview")
table=arcpy.MakeTableView_management(complete_filename, "kivu_tview")
desc = arcpy.Describe(table)
# fieldnamesoriginal=[field.name for field in desc.fields] # collects all fieldnames
# print "Table view field names: "+str(fieldnamesoriginal)
fieldselectlist=[]
for ofields in desc.fields:
ftype=ofields.type
fname=ofields.name
if ftype in ["Double","Integer","Long"]:
# print fname
if fname not in ["Profile_No","Longitude (degrees_east)","Latitude (degrees_north)","Bot. Depth (m)","Depth (m)"]:
with arcpy.da.SearchCursor(table, fname) as calcursor:
for calcrow in calcursor:
if calcrow[0] is not None:
fieldselectlist.append(fname)
break
# print fieldselectlist
arcpy.Delete_management("kivu_tview","Table View") # delete table view
# for multiple choice
try:
if self.params[1].values: #if this parameter has seleted values
oldValues = self.params[1].values #set old values to the selected values
except Exception:
pass
self.params[1].filter.list = sorted(fieldselectlist) #set the filter list equal to the sorted values
newValues = self.params[1].filter.list
try:
if len(oldValues): # if some values are selected
self.params[1].values = [v for v in oldValues if v in newValues] # check if seleted values in new list,
# if yes, retain the seletion.
except Exception:
pass
# for single choice
# self.params[1].filter.list = sorted(fieldselectlist) # set second parameter filter list
# if self.params[1].value not in self.params[1].filter.list:
# self.params[1].value = self.params[1].filter.list[0] # default to first item in list
return
def updateMessages(self):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
return
# Call routine(s) to debug ------------------------------------------------------------------------------------------
#
validator = ToolValidator()
validator.updateParameters()
validator.updateMessages()
# testing results
print(arcpy.GetMessages())
print u"Folder: {}".format(params[0].value)
# print u"Fields: {}".format(params[1].value) # for 1 value
print u"Fields: {}".format(params[1].values) # for multiple values
Once I was happy with the debug test, I pasted the ToolValidator section into the tool's validator.
Hope this helps.
Thanks Randy but it still not returning the field names in the required way. This is only writing "Short" as an option to my choice list and also this has the wrong data type because it is not a field name. I don't even know where "Short" is coming from.
I am debugging in PyScripter and everything works fine except of pushing the values back. I need a multiple values list to select my field names from.
Ha, I changed the data type to string and then the tool accepts the input but when executing the tool!
I passed the list as it is:
self.params[3].filter.list=sorted(fieldselectlist)
self.params[3].value=sorted(fieldselectlist)
I'm not sure though if the first line is even necessary. But it works. Now I just have to adjust the main script to "eat" my list...
Only that I get now an error message when trying to execute the tool:
If I interprete the line numbers correctly (does it count in the comments?), line 4 should be list_of_files = arcpy.ListFiles("*.txt") (or around there) and line 51 would be the last line. But I can click OK and then my main script starts running.
It still behaves a bit funny because it would not allow me to "unselect all fields". Also when I unselected a couple of field names then they would be turned back on when clicking "OK" to start the tool.
The same the other way around: When I put the value='' then it would not allow me to make a selection.
Not perfect yet...
I figured out that I should leave the value unset (so just remove the last line before return) to not preset a value.
But I still get a "Bad file descriptor" error message...
Funnily, sometimes I get the "Bad file descriptor" error message, sometimes I don't. But most of the times the tool is running properly.
I posted updated validator code above. I hadn't considered that you are using a multivalue selection list. In addition, there is a check that needed to be done to see if any fields were selected. I also updated the single selection check but commented it out.
Since some of your fields are using extended characters, those field name parameters will be inside single quotes.
See also:
Generating a choice list from a field
Generating a multivalue choice list
Hope this helps.
Thanks a lot!