Where to listen for OnRowChanged event

1597
14
Jump to solution
04-08-2019 11:33 AM
BrianBulla
Regular Contributor

Hi,

I'm trying to implement some code in an AddIn that will listen for Edit events, and then run some code.  I'm using the following article to help (https://pro.arcgis.com/en/pro-app/sdk/api-reference/#topic9808.html) but I'm not sure where I am supposed to put this code.

Should I be creating a new AddIn for this, or can I just add the code to an existing AddIn?  All users currently load the same Toolbar, which I have created an AddIn for.  Can I just put the code in there??

Thanks,

0 Kudos
1 Solution

Accepted Solutions
SeanJones
Esri Regular Contributor

Hey Brian,

Yes with autoload true, the module starts to act an Arcobjects extension. Initialize will be called after the application starts but before a project is loaded and its maps, so you wont have any references. If you go down this route you have to listen to the project opened event and start your events from there.

I wrote a sample way back at 1.1 that shows an example of this that only subscribed to one layer. If you need to listen to all layers in all maps within a project, you'll need to do some loops. If you think users are likely to add or remove layers to the maps, you'll need to listen to that event too. Its all doable but is a pain to setup.

View solution in original post

14 Replies
SeanJones
Esri Regular Contributor

Hi Brian,

You can put the code anywhere you have a reference to the layer you are subscribing to.

 

Normally this might be in an individual control such as a tool or button that needs to listen to the event. For a tool this could be in OnToolActivateAsync or even OnSketchCompleteAsync. Just don’t forget to unsubscribe from the event as required. The subscription token helps with that.

If you’re add-in has many controls you could subscribe/unsubscribe to the event in each one, which helps with control portability at the expense of repeating code, or put them in a common location such as the add-in module, toolbar?, or dock pane if you use one. Be careful with the module approach as that code will run before the project/map opens so you’ll need to listen to those events too.

Think about when the event needs to fire, for example for some of your controls, all your controls and/or any of the standard editor tools.

0 Kudos
BrianBulla
Regular Contributor

Hi Sean Jones‌,

Thanks.  For what I am trying to do I need to be listening to all edits as they happen;  RowCreated, RowChanged and RowDeleted.  Not to any one specific layer, but any layer pulled in from SDE.  Basically I am trying to create a custom 'Editor Tracking' as we will be turning it off on the DB side to accomodate some requirements of another application that needs to monitor the GIS data.

Currently I am trying to listen for just the RowChanged event from the Toolbar, but no such luck.  Here is my code that I have placed in the Toolbars module1.cs.  The problem is that it doesn't seem to be catching any of my edits.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using ArcGIS.Desktop.Framework;
using ArcGIS.Desktop.Framework.Contracts;
using System.Threading.Tasks;
using ArcGIS.Desktop.Editing;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using ArcGIS.Desktop.Editing.Events;

namespace DSM_Toolbar_ArcPro
{
    internal class Module1 : Module
    {
        private static Module1 _this = null;    

        /// <summary>
        /// Retrieve the singleton instance to this module here
        /// </summary>
        public static Module1 Current
        {
            get
            {
                return _this ?? (_this = (Module1)FrameworkApplication.FindModule("DSM_Toolbar_ArcPro_Module"));
            }
        }

        #region Overrides
        /// <summary>
        /// Called by Framework when ArcGIS Pro is closing
        /// </summary>
        /// <returns>False to prevent Pro from closing, otherwise True</returns>
        protected override bool CanUnload()
        {
            //TODO - add your business logic
            //return false to ~cancel~ Application close
            
            return true;
        }

        protected override bool Initialize()
        {
            QueuedTask.Run(() =>
            {
                //Listen for row events on a layer
                var featLayer = MapView.Active.GetSelectedLayers().First() as FeatureLayer;
                var layerTable = featLayer.GetTable();

                //subscribe to row events
                //var rowCreateToken = RowCreatedEvent.Subscribe(OnRowCreated, layerTable);
                var rowChangeToken = RowChangedEvent.Subscribe(OnRowChanged, layerTable);
                //var rowDeleteToken = RowDeletedEvent.Subscribe(OnRowDeleted, layerTable);
            });

            return base.Initialize();
        }

        private Guid _currentRowChangedGuid = Guid.Empty;

        protected void OnRowChanged(RowChangedEventArgs args)
        {
            var row = args.Row;

            if (_currentRowChangedGuid == args.Guid)
                return;

            row["COMMENTS"] = "EDITED";

            _currentRowChangedGuid = args.Guid;
            row.Store();
            _currentRowChangedGuid = Guid.Empty;
        }

        #endregion Overrides

    }
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
BrianBulla
Regular Contributor

OK, so I've done some more research on this.  Still not sure if I'm on the right track, but things are working more predictably now.  As per some articles and the ArcGIS Pro Reference guide, I am now looking at subscribing to the EditCompletedEvent.  This is what I have so far which seems to be working for both attribute edits and geometry changes.  (Still need to look into creating new features)

        protected override bool Initialize()
        {
            //ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("HI");

            var eceToken = EditCompletedEvent.Subscribe(onEce);
            
            return true;
        }

        protected Task onEce(EditCompletedEventArgs args)
        {
            IReadOnlyCollection<MapMember> members = args.Members;

            foreach (MapMember member in members)
            {
                ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show(member.Name.ToString());
            }            

            return Task.FromResult(0);
        }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

So I am getting a messagebox with the name of the layer I am editing, which is what I am expecting.  But....Is there a way to get at the features that were actually edited?  Not just the ones that are selected, but the ones that were actually edited.  So even if 10 are selected, but only 1 was edited, I am just interested in the 1 that was edited.

0 Kudos
SeanJones
Esri Regular Contributor

Hey Brian,

I dont see much wrong with the row event approach. You dont need the row.store in this example as you're changing the row thats being passed in. Assuming all the references are ok it should have worked. The module initialize would only be called when you activated one of your controls in the add-in (assuming autoload is false in the config). How far was it getting?

The EditCompletedEvent fires after the edit has been successfully executed. You can get what changed in the event through the Creates, Modifies and Deletes properties on EditCompletedEventArgs. If you want to make changes to any of these returned features you would have to start another edit operation, as compared to the row events where the edit is in progress, but for just reporting changes its fine.

0 Kudos
BrianBulla
Regular Contributor

Hi Sean Jones‌,

Things are working pretty good now.  I've moved the code into a new ArcGIS Pro Module Add-In, just to narrow down where things were going wrong.  In my Module1.cs I have the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Threading.Tasks;
using ArcGIS.Core.CIM;
using ArcGIS.Core.Data;
using ArcGIS.Core.Geometry;
using ArcGIS.Desktop.Catalog;
using ArcGIS.Desktop.Core;
using ArcGIS.Desktop.Editing;
using ArcGIS.Desktop.Extensions;
using ArcGIS.Desktop.Framework;
using ArcGIS.Desktop.Framework.Contracts;
using ArcGIS.Desktop.Framework.Dialogs;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using ArcGIS.Desktop.Editing.Events;

namespace RowChanged_TEST
{
    internal class Module1 : Module
    {
        private static Module1 _this = null;

        /// <summary>
        /// Retrieve the singleton instance to this module here
        /// </summary>
        public static Module1 Current
        {
            get
            {
                return _this ?? (_this = (Module1)FrameworkApplication.FindModule("RowChanged_TEST_Module"));
            }
        }

        #region Overrides
        /// <summary>
        /// Called by Framework when ArcGIS Pro is closing
        /// </summary>
        /// <returns>False to prevent Pro from closing, otherwise True</returns>
        protected override bool CanUnload()
        {
            //TODO - add your business logic
            //return false to ~cancel~ Application close
            return true;
        }

        protected override bool Initialize()
        {
            //ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("HI");

            var eceToken = EditCompletedEvent.Subscribe(onEce);
            
            return true;
        }

        protected Task onEce(EditCompletedEventArgs args)
        {
            QueuedTask.Run(() =>
            {
                //Listen for row events on a layer
                var featLayer = MapView.Active.GetSelectedLayers().First() as FeatureLayer;
                var layerTable = featLayer.GetTable();

                //subscribe to row events                
                var rowChangeToken = RowChangedEvent.Subscribe(OnRowChanged, layerTable);
            });
            
            return Task.FromResult(0);
        }

        private Guid _currentRowChangedGuid = Guid.Empty;

        protected void OnRowChanged(RowChangedEventArgs args)
        {
            var row = args.Row;

            if (_currentRowChangedGuid == args.Guid)
                return;

            row["COMMENTS"] = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
            row["CONDITION"] = DateTime.Now.ToString();

            //_currentRowChangedGuid = args.Guid;
            //row.Store();
            _currentRowChangedGuid = Guid.Empty;
        }


        #endregion Overrides

    }

It's all working well.  One issue I am having is getting past this line:

var featLayer = MapView.Active.GetSelectedLayers().First() as FeatureLayer;

Unless I have a layer selected in the Table of Contents, I get an error....as expected I suppose.  This was just a copy/paste from the API Reference Guide, so I'll have to refine it a bit.  I guess I should be getting the layers edited through the EditCompletedEventArgs??

Another issue, is that whenever I run the code it never works the first time.  For example, I'll make some edits (with a layer in the TOC selected), the code runs, but doesn't do anything....no errors....it just doesn't work.  Then I immediatley do another edit, and everything works as expected.  I'm not really sure what might be going on, but I've created a few other tools where I have seen this behaviour before.  Do you see anything in my code that might be causing this?  Have you ever heard of network or back-end DB configuration that might be causing this problem??

Thanks for your help!

0 Kudos
SeanJones
Esri Regular Contributor

Hey Brian,

GetSelectedLayers returns the 'highlighted' layers in the table of contents, to return everything you'll want GetLayersAsFlattenedList. You can filter that by layer type or search for a particular layer name.

In your current code you're setting up a row listener within the edit completed event. Nothing happens after the first edit because all its doing is setting up the event (the subscribe). Remember in the editcompleted event, the edit has already occurred.

On the second edit, another row event is established, now you have two row events pointing to OnRowChanged. OnRowChanged will fire from the first one and do its thing. Row events occur during the edit.

To avoid this, you only want to subscribe to the layer once. Additional subscribes to the same layer create additional events and make it hard to unsubscribe when needed. Try again to subscribe to the row event under Initialize. If it works in the current code, it should work under Initialize.

0 Kudos
BrianBulla
Regular Contributor

Hi Sean Jones‌,

When I do this in the Initialize, I get an error even before the .aprx file loads into ArcGIS with a NullReference Exception on the var featLayer initialization.  I guess the Module is loading before the map?  

protected override bool Initialize()
        {
            var featLayer = MapView.Active.Map.GetLayersAsFlattenedList().FirstOrDefault() as FeatureLayer;
            var layerTable = featLayer.GetTable();

            //subscribe to row events                
            var rowChangeToken = RowChangedEvent.Subscribe(OnRowChanged, layerTable);
            
            return true;
        }
‍‍‍‍‍‍‍‍‍‍‍

When I do it this way, things seem to work, but only after the first edit.

protected override bool Initialize()
        {
            var eceToken = EditCompletedEvent.Subscribe(onEce);
            
            return true;
        }

        protected Task onEce(EditCompletedEventArgs args)
        {
            QueuedTask.Run(() =>
            {
                    var featLayer = MapView.Active.Map.GetLayersAsFlattenedList().FirstOrDefault() as FeatureLayer;
                    var layerTable = featLayer.GetTable();

                    //subscribe to row events                
                    var rowChangeToken = RowChangedEvent.Subscribe(OnRowChanged, layerTable);
                //}
            });

            return Task.FromResult(0);
        ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
BrianBulla
Regular Contributor

And in my Config.daml I have autoload="True" because I always want this tool to be running in the background.  I always want it to be tracking user edits, and not just when the user turns in 'On'.

0 Kudos
SeanJones
Esri Regular Contributor

Hey Brian,

Yes with autoload true, the module starts to act an Arcobjects extension. Initialize will be called after the application starts but before a project is loaded and its maps, so you wont have any references. If you go down this route you have to listen to the project opened event and start your events from there.

I wrote a sample way back at 1.1 that shows an example of this that only subscribed to one layer. If you need to listen to all layers in all maps within a project, you'll need to do some loops. If you think users are likely to add or remove layers to the maps, you'll need to listen to that event too. Its all doable but is a pain to setup.