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;
}
}
Solved! Go to Solution.
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();
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();
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.
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!!
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.
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.
Brian,
Let me see if I can clarify things a bit
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
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
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!!