1

I am currently working in a company offering GIS solutions, and using ArcGIS tools on a daily basis. One of their main products is an ArcMap extension, which I am assigned on to correct some remaining bugs (my configuration being Windows 8 , ArcGIS 10.1 and Visual Studio 2008). It was impossible to open a custom mxd file by double clicking it. The file was loaded without the custom solvers, displaying a "naked" network.

With lots of debugging and experimenting, I finally "solved" the problem using the ApplicationInitialized event, and loading the file from here with IApplication.OpenDocument if the application was launched from a double click. The idea was to let the file be loaded with the default method, and then load it the right way by triggering our own implementation of IPersistStream.Load.

It worked fine at first, but with a particular file the load fails in ArcMap by double clicking it, displaying the following message: screenshot here

The file can be loaded from ArcMap with File>Open or the initial browsing panel, is recent and not corrupted.

Since the problem is happening right after our implementation of IExtension.Startup, I tried anything I could in this method but I finally understood it isn't supposed to contain file manipulations. Then I tried to force ArcMap to always open a new file on start, but it didn't work either.

The class loading the extension:

 public class OurExtension : IExtension, IExtensionConfig, IPersistVariant
 {
    [ComRegisterFunction()]
    [ComVisible(false)]
    static void RegisterFunction(Type registerType)
    {
        // Required for ArcGIS Component Category Registrar support
        ArcGISCategoryRegistration(registerType);
    }

    [ComUnregisterFunction()]
    [ComVisible(false)]
    static void UnregisterFunction(Type registerType)
    {
        // Required for ArcGIS Component Category Registrar support
        ArcGISCategoryUnregistration(registerType);
    }

   public void Shutdown()
    {
        //TODO: Clean up resources
        application = null;
    }

   public void Startup(ref object initializationData)
    {
        application = initializationData as IApplication;

        if (application == null)
        {
            return;
        }

        //Listening to document events
        SetUpDocumentEvent(application.Document);

        //Listening to the initialized status event of the application 
        (application as IApplicationStatusEvents_Event).Initialized += new IApplicationStatusEvents_InitializedEventHandler(ApplicationInitialized);

        //Language configuration
        // ....

    }

    public void Load(IVariantStream stream)
    {
        //TODO: Load persisted data from document stream
        Marshal.ReleaseComObject(stream);
    }

    public void Save(IVariantStream stream)
    {
        //TODO: Save extension related data to document stream
        Marshal.ReleaseComObject(stream);
    }



    #region EVENTS

    //Wiring.
    private ESRI.ArcGIS.ArcMapUI.IDocumentEvents_Event m_docEvents = null;

    private void SetUpDocumentEvent(ESRI.ArcGIS.Framework.IDocument myDocument)
    {
        m_docEvents = myDocument as ESRI.ArcGIS.ArcMapUI.IDocumentEvents_Event;
        m_docEvents.NewDocument += new ESRI.ArcGIS.ArcMapUI.IDocumentEvents_NewDocumentEventHandler(OnNewDocument);
        m_docEvents.BeforeCloseDocument += new ESRI.ArcGIS.ArcMapUI.IDocumentEvents_BeforeCloseDocumentEventHandler(onBeforeCloseDocument);
    }

    public void OnNewDocument()
    {
        if (!newDocumentCreated)
        {
            //Not listening to this event anymore ensures no error will come from this handler later on
            m_docEvents.NewDocument -= new ESRI.ArcGIS.ArcMapUI.IDocumentEvents_NewDocumentEventHandler(OnNewDocument);
        }

        //If a new document is created, the boolean must become true to avoid re-loading the file from this class
        newDocumentCreated = true;
    }

    public bool onBeforeCloseDocument()
    {
        //If the new document hasn't been created and the current one is being closed, it means it was loaded as a NetworkAnalyst document
        if (!newDocumentCreated)
        {
            //Setting the current document as clean before closing avoids the opening of the Save Dialog Box
            IDocumentDirty2 dirtyDocument = application.Document as IDocumentDirty2;
            dirtyDocument.SetClean();

            //Not listening to this event anymore ensures no error will come from this handler later on
            m_docEvents.BeforeCloseDocument -= new ESRI.ArcGIS.ArcMapUI.IDocumentEvents_BeforeCloseDocumentEventHandler(onBeforeCloseDocument);
        }

        return false;
    }

    public void ApplicationInitialized()
    {
        System.Diagnostics.Debug.WriteLine("ApplicationInitialized");

        //When the application is fully initialized, if a new document hasn't been created, then the double clicked file isn't loaded properly yet
        if (!newDocumentCreated)
        {
            //Retrieve the double clicked file path from the application parameters
            ESRI.ArcGIS.Framework.ITemplates templates = application.Templates;
            doubleClickedFilePath = templates.get_Item(templates.Count - 1);

            //If the file path is correct, a new document is created (without user input and with the default template), then the double clicked file is opened
            //IApplicationDocument.OpenDocument is the method triggering the event that leads to calling the MobiAnalyst implementation of IPersistStream.Load (in Persist.cs)
            if (File.Exists(doubleClickedFilePath))
            {
                application.NewDocument(false, templates.get_Item(0));

                application.OpenDocument(doubleClickedFilePath);
            }
        }

        //Not listening to this event anymore ensures no error will come from this handler later on
        (application as IApplicationStatusEvents_Event).Initialized -= new IApplicationStatusEvents_InitializedEventHandler(ApplicationInitialized);
    }
}

And a part of the Persist.cs file:

public partial class OurSolver : IOurSolver, INASolver, INASolverSettings2, IPersistStream
{
    public void GetClassID(out Guid classID)
    {
        classID = Type.GetTypeFromProgID("MobiAnalystSolverCOM.MobiAnalystSolver").GUID;
    }

    public int IsDirty()
    {
        if (PersistDirty)
            return 1;
        else
            return 0;
    }

    public void Load(IStream paramStream)
    {
        // Load properties into a property set
        IPropertySet properties = new PropertySetClass();
        (properties as IPersistStream).Load(paramStream);

        //Get back all loaded values into C# system arrays
        object propertyNames;
        object propertyValues;
        properties.GetAllProperties(out propertyNames, out propertyValues);
        System.Array propNameArray = (System.Array)propertyNames;
        System.Array propValuesArray = (System.Array)propertyValues;

        // Use this property set to populate variables
        //For each parameter, check existence first
        int index = System.Array.IndexOf(propNameArray, "m_eOutputConnectivity");
        if (index >= 0) { outputConnectivity = (OutputConnectivityEnum)(propValuesArray.GetValue(index)); }

        //And so forth
        // ...

    }   
}

So now that I have (I hope) clearly explained the context and the problem, my question is: is it possible to load a custom extension generated mxd with our own loading method right from the start, preventing ArcMap to load it any other way ? or to force it to launch from a double clicked file as it launches from the .exe ? Maybe even to correct the file if this is a known problem, but I couldn't find anything clear about it.

11
  • Are you basically saying that IPersistStream.Load implemented on your extension is not being called when opening the document by double-clicking?
    – Petr Krebs
    May 28, 2013 at 10:15
  • Exactly. In the class loading the extension, there is an implementation of IPersistVariant.Load and Save, that is correctly called but can't implement the loading of the file. The IPersistStream methods are defined in a speficic file part of the main extension class, and is called only by loading the file from a launched ArcMap or IApplication.OpenDocument. But the documentation on that is so rare that I can't even find if it is a normal behavior or not.
    – Arnaud31
    May 28, 2013 at 11:33
  • Well, still not clear to me. Can you post a skeleton of how the extension class and the persistence implementation is organized?
    – Petr Krebs
    May 28, 2013 at 11:34
  • In the IPersistVariant implementation (implemented by the extension), you are responsible for saving and loading your extension-specific data. E.g. IVariantStream.Save should use the passed stream to save your OurSolver instance, which will result in IPersistStream.Save (implemented by OurSolver) being called. Analogically, you implement IVariantStream.Load in the same way.
    – Petr Krebs
    May 28, 2013 at 12:15
  • I did a little research in that direction, because I thought maybe the implementation was wrong (honestly I have a hard time differencing IPersistStream and IPersistVariant), but even if there is a problem here the crash of ArcMap happens before. To be absolutely clear, when debugging the extension, if the file is opened correctly it goes like Startup() > the file opens as "naked" > IPersistVariant.Load(). If it doesn't, it goes through Startup() and then the crash message on the screenshot above.
    – Arnaud31
    May 28, 2013 at 12:56

1 Answer 1

1

Ok, this is the most simplistic extension with persistence. Works as expected, see the comments.

[ComVisible(true)]
[ProgId("Test.TestExtension")]
[Guid("FD5E7EA9-DEFC-4AA5-81C8-A378BFA4D506")]
[ClassInterface(ClassInterfaceType.None)]
public class TestExtension : IExtension, IPersistVariant
{
    private IApplication _application;

    public void Startup(ref object initializationData)
    {
        _application = (IApplication)initializationData;
    }

    public void Shutdown()
    {
        _application = null;
    }

    public string Name
    {
        get { return GetType().FullName; }
    }

    public void Load(IVariantStream stream)
    {
        // when a document, which was previously saved with this extension (i.e. IPersistVariant.Save implemented
        // here was called with that document), is opened, this method gets called

        // it does not matter whether the document was opened from within ArcMap or by double-clicking it in the shell
    }

    public void Save(IVariantStream stream)
    {
        // this method gets called whenever a document is saved
    }

    public UID ID
    {
        get { return new UIDClass {Value = GetType().GUID.ToString("B")}; }
    }

    #region COM Registration Function(s)

    [ComRegisterFunction]
    [ComVisible(false)]
    static void RegisterFunction(Type registerType)
    {
        var regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
        ESRI.ArcGIS.ADF.CATIDs.MxExtension.Register(regKey);
    }

    [ComUnregisterFunction]
    [ComVisible(false)]
    static void UnregisterFunction(Type registerType)
    {
        var regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
        ESRI.ArcGIS.ADF.CATIDs.MxExtension.Unregister(regKey);
    }

    #endregion
}
7
  • I already tried to load from IPersistVariant.Load, but my understanding was that it doesn't load the data the same way than IPersistStream.Load. Correct me if I am wrong, but the first loads data found in sequence and the second works like a hashtable, to get what you want when you want. I guess this implementation was chosen for a reason, and I can't change something this big. Aside from that, it wouldn't solve the problem of the file making ArcMap crash before IPersistVariant.Load is even called.
    – Arnaud31
    May 29, 2013 at 7:12
  • The two are related but not the same. I think you misunderstand some of the core concepts around application extensions, as well as the persistence framework in ArcGIS. See Creating an application extension and Implementing persistence
    – Petr Krebs
    May 29, 2013 at 8:13
  • I had already read these pages and just read them again, but it doesn't really explain the differences between the two interfaces. I understand that IPersistVariant comes from ESRI and should be implemented for persistence, and IPersistStream from the .NET framework. Maybe having both in the project I'm working on is a poor choice, but since changing everything wouldn't help solving my problem because the crash happens earlier, I don't think it's worth it.
    – Arnaud31
    May 29, 2013 at 8:58
  • In my opinion, you don't need to radically change anything. It is perfectly ok to use both in one project. If the documentation doesn't explain the persistence mechanism, then I'm afraid I can't either.
    – Petr Krebs
    May 29, 2013 at 9:40
  • Did you ever actually try to run ArcMap without the extension to see if the crash occurs?
    – Petr Krebs
    May 29, 2013 at 9:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.