Creating an ArcMap button to update field value only for selected feature(s).

1454
7
09-17-2018 10:10 AM
DavidCakalic
Occasional Contributor
I'm looking for a way to have 3-4 buttons on an ArcMap toolbar, each changing a specific field TO a specific value, only for features that are currently selected.
We have several feature classes, all have the field STATUS.
Acceptable values for the STATUS field are: New, Existing, Deleted, Modified
I'd like to be able to select a feature, then quickly press a pre-defined button on the toolbar to update that STATUS field only for the currently selected feature.
This would save the time of entering the attribute window, selecting that field, and either typing or selecting the value from a list (if setup with domain.) 
Currently I'm having trouble just finding some python code snippets that will let me do the basics (change the field value 'Status' for a selected feature.)
0 Kudos
7 Replies
JoeBorgione
MVP Emeritus

Sounds like you are developing a python addin?

The syntax for CalculateField_Managment() is pretty straight forward: see Calculate Field—Help | ArcGIS Desktop:

arcpy.CalculateField_management (in_table, field, expression, {expression_type}, {code_block})

where in_table is your feature layer or table view  and

field is the status field name

expression is what you would otherwise type into the field calculator

expression_type declares whether Python, VB or Python9.3 is used

code_block  allows you to create your own function

That should just about do it....
DavidCakalic
Occasional Contributor

Joe,

Yes, I guess its an add-in I'm trying to create. Thanks for the link to the CalculateField… that's probably the command I need to work with.   Maybe part of the problem is I don't want to define a SINGLE in-table or feature layer, as it could be any of the dozen layers that I want to update the field value on.... and it seems wasteful to iterate through every possible feature layer when I've already selected a feature I want to operate on.

.

A little more detail on use case:

Currently the database has the status field of all features set to "Existing".

As we review roads/buildings/driveways over imagery, we may find features that have been modified, removed, or are new.  For each item we come across  (regardless of the layer it is in) we want to select that feature then (with a single keypress) be able to set the status field to either "Modified"  "Deleted"  or "New".

.

Honestly, if there were a reliable way (using only keyboard commands in arcmap) to pull up the attribute table and type the new value, I'd just record a keystroke macro and be done...  I was hoping that if only a single feature was already selected there would be a way to quickly reference it through python and then calc a particular field to a defined value... I just haven't come across any so far.

Thanks for your input, and I'll definitely be looking into the CalculateField_management when I have more time to put towards this. 

0 Kudos
JoeBorgione
MVP Emeritus

 Maybe part of the problem is I don't want to define a SINGLE in-table or feature layer, as it could be any of the dozen layers that I want to update the field value on.... and it seems wasteful to iterate through every possible feature layer when I've already selected a feature I want to operate on.

I'm not sure you'll be able to avoid this...

That should just about do it....
RichardFairhurst
MVP Honored Contributor

I agree with Joe.  You are not going to avoid this.  It is the nature of ArcGIS object model that you have to drill down to actual features through each individual layer and cannot access them directly from the list of layers returned by the ListLayers method or other methods that deal with the map as a whole.  Even if you had access to a method that appeared to let you do this, in reality it would most likely just be hiding from you the process of iterating through the layers individually.

ArcObjects had a way of accessing the selected features that are editable in an Editor session through its IEditor interface object, but I don't think Python exposes that kind of functionality.  So, ArcObjects includes a much more robust object model that could have theoretically allowed you to do what you want that the Python developers chose not to incorporate.  But Python itself is already a code base that has vastly simplified the automation of most common workflows and is hiding hundreds if not thousands of lines of ArcObjects code that you would have had to write if you wanted to do the same things in ArcObjects.

DavidCakalic
Occasional Contributor

Thank you both for the replies... that was what I was kind of afraid of.

To me, it seems like a fundamental oversight not to surface things like SELECTED FEATURES or VISIBLE FEATURES, as objects to manipulate; I mean come on... I've done half the work for Arcmap by selecting the stupid feature!!

In the end, it unfortunately may be easier  to create a keyboard macro using AutoHotkey to call up the Attribute window, down arrow 3 times, and enter the text into the field....  It would be much more elegant  to say: "for [selected features] calc status = 'Deleted' "    {I know that's not even close to valid code... but the English equivalent }

0 Kudos
RichardFairhurst
MVP Honored Contributor

The Python code is not inefficient or difficult to write and a HotKey is a poor substitute for real programming.  The code below handles mxds that have group layers of any depth.  The code is designed for simple feature class editing and assumes that all feature classes with selections and a STATUS field are in editable workspaces.  It is not designed for editing versioned SDE feature classes, topologies, geometric networks, etc., since these are non-simple feature classes and require an active editor session to be set up to update features.

import arcpy

# method recursively handles group layers
def updateLayer(layerObj,keypress):
  # dictionary of values based on key entered by user
  valueDict = {'e':'Existing','m':'Modified','d':'Deleted','n':'New'}
  if not layerObj.isGroupLayer:
    if layerObj.isFeatureLayer == True:
      if len(layerObj.getSelectionSet()) > 0:
        lyrFields = arcpy.ListFields(layerObj)
        if 'STATUS' is lyrFields:
          fields = ['STATUS']
          with arcpy.da.UpdateCursor(layerObj, fields) as cursor:
            for row in cursor:
              if keypress.lower() in valueDict:
                row[0] = valueDict[keypress.lower()]
                cursor.updateRow(row)‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
  else:
    for sublayer in arcpy.mapping.ListLayers(layerObj):
      if not sublayer.isGroupLayer:
        updateLayer(sublayer,keypress)

# Capture the keypress value somehow.  Hardcoded just to demonstrate the overall code
keypress = 'm'

mxd = arcpy.mapping.MapDocument("CURRENT")  # Uses your currently open MXD
df = arcpy.mapping.ListDataFrames(mxd, '')[0] # Chooses the first dataframe

lyrs = arcpy.mapping.ListLayers(mxd, '', df)
for lyr in lyrs:
  updateLayer(lyr, keypress.lower())‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
JoshuaBixby
MVP Esteemed Contributor

If you don't mind selecting the appropriate layers in the TOC, you can use The pythonaddins module—Help | ArcGIS Desktop GetSelectedTOCLayerOrDataFrame to return the selected layers and Calculate Field—Help | ArcGIS Desktop honors selection sets on layers.  With some proper error handling, I think creating a single button to do what you want is possible using a Python Add-in.

My overall approach would be:

  • User:
    • Select layers to modify data sets in TOC
    • Select features to update in active view
    • Press button to update tables
  • Tool/button
    • Use GetSelectedTOCLayerOrDataFrame to return a list of layers selected in TOC
    • Iterate over list of layers and execute CalculateField_management on each layer
0 Kudos