Tabbed feature inspector
TabbedInspector\AttachTabbedInspectorExtensionCommand.vb
' 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.
' 

Imports System
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports ESRI.ArcGIS.ADF.BaseClasses
Imports ESRI.ArcGIS.ADF.CATIDs
Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.Controls
Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.Geodatabase
Imports 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("61B2CFFB-35DB-4aec-8DA2-C40C20C76901")> _
  <ClassInterface(ClassInterfaceType.None)> _
  <ProgId("TabbedFeatureInspectorVB.AttachTabbedInspectorExtensionCommand")> _
  Public Class AttachTabbedInspectorExtensionCommand
    Inherits BaseCommand
#Region "COM Registration Function(s)"
    <ComRegisterFunction()> _
    <ComVisible(False)> _
    Public Shared Sub RegisterFunction(ByVal registerType As Type)
      ' Required for ArcGIS Component Category Registrar support
      ArcGISCategoryRegistration(registerType)
    End Sub

    <ComUnregisterFunction()> _
    <ComVisible(False)> _
    Public Shared Sub UnregisterFunction(ByVal registerType As Type)
      ' Required for ArcGIS Component Category Registrar support
      ArcGISCategoryUnregistration(registerType)
    End Sub

#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 Shared Sub ArcGISCategoryRegistration(ByVal registerType As Type)
      Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}", registerType.GUID)
      ControlsCommands.Register(regKey)
    End Sub
    '/ <summary>
    '/ Required method for ArcGIS Component Category unregistration -
    '/ Do not modify the contents of this method with the code editor.
    '/ </summary>
    Private Shared Sub ArcGISCategoryUnregistration(ByVal registerType As Type)
      Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}", registerType.GUID)
      ControlsCommands.Unregister(regKey)
    End Sub

#End Region
#End Region

    Dim m_hookHelper As IHookHelper
    Dim m_appServices As IApplicationServices
    Dim workHelper As System.Windows.Forms.MethodInvoker = AddressOf Work
    Dim fc As IFeatureClass
    Dim succeeded As Boolean = False 'return value of the 'Work' delegate

    Public Sub New()
      m_category = "Developer Samples"
      m_caption = "Attach/Detach Tabbed Inspector Extension VB"
      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_VB"
    End Sub

    '/ <summary>
    '/ Occurs when this command is created
    '/ </summary>
    '/ <param name="hook">Instance of the application</param>
    Public Overrides Sub OnCreate(ByVal hook As Object)
      If hook Is Nothing Then
        Return
      End If

      m_hookHelper = New HookHelperClass()
      m_hookHelper.Hook = hook
      m_appServices = Nothing
    End Sub

    '/ <summary>
    '/ Occurs when this command is clicked
    '/ </summary>
    Public Overrides Sub OnClick()
      Try
        GetApplicationServices()

        Dim fl As IFeatureLayer = m_appServices.GetLayerSelectedInTOC()

        If Not fl Is Nothing Then
          fc = fl.FeatureClass
          AlterClassExtension()
        Else
          m_appServices.SetStatusMessage("Couldn't attach the 'custom inspector' extension. No feature layer was selected in the Table of Contents.", True)
        End If
      Catch ex As Exception
        MessageBox.Show("Error: Could open the feature class. Original error: " + ex.Message)
      End Try
    End Sub

    '/ <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>
    Private Sub GetApplicationServices()
      If m_appServices Is Nothing Then
        Dim toolbarControl As IToolbarControl2 = m_hookHelper.Hook
        If toolbarControl Is Nothing Then
          Throw New ApplicationException( _
            "Command appears to be running in an unexpected environment. Its hookHelper ought to be a toolbar control.")
        End If

        m_appServices = toolbarControl.CustomProperty
        If m_appServices Is Nothing Then
          Throw New ApplicationException( _
            "Command appears to be running in an unexpected environment. The toolbar custom property ought to be an instance of IApplicationServices.")
        End If
      End If
    End Sub

    '/ <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>
    Shared Sub DoInSchemaLock(ByVal fc As IFeatureClass, ByVal work As MethodInvoker)
      Dim schemaLock As ISchemaLock = DirectCast(fc, ISchemaLock)
      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)
      End Try
    End Sub

    '/ <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 isnt 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>
    Private Function AlterClassExtension() As Boolean
      ' Attempt to get access to schema-editing functionality on the feature class
      Dim classSchemaEdit As IClassSchemaEdit = TryCast(fc, IClassSchemaEdit)
      If classSchemaEdit Is Nothing Then
        m_appServices.SetStatusMessage("The selected feature class doesn't support attaching an extension class.", True)
        Return False
      End If

      ' Do the schema update within a schema lock.
      DoInSchemaLock(fc, workHelper)

      AlterClassExtension = succeeded
    End Function

    Public Sub Work()
      succeeded = False
      ' Attempt to get access to schema-editing functionality on the feature class
      Dim classSchemaEdit As IClassSchemaEdit = DirectCast(fc, IClassSchemaEdit)

      ' Create a UID object holding the TabbedInspector's CLSID
      Dim classUID As UID = New UIDClass()
      classUID.Value = "{" + TabbedInspectorCLSID.TabbedInspectorCLSID + "}"

      ' Does the feature class already have an extension class associated with it?
      If Not fc.EXTCLSID Is Nothing Then
        ' The featureclass already has an extension attached.
        If fc.EXTCLSID.Value.Equals(classUID.Value) Then
          ' The extension is the TabbedInspector extension. Detach it.
          classSchemaEdit.AlterClassExtensionCLSID(Nothing, Nothing)

          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
        End If
      Else
        ' There is no extension attached to the featureclass. Attach the TabbedInspector extension.
        classSchemaEdit.AlterClassExtensionCLSID(classUID, Nothing)
        m_appServices.SetStatusMessage( _
              String.Format("The 'custom inspector' extension class was attached to {0}.", fc.AliasName), False)
        succeeded = True
      End If
    End Sub
  End Class
End Namespace