I am developing a simple add-in that will trigger an action after user selects features on the map. The action will essentially make sure a dockable window is open and in that window display information about the selected features.
I have implemented a simple function that loops over selected features using IEnumFeature and collects their OIDs. To make sure the OIDs are present for all features in the selection, I set the IEnumFeatureSetup.AllFields = true.
Now to the problem. After I've selected a number features several times (regardless of what features or how many I select), the SelectionChanged event stops from being fired. It appears as if the add-in has crashed, but no error message is displayed. This happens both in debug and production. If I try to restart the add-in by de-selecting it in the Customise->Extensions menu, I can no longer select it back. However, ArcMap works fine otherwise. To make the add-in work again I must restart ArcMap.
I have done some naive memory usage inspection using Task Manager. Memory usage raises as I keep re-selecting features. Is this a memory leak? I am releasing the IEnum reference so this should, at least in theory, not be a problem.
The features are pulled from a file geodatabase. They have 5 short text fields each and there is around 1300 of them. The more features I select at once, the sooner the add-in stops working (e.g. if I select all of the features in the map at once, the SelectionChanged event won't be fired again).
I am developing in Arcobjects 10.1 SDK with Visual Studio 10 for C# Express using .NET 3.5. I cannot upgrade nor can I use .NET 4.0 due to client constraints. The add-in is being tested in ArcMap 10.1.
using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.IO; using System.Collections; using ESRI.ArcGIS.Framework; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Desktop.AddIns; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Geometry; using System.Runtime.InteropServices; namespace DfT.Maritime.DeepPort { public class DeepPortExtension: ESRI.ArcGIS.Desktop.AddIns.Extension { private static IActiveViewEvents_Event ViewEvents { get { return ArcMap.Document.ActiveView as IActiveViewEvents_Event; } } public DeepPortExtension() { } protected override void OnStartup() { } protected override bool OnSetState(ExtensionState state) { Init(); return base.OnSetState(state); } private void Init() { if (ArcMap.Document.ActiveView.IsActive()) { InitAddin(); } else { ArcMap.Events.OpenDocument += delegate() { InitAddin(); }; ArcMap.Events.NewDocument += delegate() { InitAddin(); }; } } private void InitAddin() { // Bind selection changed handler ViewEvents.SelectionChanged += SelectionChanged_Handler; } private void SelectionChanged_Handler() { ISelection selection = ArcMap.Document.FocusMap.FeatureSelection; IEnumFeature featuresEnum = (IEnumFeature)selection; IEnumFeatureSetup featureSetup = (IEnumFeatureSetup)selection; featureSetup.AllFields = true; string message = ""; IFeature feature; while ((feature = featuresEnum.Next()) != null) { int fldID = feature.Fields.FindField("locode"); message += feature.get_Value(fldID).ToString() + ","; } System.Windows.Forms.MessageBox.Show(message); Marshal.ReleaseComObject(featuresEnum); Marshal.ReleaseComObject(featureSetup); Marshal.ReleaseComObject(selection); } } }
Please help, this has been bugging me for the good part of the last week!
Solved! Go to Solution.
For what it's worth, here's how I solved the problem. Is seems it was the repeated call to ArcMap.Document.FocusMap.FeatureSelection that was breaking my add-in. If I instead store the focus map in a member variable IMap map and retrieve the feature selection by calling map.FeatureSelection is works just fine.
using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.IO; using System.Collections; using ESRI.ArcGIS.Framework; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Desktop.AddIns; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Geometry; using System.Runtime.InteropServices; namespace DfT.Maritime.DeepPort { public class DeepPortExtension: ESRI.ArcGIS.Desktop.AddIns.Extension { IMap map; private static IActiveViewEvents_Event ViewEvents { get { return ArcMap.Document.ActiveView as IActiveViewEvents_Event; } } public DeepPortExtension() { } protected override void OnStartup() { } protected override bool OnSetState(ExtensionState state) { Init(); return base.OnSetState(state); } private void Init() { if (ArcMap.Document.ActiveView.IsActive()) { InitAddin(); } else { ArcMap.Events.OpenDocument += delegate() { InitAddin(); }; ArcMap.Events.NewDocument += delegate() { InitAddin(); }; } } private void InitAddin() { // Bind selection changed handler ViewEvents.SelectionChanged += SelectionChanged_Handler; // // Store focus map in a member variable!!! // map = ArcMap.Document.FocusMap; } private void SelectionChanged_Handler() { // // Retrieve selected features from member variable!!! // ISelection selection = map.FeatureSelection; IEnumFeature featuresEnum = (IEnumFeature)selection; IEnumFeatureSetup featureSetup = (IEnumFeatureSetup)selection; featureSetup.AllFields = true; string message = ""; IFeature feature; while ((feature = featuresEnum.Next()) != null) { int fldID = feature.Fields.FindField("locode"); message += feature.get_Value(fldID).ToString() + ","; } System.Windows.Forms.MessageBox.Show(message); Marshal.ReleaseComObject(featuresEnum); Marshal.ReleaseComObject(featureSetup); Marshal.ReleaseComObject(selection); } } }
Have you tried using Try..Catch in the SelectionChanged_Handler function to see if there is anything unusual happening?
Yes. I have tried to wrap the whole block of code in a try..catch to no avail. Catching Exception doesn't seem to help, unfortunately, as none seems to be thrown...
How about using FinalReleaseComObject or the Garbage Collector, as shown in this discussion?
All right, this bug is very odd. I have tried to force garbage collection but the only effect was that the SelectionChanged_Handler would get called only once upon selecting features. After that, the add-in would stop working as it does without the forced collection. I am no expert in using forced garbage collection, but this approach doesn't seem to be the answer to my problem and appears to be frowned upon by many C# programmes (that doesn't mean it's wrong, I know).
I have altered the code to localize the problem. It seems that the issue is caused by the growing message string, i.e. the step message += feature.HasOID.ToString() + "\n"; (line 15.)
The following code breaks as described previously:
private void SelectionChanged_Handler() { string message = ""; try { ISelection selection = ArcMap.Document.FocusMap.FeatureSelection; IEnumFeature featuresEnum = (IEnumFeature)selection; IEnumFeatureSetup featureSetup = (IEnumFeatureSetup)selection; featureSetup.AllFields = true; IFeature feature; while ((feature = featuresEnum.Next()) != null) { message += feature.HasOID + ","; Marshal.FinalReleaseComObject(feature); } Marshal.FinalReleaseComObject(featuresEnum); Marshal.FinalReleaseComObject(featureSetup); } catch (Exception e) { System.Windows.Forms.MessageBox.Show(e.Message); } System.Windows.Forms.MessageBox.Show(message); }
No exception is caught, no error displayed, the add-in simply stops showing the message as expected and cannot be re-initialized in the Customize menu.
However, the altered code below seems to run OK:
private void SelectionChanged_Handler() { List<string> message = new List<string>(); try { ISelection selection = ArcMap.Document.FocusMap.FeatureSelection; IEnumFeature featuresEnum = (IEnumFeature)selection; IEnumFeatureSetup featureSetup = (IEnumFeatureSetup)selection; featureSetup.AllFields = true; IFeature feature; while ((feature = featuresEnum.Next()) != null) { message.Add(feature.HasOID.ToString()); Marshal.FinalReleaseComObject(feature); } Marshal.FinalReleaseComObject(featuresEnum); Marshal.FinalReleaseComObject(featureSetup); } catch (Exception e) { System.Windows.Forms.MessageBox.Show(e.Message); } System.Windows.Forms.MessageBox.Show(String.Join(",", message.ToArray())); message.Clear(); }
Can someone explain what's going on behind the scene in these two cases?
For what it's worth, here's how I solved the problem. Is seems it was the repeated call to ArcMap.Document.FocusMap.FeatureSelection that was breaking my add-in. If I instead store the focus map in a member variable IMap map and retrieve the feature selection by calling map.FeatureSelection is works just fine.
using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.IO; using System.Collections; using ESRI.ArcGIS.Framework; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Desktop.AddIns; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Geometry; using System.Runtime.InteropServices; namespace DfT.Maritime.DeepPort { public class DeepPortExtension: ESRI.ArcGIS.Desktop.AddIns.Extension { IMap map; private static IActiveViewEvents_Event ViewEvents { get { return ArcMap.Document.ActiveView as IActiveViewEvents_Event; } } public DeepPortExtension() { } protected override void OnStartup() { } protected override bool OnSetState(ExtensionState state) { Init(); return base.OnSetState(state); } private void Init() { if (ArcMap.Document.ActiveView.IsActive()) { InitAddin(); } else { ArcMap.Events.OpenDocument += delegate() { InitAddin(); }; ArcMap.Events.NewDocument += delegate() { InitAddin(); }; } } private void InitAddin() { // Bind selection changed handler ViewEvents.SelectionChanged += SelectionChanged_Handler; // // Store focus map in a member variable!!! // map = ArcMap.Document.FocusMap; } private void SelectionChanged_Handler() { // // Retrieve selected features from member variable!!! // ISelection selection = map.FeatureSelection; IEnumFeature featuresEnum = (IEnumFeature)selection; IEnumFeatureSetup featureSetup = (IEnumFeatureSetup)selection; featureSetup.AllFields = true; string message = ""; IFeature feature; while ((feature = featuresEnum.Next()) != null) { int fldID = feature.Fields.FindField("locode"); message += feature.get_Value(fldID).ToString() + ","; } System.Windows.Forms.MessageBox.Show(message); Marshal.ReleaseComObject(featuresEnum); Marshal.ReleaseComObject(featureSetup); Marshal.ReleaseComObject(selection); } } }