Tabbed feature inspector
TabbedInspector\AttachTabbedInspectorExtensionCommand.cs
// Copyright 2012 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using System.Diagnostics;

namespace TabbedFeatureInspector
{
  /// <summary>
  /// A command that attaches/detaches the 'tabbed inspector' extension class from 
  /// the feature class selected in the table of contents.
  /// In order to work correctly, the hosting application must implement and pass an 
  /// instance of IApplicationServices in the CustomProperty of its toolbar control.
  /// </summary>
  [Guid("14BAA8DD-677E-425b-B5CC-26F18B41D5B3")]
  [ClassInterface(ClassInterfaceType.None)]
  [ProgId("TabbedFeatureInspectorCS.AttachTabbedInspectorExtensionCommand")]
  public sealed class AttachTabbedInspectorExtensionCommand : BaseCommand
  {
    #region COM Registration Function(s)
    [ComRegisterFunction]
    [ComVisible(false)]
    public static void RegisterFunction(Type registerType)
    {
      // Required for ArcGIS Component Category Registrar support
      ArcGISCategoryRegistration(registerType);
    }

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

    #region ArcGIS Component Category Registrar generated code
    /// <summary>
    /// Required method for ArcGIS Component Category registration -
    /// Do not modify the contents of this method with the code editor.
    /// </summary>
    private static void ArcGISCategoryRegistration(Type registerType)
    {
      string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
      ControlsCommands.Register(regKey);
    }
    /// <summary>
    /// Required method for ArcGIS Component Category unregistration -
    /// Do not modify the contents of this method with the code editor.
    /// </summary>
    private static void ArcGISCategoryUnregistration(Type registerType)
    {
      string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
      ControlsCommands.Unregister(regKey);
    }

    #endregion
    #endregion

    IHookHelper m_hookHelper;
    IApplicationServices m_appServices;

    public AttachTabbedInspectorExtensionCommand()
    {
      m_category = "Developer Samples";
      m_caption = "Attach/Detach Tabbed Inspector Extension CS";
      m_message = "This command attaches or detaches the Tabbed Inspector class extension from the selected feature class.";
      m_toolTip = "This command attaches or detaches the Tabbed Inspector class extension from the selected feature class.";
      m_name = "TabbedInspector_AttachDetachExtension_CS";
    }

    /// <summary>
    /// Occurs when this command is created
    /// </summary>
    /// <param name="hook">Instance of the application</param>
    public override void OnCreate(object hook)
    {
      if (hook == null)
        return;

      m_hookHelper = new HookHelperClass();
      m_hookHelper.Hook = hook;
      m_appServices = null;
    }

    /// <summary>
    /// Occurs when this command is clicked
    /// </summary>
    public override void OnClick()
    {
      try
      {
        GetApplicationServices();

        IFeatureLayer fl = m_appServices.GetLayerSelectedInTOC();

        if (fl != null)
          AlterClassExtension(fl.FeatureClass);
        else
          m_appServices.SetStatusMessage("Couldn't attach the 'custom inspector' extension. No feature layer was selected in the Table of Contents.", true);
      }
      catch (Exception ex)
      {
        MessageBox.Show("Error: Could open the feature class. Original error: " + ex.Message);
      }
    }

    /// <summary>
    /// Obtains the IApplicationServices interface instance implemented by the hosting application.
    /// This is needed so the command can determine the selected layer, and update the application's status message.
    /// </summary>
    void GetApplicationServices()
    {
      if (m_appServices == null)
      {
        IToolbarControl2 toolbarControl = m_hookHelper.Hook as IToolbarControl2;
        if (toolbarControl == null)
          throw new ApplicationException(
            "Command appears to be running in an unexpected environment. Its hookHelper ought to be a toolbar control.");

        m_appServices = toolbarControl.CustomProperty as IApplicationServices;
        if (m_appServices == null)
          throw new ApplicationException(
            "Command appears to be running in an unexpected environment. The toolbar custom property ought to be an instance of IApplicationServices.");
      }
    }

    /// <summary>
    /// Perform the work contained in the delegate inside an exclusive schema lock.
    /// </summary>
    /// <param name="fc">The feature class whose schema is to be exclusively locked.</param>
    /// <param name="work">The work to be performed.</param>
    static void DoInSchemaLock(IFeatureClass fc, MethodInvoker work)
    {
      ISchemaLock schemaLock = (ISchemaLock)fc;
      try
      {
        // Exclusively lock the class schema.
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);
        
        // Do the work inside the schema lock
        work();
      }
      finally
      {
        // Release the exclusive lock on the featureclass' schema.
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
      }
    }

    /// <summary>
    /// This method attaches or detaches the "TabbedInspector" class extension to/from the specified
    /// feature class. If the featureclass already has an extension class, and it isn't the 'TabbedInspector' extension,
    /// the method does not modify the class extension.
    /// </summary>
    /// <param name="fc">The feature class to be altered.</param>
    /// <returns>Whether the operation succeeded (successful detach or attach).</returns>
    bool AlterClassExtension(IFeatureClass fc)
    {
      // Attempt to get access to schema-editing functionality on the feature class
      IClassSchemaEdit classSchemaEdit = fc as IClassSchemaEdit;
      if (classSchemaEdit == null)
      {
        m_appServices.SetStatusMessage("The selected feature class doesn't support attaching an extension class.", true);
        return false;
      }

      // Create a UID object holding the TabbedInspector's CLSID
      UID classUID = new UIDClass();
      classUID.Value = "{" + TabbedInspector.TabbedInspectorCLSID + "}";

      // Do the schema update within a schema lock.
      bool succeeded = false;
      DoInSchemaLock(fc, delegate
        {

          // Does the feature class already have an extension class associated with it?
          if (fc.EXTCLSID != null)
          {
            // The featureclass already has an extension attached.
            if (fc.EXTCLSID.Value.Equals(classUID.Value))
            {
              // The extension is the TabbedInspector extension. Detach it.
              classSchemaEdit.AlterClassExtensionCLSID(null, null);

              m_appServices.SetStatusMessage(
                string.Format("The 'custom inspector' extension class was detached from {0}.", fc.AliasName), false);
              succeeded = true;
            }
            else
            {
              //Don't mess with featureclasses that have some other existing extension class associated with them.
              m_appServices.SetStatusMessage(
                string.Format("{0} already has another extension class attached to it. No change was made.", fc.AliasName), true);
              succeeded = false;
            }
          }
          else
          {
            // There is no extension attached to the featureclass. Attach the TabbedInspector extension.
            classSchemaEdit.AlterClassExtensionCLSID(classUID, null);
            m_appServices.SetStatusMessage(
              string.Format("The 'custom inspector' extension class was attached to {0}.", fc.AliasName), false);
            succeeded = true;
          }
        });

      
      return succeeded;
    }
  }
}