Select to view content in your preferred language

EditOpertion during Form event cannot be Undone

1434
7
Jump to solution
01-05-2018 11:28 AM
BrianBulla
Honored Contributor

I have an EditOperation that runs on the _Click event of a form.  I'm pretty sure I have the ChainedEditOperation setup properly, as I have set this up previously in another tool I have made that work successfully.  This is my first time trying an EditOperation of a Form event.

What is happening is that edits are happening, but not showing up on the Undo Stack.  Does anyone know if there are issues with an EditOperation run on a Form event that may cause problems??  I have posted my code below, as well as the windows Form to help provide some context:

Basically it gives the user the ability to update common fields in the selected features of multiple feature classes, and the code below is for the Save button.

private void btnSave_Click(object sender, EventArgs e)
        {
            try
            {
                //Give the user a chance to get out.
                if (ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Are you sure you want to update all the selected layers?", "Update Selected Features?", System.Windows.MessageBoxButton.YesNo ) == System.Windows.MessageBoxResult.No)
                    return;
                this.Cursor = Cursors.WaitCursor;

                QueuedTask.Run(() =>
                {
                    Map map = MapView.Active.Map;
                    FeatureLayer firstLayer = map.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(l => l.Name.IndexOf(lstLayers.Items[0].ToString(), StringComparison.CurrentCultureIgnoreCase) >= 0).FirstOrDefault();

                    //Create the MAIN edit operation...this will show up on the Undo Stack
                    var editOperation = new ArcGIS.Desktop.Editing.EditOperation();
                    editOperation.Name = "Update Multiple Features";
                    editOperation.EditOperationType = ArcGIS.Desktop.Editing.EditOperationType.Long;

                    //Make a 'blank' edit, so that it triggers the ChainedEditOperation to work properly, later in the code
                    var inspSetup = new ArcGIS.Desktop.Editing.Attributes.Inspector();
                    inspSetup.Load(firstLayer, 1);
                    editOperation.Modify(inspSetup);
                    editOperation.Execute();

                    //Go through each layer in the Layers listbox
                    foreach (string layer in lstLayers.Items)
                    {
                        //MessageBox.Show(layer);

                        var currentLayer = map.FindLayers(layer).FirstOrDefault() as BasicFeatureLayer;
                        var selection = currentLayer.GetSelection();
                        IReadOnlyList<long> selectedOIDs = selection.GetObjectIDs();                        

                        foreach (var oid in selectedOIDs)
                        {
                            //Create the chained edit for the Undo stack
                            var chainedOp = editOperation.CreateChainedOperation();
                            var insp = new ArcGIS.Desktop.Editing.Attributes.Inspector();

                            insp.Load(currentLayer, oid);   //use the current selected OBJECTID as you go through the loop of selected features

                            for (int i = 0; i < gridFields.Rows.Count; i++)
                            {
                                if (gridFields.Rows[i].Cells[1].Value != null)
                                {
                                    insp[gridFields.Rows[i].Cells[0].Value.ToString()] = gridFields.Rows[i].Cells[1].Value;
                                }                                
                            }

                            //Save the edits and commit the changes.
                            //This will update the ChainedEditOperation, so it only shows up once in the Undo Stack
                            chainedOp.Modify(insp);
                            chainedOp.Execute();
                        }
                    }
                }
                );  //end of QueuedTask
            }

            catch
            {

            }

            finally
            {
                this.Cursor = Cursors.Default;
            }
        }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

0 Kudos
1 Solution

Accepted Solutions
NarelleChedzey
Esri Contributor

Brian, 

The first thing to check would be the result of the main EditOperation.  If this does not succeed then nothing will be placed on the undo/redo stack regardless of the success or failure of the chained operations. 

In your situation perhaps there is no feature with object id 1 in 'firstLayer' or 'firstLayer'  itself could be null.  Check the result of the editOperation.Execute line (as I updated below).  If this is false then that is the reason why you are not seeing anything on the undo/redo stack. 

There shouldn't be any issues with running editOperations (chained or otherwise) from forms whether they are WPF or windows forms. 

Narelle

                    var inspSetup = new ArcGIS.Desktop.Editing.Attributes.Inspector();
                    inspSetup.Load(firstLayer, 1);
                    editOperation.Modify(inspSetup);
                    bool result = editOperation.Execute();

View solution in original post

0 Kudos
7 Replies
NarelleChedzey
Esri Contributor

Brian, 

The first thing to check would be the result of the main EditOperation.  If this does not succeed then nothing will be placed on the undo/redo stack regardless of the success or failure of the chained operations. 

In your situation perhaps there is no feature with object id 1 in 'firstLayer' or 'firstLayer'  itself could be null.  Check the result of the editOperation.Execute line (as I updated below).  If this is false then that is the reason why you are not seeing anything on the undo/redo stack. 

There shouldn't be any issues with running editOperations (chained or otherwise) from forms whether they are WPF or windows forms. 

Narelle

                    var inspSetup = new ArcGIS.Desktop.Editing.Attributes.Inspector();
                    inspSetup.Load(firstLayer, 1);
                    editOperation.Modify(inspSetup);
                    bool result = editOperation.Execute();
0 Kudos
BrianBulla
Honored Contributor

Hi Narelle,

Thanks for responding and the explanation of what is going wrong.  You are correct; the first edit operation is coming up as FALSE.  

Can you possibly explain how the ChainedOperation should would in a looping situation?  I have done this before in a previous tool I created, and I guess I got lucky in that it works.  All of the samples I have found for ChainedOperation use a linear progression of code, so it's easy to see how it works, but in a looping situation I am not sure how to code that first edit that seems to trigger the rest of the ChainedEdits......does that make sense??

I'm not sure the way I am doing it, with the 'blank' edit, is really the proper way to do it.

0 Kudos
BrianBulla
Honored Contributor

Hi Narelle,

Here is my code now, and it seems to work.  Again, I'm not really sure if this is the recommended way of dealing with the ChainedEditOperation in a looping situation, but it seems to work.

private void btnSave_Click(object sender, EventArgs e)
        {
            try
            {
                //Give the user a chance to get out.
                if (ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Are you sure you want to update all the selected layers?", "Update Selected Features?", System.Windows.MessageBoxButton.YesNo ) == System.Windows.MessageBoxResult.No)
                    return;
                this.Cursor = Cursors.WaitCursor;

                //Create a list of all the selected layers (since we cannot access Form controls in a QueuedTask)
                //var updateLayers = lstLayers.Items.Cast<String>().ToList();

                QueuedTask.Run(() =>
                {
                    Map map = MapView.Active.Map;
                    FeatureLayer firstLayer = map.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(l => l.Name.IndexOf(lstLayers.Items[0].ToString(), StringComparison.CurrentCultureIgnoreCase) >= 0).FirstOrDefault();

                    //Create the MAIN edit operation...this will show up on the Undo Stack
                    var editOperation = new ArcGIS.Desktop.Editing.EditOperation();
                    editOperation.Name = "Update Multiple Features";
                    editOperation.EditOperationType = ArcGIS.Desktop.Editing.EditOperationType.Long;
                                        
                    var inspSetup = new ArcGIS.Desktop.Editing.Attributes.Inspector();
                    

                    bool firstEdit = false;  //Create this to track the 'blank' edit

                    //Go through each layer in the Layers listbox
                    foreach (string layer in lstLayers.Items)
                    {
                        var currentLayer = map.FindLayers(layer).FirstOrDefault() as BasicFeatureLayer;
                        var selection = currentLayer.GetSelection();
                        IReadOnlyList<long> selectedOIDs = selection.GetObjectIDs();

                        //Make a 'blank' edit, so that it triggers the ChainedEditOperation to work properly, later in the code
                        if (firstEdit == false)
                        {
                            inspSetup.Load(firstLayer, selectedOIDs.First<long>());
                            editOperation.Modify(inspSetup);
                            editOperation.Execute();
                            firstEdit = true;
                        }

                        foreach (var oid in selectedOIDs)
                        {
                            //Create the chained edit for the Undo stack
                            var chainedOp = editOperation.CreateChainedOperation();
                            var insp = new ArcGIS.Desktop.Editing.Attributes.Inspector();

                            insp.Load(currentLayer, oid);   //use the current selected OBJECTID as you go through the loop of selected features

                            for (int i = 0; i < gridFields.Rows.Count; i++)
                            {
                                if (gridFields.Rows[i].Cells[1].Value != null)
                                {
                                    insp[gridFields.Rows[i].Cells[0].Value.ToString()] = gridFields.Rows[i].Cells[1].Value;
                                }                                
                            }

                            //Save the edits and commit the changes.
                            //This will update the ChainedEditOperation, so it only shows up once in the Undo Stack
                            chainedOp.Modify(insp);
                            chainedOp.Execute();
                        }
                    }
                }
                );  //end of QueuedTask
            }

            catch
            {

            }

            finally
            {
                this.Cursor = Cursors.Default;
            }
        }

Thanks again for your assistance!!

0 Kudos
by Anonymous User
Not applicable

Brian,

In this case you can also remove the chained operation and just use a looped inspector load/modify pattern with a single execute at the end. ChainedOperation is only needed when you need the results of one execute to be used in the next and have one operation on the stack. 

0 Kudos
BrianBulla
Honored Contributor

Hi Sean,

Yes, I have tried that before (see this post:  https://community.esri.com/thread/205610-cannot-undo-after-edits-made ) but was not successful.  

In my case, I am looking to have all the edits created in the loop under one operation in the Undo stack.  (ie. 100 edits, but just click Undo once to undo them all).

My method seems to be working, but it seems a but counter-intuitive to have to do a 'fake' edit before entering the loop where all the real edits are made.

0 Kudos
NarelleChedzey
Esri Contributor

Brian,

Let me see if I can clarify things a bit

  • Any edits performed in ArcGIS Pro need to be wrapped in an EditOperation.
  • Each EditOperation puts an item on the undo/redo stack (assuming it is a long transaction)
  • An EditOperation can contain a single edit (for example a modify) or multiple edits at once (for example a create, 2 modifies and 3 deletes). Caveat - the multiple edits MUST be independent of each other. For example I cannot perform a create and then an attribute update of the same feature in the same EditOperation, because setting up the Modify method requires an objectID which depends on the create having been committed to the database.
  • The edit(s) are 'committed' when the Execute or ExecuteAsync method is called

For example, the following Edit operation contains a create, 2 modifies and 1 delete. This edit operation will succeed assuming that i'm modifying and deleting valid features.

 var op = new EditOperation();
 op.Name = "Multiple edits";
 op.Create(featureLayer, geometry);
 op.Modify(featureLayer2, 1);
 op.Modify(featureLayer3, 2);
 op.Delete(featureLayer2, 3);
 bool result = op.Execute(); // <= create, modifies and delete are committed here

  • A chained operation is simply an edit operation that links to a previous EditOperation allowing all edits from both operations to be part of the same undo/redo item. You use a chained operation when the edit you require is dependent on the results of a previous EditOperation being commited.
  • As you can have multiple edits in an EditOperation you can also have multiple edits in a Chained operation.


Using the previous example, technically there's no reason why you can't do the following with a chained operation but it is not best practice - you're adding additional overhead to the database and the code is not as easy to read or maintain.

 var op = new EditOperation();
 op.Name = "Multiple edits";
 op.Create(featureLayer, geometry);
 op.Modify(featureLayer2, 1);
 bool result = op.Execute(); // <= create, modify are committed here

 if (result)
 {
    // create chained op
    var chainedOp = op.CreateChainedOperation();
    chainedOp.Modify(featureLayer3, 2);
    chainedOp.Delete(featureLayer2, 3);
    result = chainedOp.Execute(); // <= modify and delete are committed here
 }


The example we mostly use with chained operations is the 'create a feature and add an attachment' example. The method signature of AddAttachment requires the objectID of the feature which means that the creation needs to be committed in order for that objectID to exist. The creation occurs in the main edit operation and adding the attachment occurs in the chained operation.

        //create an edit operation and name. 
       var op1 = new EditOperation();
       op1.Name = string.Format("Create point in '{0}'", CurrentTemplate.Layer.Name);
      
       //create a new feature and return the oid.
       long newFeatureID = -1;
       op1.Create(CurrentTemplate, geometry, oid => newFeatureID = oid);

       // newFeatureID is populated when the Execute is called
       op1.Execute();
       
       //create a chained operation and add the attachment.        
       var op2 = op1.CreateChainedOperation();         
       // use the newFeatureID and template layer to identify the new feature         
       op2.AddAttachment(CurrentTemplate.Layer, newFeatureID, @"C:\Hydrant.jpg");         
       op2.Execute();

With regards to the examples in your posts;

It made sense to use the ChainedOperation in your previous post because the updates you were performing incremented values from features whose attributes were also required to be updated.  You wanted all these operations to appear in one undo/redo item AND you were dependent on the attribute values of previous edit commits. 

The scenario in this post doesn't appear to have the same constraints; all the updates are independent of each other as the values come from the form. This is why Sean posted about not requiring the ChainedOperation.

I've reworked your code into multiple snippets.

The first illustrating how to perform the chainedOperation in a loop to give you an example of how it might be structured if it was required (note - this is just one way of structuring it, not the only way);.  The update you posted is pretty similar to this.  You're correct in saying that  having to do a 'fake' edit (as per your first code snippet) is counter-intuitive... that's generally an indication that you either need to restructure your code or think of a different approach.  

the second snippet is the preferred solution with regards to Edit Operations to the problem in this post. It uses a single EditOperation and queues the multiple Modify methods. You should use this rather than the chainedOperation because the edits you're performing are all independent of each other and not reliant on values from a previous operation.

the third snippet shows you an optimization with respect to the Inspector object.  Because the selected features in each layer will have the exact same fields updated to the same values we can load the inspector once per layer with all the selected features.rather than for every feature.  (you can use this optimization with the chainedOperation snippet too - i just didn't include it)

snippet one - illustrates looping of a chainedOperation

 private void btnSave_Click(object sender, EventArgs e)
 {
    try
    {
       //Give the user a chance to get out
.      if (ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Are you sure you want to update all the selected layers?", "Update Selected Features?", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.No)
         return;

       this.Cursor = Cursors.WaitCursor;

       QueuedTask.Run(() =>         
       {
          Map map = MapView.Active.Map;
          
          //Create the MAIN edit operation...this will show up on the Undo Stack
          var editOperation = new ArcGIS.Desktop.Editing.EditOperation();
          editOperation.Name = "Update Multiple Features";
          editOperation.EditOperationType = ArcGIS.Desktop.Editing.EditOperationType.Long;

          ArcGIS.Desktop.Editing.EditOperation chainedOp = null;
          
          // create an inspector object
          var inspector = new ArcGIS.Desktop.Editing.Attributes.Inspector(true);

          // set a flag - we will be using the main operation first
          bool isMainOperation = true;

          //Go through each layer in the Layers listbox
          foreach (string layer in lstLayers.Items)
          {
            var currentLayer = map.FindLayers(layer).FirstOrDefault() as BasicFeatureLayer;
            var selection = currentLayer.GetSelection();
            IReadOnlyList<long> selectedOIDs = selection.GetObjectIDs();

            foreach (var oid in selectedOIDs)
            {
               inspector.Clear();
               inspector.Load(currentLayer, oid);

               //use the current selected OBJECTID as you go through the loop of selected features
               for (int i = 0; i < gridFields.Rows.Count; i++)
               { 
                 if (gridFields.Rows.Cells[1].Value != null)
                 {
                   insp[gridFields.Rows.Cells[0].Value.ToString()] = gridFields.Rows.Cells[1].Value;
                 }
               }

               if (isMainOperation)
               {
                 // set up the edit
                 editOperation.Modify(inspector);
                 // execute it
                 bool result = editOperation.Execute();
                 // change the flag so we'll use the chainedOperation next
                 isMainOperation = false;
               }
               else 
               {
                 // only create the chained operation once
                 if (chainedOp == null)
                   chainedOp = editOperation.CreateChainedOperation();
                
                 // queue up a modify
                 chainedOp.Modify(inspector);
               }
             }
           }
            
          // finished the layer loop, lets commit anything in the chained operation
          if ((chainedOp != null) && !chainedOp.IsEmpty)
          {
            bool chainedResult = chainedOp.Execute();
          }
       });  //end of QueuedTask
    }
    catch
    {
        
    } 
    finally 
    {  
       this.Cursor = Cursors.Default; 
    }    
 }

snippet two - illustrates the preferred EditOperation solution - queues all edits into a single EditOperation

private void btnSave_Click_NoChain(object sender, EventArgs e)
{
   try
   {
       //Give the user a chance to get out.
      if (ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Are you sure you want to update all the selected layers?", "Update Selected Features?", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.No)           
        return;
      
       this.Cursor = Cursors.WaitCursor;
        
       QueuedTask.Run(() => 
       {
          Map map = MapView.Active.Map;
     
          //Create the MAIN edit operation...this will show up on the Undo Stack
          var editOperation = new ArcGIS.Desktop.Editing.EditOperation();
          editOperation.Name = "Update Multiple Features";
          editOperation.EditOperationType = ArcGIS.Desktop.Editing.EditOperationType.Long;
          
          var inspector = new ArcGIS.Desktop.Editing.Attributes.Inspector(true);
        
          //Go through each layer in the Layers listbox 
          foreach (string layer in lstLayers.Items)
          {
            var currentLayer = map.FindLayers(layer).FirstOrDefault() as BasicFeatureLayer;
            var selection = currentLayer.GetSelection();
            IReadOnlyList<long> selectedOIDs = selection.GetObjectIDs();
          
            foreach (var oid in selectedOIDs)
            {
              inspector.Clear();
              inspector.Load(currentLayer, oid);
           
              for (int i = 0; i < gridFields.Rows.Count; i++)
              {
                 if (gridFields.Rows.Cells[1].Value != null)
                 {
                   insp[gridFields.Rows.Cells[0].Value.ToString()] = gridFields.Rows.Cells[1].Value;
                 }
              }
              editOperation.Modify(inspector);
            }
          }
          
          if (!editOperation.IsEmpty)
          {
             bool result = editOperation.Execute();
             if (!result)
             {
               // throw an error message
             } 
          }
      });  //end of QueuedTask
   }
   catch
   {
   }
   finally
   {
      this.Cursor = Cursors.Default;
   }
}

snippet three - an optimized approach

private void btnSave_Click_NoChainOptimized(object sender, EventArgs e)
{
   try
   {
      //Give the user a chance to get out.
      if (ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Are you sure you want to update all the selected layers?", "Update Selected Features?", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.No)
          return;
      
      this.Cursor = Cursors.WaitCursor;
      
      QueuedTask.Run(() => 
      {
         Map map = MapView.Active.Map;
      
        //Create the MAIN edit operation...this will show up on the Undo Stack
        var editOperation = new ArcGIS.Desktop.Editing.EditOperation();
        editOperation.Name = "Update Multiple Features";
        editOperation.EditOperationType = ArcGIS.Desktop.Editing.EditOperationType.Long;
  
        var inspector = new ArcGIS.Desktop.Editing.Attributes.Inspector(true);
        
        //Go through each layer in the Layers listbox
        foreach (string layer in lstLayers.Items)
        {
           var currentLayer = map.FindLayers(layer).FirstOrDefault() as BasicFeatureLayer;
           var selection = currentLayer.GetSelection();
           IReadOnlyList<long> selectedOIDs = selection.GetObjectIDs();
     
           inspector.Clear();    
           // load all the selected oids into the inspector   
           // we can do this because each feature will have the exact same fields updated to the same values
           inspector.Load(currentLayer, selectedOIDs);
           
            for (int i = 0; i < gridFields.Rows.Count; i++) 
            {
               if (gridFields.Rows.Cells[1].Value != null)
               {
                 insp[gridFields.Rows.Cells[0].Value.ToString()] = gridFields.Rows.Cells[1].Value;
               }
            } 
            
            // queue the modify
            editOperation.Modify(inspector);
          }
          
          // finished the layer loop - commit all the edits
          if (!editOperation.IsEmpty)
          {
             bool result = editOperation.Execute(); 
            if (!result)
             {
               // throw an error message
             } 
          }
       });  //end of QueuedTask
    }
    catch
    {
    }
   finally
   { 
     this.Cursor = Cursors.Default;
   }
}

There's a lot of information here. Let me know if you still have questions about edit operations and chaining or if something I've said isn't clear.

Narelle

BrianBulla
Honored Contributor

Hi Narelle.  Wow!!  Thanks for taking the time to put all of that together.  That definitely helps to clarify how editing in ArcPro works.  I still need to sit down and work through making some changes to mine, but this will certainly help me optimize my code so it runs a bit smoother.

I'm really just a part-time coder, converting the code in a bunch of ArcMap tools so they work in ArcPro but I'm always up for making things work better than before.

I'll work on what you mentioned and post an further questions here.

Thanks!!

0 Kudos