Friday, January 9, 2009

Intercepting only GetLast and GetSpecific menu commands in TFS/VSTS 2008 plug-in

I had run into a need to intercept GetLast and GetSpecific menu commands in Visual Studio Source Control component that contact Team Foundation Server and retrieve specified items. I had quite a bit of trouble finding exactly what I needed in documentation, on MSDN library, or in MSDN forums. I have eventually found a way to do it. The class VSCommandInterceptor comes from this blog . Below are the relevant C# excerpts:

   1: using System;


   2: using System.IO;


   3: using System.Collections;


   4: using System.Collections.Generic;


   5: using Extensibility;


   6: using EnvDTE;


   7: using EnvDTE80;


   8: using EnvDTE90;


   9: using Microsoft.VisualStudio.TeamFoundation;


  10: using Microsoft.TeamFoundation.Client;


  11: using Microsoft.VisualStudio.TeamFoundation.VersionControl;


  12: using Microsoft.TeamFoundation.VersionControl.Client;


  13: using Microsoft.VisualStudio;


  14: using Microsoft.VisualStudio.CommandBars;


  15: namespace VSTS.AddIns.PureAwesomeness


  16: {


  17:      /// <summary>The object for implementing an Add-in.</summary>


  18:      /// <remarks>


  19:      /// </remarks>


  20:      /// <seealso class='IDTExtensibility2' />


  21:      public class Connect : IDTExtensibility2


  22:      {


  23:         #region Private Fields


  24:         /// <summary>


  25:         /// Version control extension


  26:         /// </summary>


  27:         private VersionControlExt _vcExt;


  28:         /// <summary>


  29:         /// Version control explorer window


  30:         /// </summary>


  31:         private VersionControlExplorerExt _explorer;


  32:         /// <summary>


  33:         /// Version control workspace


  34:         /// </summary>


  35:         private Workspace _workspace;


  36:         /// <summary>


  37:         /// Version control server


  38:         /// </summary>


  39:         private VersionControlServer _tfsVcSrv;


  40:         private DTE2 _applicationObject;


  41:            private AddIn _addInInstance;


  42:         private SortedList endWSItemColl = null;


  43:         private List<VSCommandInterceptor> cmdHandlers;


  44:         #endregion


  45:        /// <summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>


  46:        public Connect()


  47:        {


  48:        }


  49:         //this is no longer necessary, but still a pretty nice way to intercept context menu & button clicks


  50:         private void AttachToMenuCommands( DTE2 appObj )


  51:         {


  52:             if (cmdHandlers == null) { cmdHandlers = new List<VSCommandInterceptor>(); }


  53:             Commands cmds = appObj.Commands;


  54:             //find GetLast & GetSpecific commands in VSTS IDE (IDs 21010, 21011 for GetLast; 20500, 20501 for GetSpec)


  55:             foreach (Command c in cmds)


  56:             {


  57:                 if (c.ID == 21010 || c.ID == 21500 || c.ID == 21011 || c.ID == 21501)


  58:                 {


  59:                     VSCommandInterceptor vsCmd = new VSCommandInterceptor(appObj, new Guid(c.Guid), c.ID);


  60:                     vsCmd.BeforeExecute += new EventHandler<CommandExecuteEventArgs>(VSIDECommand_BeforeExecute);


  61:                     vsCmd.AfterExecute += new EventHandler<CommandExecuteEventArgs>(VSIDECommand_AfterExecute);


  62:                     cmdHandlers.Add(vsCmd);


  63:                     LogUtility.Instance.TraceInformation("Added Handling for Command: (Name {0}, ID {1}, GUID {2})", c.Name, c.ID, c.Guid);


  64:                 }


  65:             }


  66:             cmds = null;


  67:         }


  68:         //do useful work here


  69:         private void VSIDECommand_BeforeExecute(object sender, CommandExecuteEventArgs e)


  70:         {


  71:             //LogUtility.Instance.TraceProperties("Invoked VSIDECommand_BeforeExecute:",e);


  72:         }


  73:         private void VSIDECommand_AfterExecute(object sender, CommandExecuteEventArgs e)


  74:         {


  75:             //LogUtility.Instance.TraceProperties("Invoked VSIDECommand_AfterExecute:",e);


  76:         }


  77:         private void AttachToVcSrvEvents(DTE2 appObj)


  78:         {


  79:             // Capture the relevent TFS objects


  80:             try


  81:             {


  82:                 _vcExt = appObj.GetObject("Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt") as VersionControlExt;


  83:                 _explorer = _vcExt.Explorer;


  84:                 //open source control view, if not open to avoid exceptions


  85:                 if (_explorer == null || _explorer.Workspace == null || _explorer.Workspace.VersionControlServer == null)


  86:                 {


  87:                     appObj.ExecuteCommand("View.TfsSourceControlExplorer", "");


  88:                 }


  89:                 _workspace = _explorer.Workspace;


  90:                 AttachToMenuCommands(appObj);


  91:             }


  92:             catch (Exception ex)


  93:             {


  94:                 LogUtility.Instance.TraceError("Error attaching to TFS Version Control IDE events", ex);


  95:                 return;


  96:             }


  97:         }


  98:         #region IDTExtensibility2 implementation


  99:         /// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>


 100:         /// <param term='application'>Root object of the host application.</param>


 101:         /// <param term='connectMode'>Describes how the Add-in is being loaded.</param>


 102:         /// <param term='addInInst'>Object representing this Add-in.</param>


 103:         /// <seealso class='IDTExtensibility2' />


 104:         public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)


 105:         {


 106:             // Capture the plug-in context


 107:             try


 108:             {


 109:                 //LogUtility.Instance.WriteLine("Connecting the VSTS DateModifier addin");


 110:                 _applicationObject = (DTE2)application;


 111:                 _addInInstance = (AddIn)addInInst;


 112:                 //LogUtility.Instance.WriteLine("... connected the VSTS DateModifier addin");


 113:             }


 114:             catch (Exception ex)


 115:             {


 116:                 LogUtility.Instance.TraceError("Error loading plugin", ex);


 117:                 return;


 118:             }


 119:             AttachToVcSrvEvents();


 120:         }


 121:         /// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.</summary>


 122:         /// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param>


 123:         /// <param term='custom'>Array of parameters that are host application specific.</param>


 124:         /// <seealso class='IDTExtensibility2' />


 125:         public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)


 126:         {


 127:             if (cmdHandlers != null)


 128:             {


 129:                 foreach (VSCommandInterceptor cmd in cmdHandlers)


 130:                 {


 131:                     if (cmd != null) cmd.Dispose();


 132:                 }


 133:                 cmdHandlers = null;


 134:             }


 135:         }


 136:         /// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>


 137:         /// <param term='custom'>Array of parameters that are host application specific.</param>


 138:         /// <seealso class='IDTExtensibility2' />          


 139:         public void OnAddInsUpdate(ref Array custom)


 140:         {


 141:         }


 142:         /// <summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>


 143:         /// <param term='custom'>Array of parameters that are host application specific.</param>


 144:         /// <seealso class='IDTExtensibility2' />


 145:         public void OnStartupComplete(ref Array custom)


 146:         {


 147:         }


 148:         /// <summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>


 149:         /// <param term='custom'>Array of parameters that are host application specific.</param>


 150:         /// <seealso class='IDTExtensibility2' />


 151:         public void OnBeginShutdown(ref Array custom)


 152:         {


 153:         }


 154:         #endregion


 155:     }


 156: }




VSCommandInterceptor (a little modified from the original) follows:





   1: using System;


   2: using System.Collections.Generic;


   3: using System.Text;


   4: using EnvDTE;


   5: using EnvDTE80;


   6: namespace VSTS.AddIns.PureAwesomeness


   7: {


   8:     public class VSCommandInterceptor : IDisposable


   9:     {


  10:         private DTE2 _dte;


  11:         private Guid _guid;


  12:         private int _id;


  13:         private bool _isDisposed;


  14:         public VSCommandInterceptor(DTE2 dte, Guid commandGuid, int commandId)


  15:         {


  16:             this._dte = dte;


  17:             this._guid = commandGuid;


  18:             this._id = commandId;


  19:             if(CommandEvents != null)


  20:             {


  21:                 CommandEvents.AfterExecute += new _dispCommandEvents_AfterExecuteEventHandler(OnAfterExecute);


  22:                 CommandEvents.BeforeExecute += new _dispCommandEvents_BeforeExecuteEventHandler(OnBeforeExecute);


  23:             }


  24:         }


  25:         public event EventHandler<CommandExecuteEventArgs> AfterExecute;


  26:         public event EventHandler<CommandExecuteEventArgs> BeforeExecute;


  27:         private CommandEvents commandEvents;


  28:         protected CommandEvents CommandEvents


  29:         {


  30:             get


  31:             {


  32:                 if(commandEvents == null)


  33:                 {


  34:                     if(_dte != null)


  35:                     {


  36:                         commandEvents = _dte.Events.get_CommandEvents(_guid.ToString("B"), _id) as CommandEvents;


  37:                     }


  38:                 }


  39:                 return commandEvents;


  40:             }


  41:         }


  42:         public void Dispose()


  43:         {


  44:             this.Dispose(true);


  45:             GC.SuppressFinalize(this);


  46:         }


  47:         private void Dispose(bool disposing)


  48:         {


  49:             if(!this._isDisposed && disposing)


  50:             {


  51:                 if(CommandEvents != null)


  52:                 {


  53:                     try


  54:                     {


  55:                         CommandEvents.AfterExecute -= OnAfterExecute;


  56:                         CommandEvents.BeforeExecute -= OnBeforeExecute;


  57:                     }


  58:                     catch (Exception ex)


  59:                     {


  60:                         LogUtility.Instance.TraceError("Exception while detaching VSTS IDE command handlers", ex);


  61:                     }


  62:                 }


  63:                 this._isDisposed = true;


  64:             }


  65:         }


  66:         private void OnAfterExecute(string Guid, int ID, object CustomIn, object CustomOut)


  67:         {


  68:             if(AfterExecute != null)


  69:             {


  70:                 AfterExecute(this, new CommandExecuteEventArgs(Guid,ID,CustomIn,CustomOut,false));


  71:             }


  72:         }


  73:         private void OnBeforeExecute(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)


  74:         {


  75:             if(BeforeExecute != null)


  76:             {


  77:                 BeforeExecute(this, new CommandExecuteEventArgs(Guid,ID,CustomIn,CustomOut,CancelDefault));


  78:             }


  79:         }


  80:     }


  81: //Usage


  82: //    VSCommandInterceptor interceptor =


  83: //        new VSCommandInterceptor(


  84: //            serviceProvider,


  85: //            typeof(Microsoft.VisualStudio.VSConstants.VSStd97CmdID).GUID,


  86: //            (int)Microsoft.VisualStudio.VSConstants.VSStd97CmdID.CleanSln);


  87: //    interceptor.BeforeExecute += new EventHandler<EventArgs>(BeforeExecute);


  88:     //private void BeforeExecute(object sender, EventArgs e)


  89:     //{


  90:     //    //TODO: Provide logic


  91:     //}


  92:     public class CommandExecuteEventArgs : System.EventArgs


  93:     {


  94:         #region private fields


  95:         private object customIn;


  96:         private object customOut;


  97:         private string commandGuid;


  98:         private int commandId;


  99:         private bool cancelDefault;


 100:        #endregion


 101:         public CommandExecuteEventArgs(string guid, int id, object objIn, object objOut, bool cancelDefaultAction)


 102:         {


 103:             customIn = objIn;


 104:             customOut = objOut;


 105:             commandGuid = guid;


 106:             commandId = id;


 107:             cancelDefault = cancelDefaultAction;


 108:         }


 109:         public object CustomIn


 110:         {


 111:             get { return customIn; }


 112:         }


 113:         public object CustomOut


 114:         {


 115:             get { return customOut; }


 116:         }


 117:         public string CommandGuid


 118:         {


 119:             get { return commandGuid; }


 120:         }


 121:         public int CommandId


 122:         {


 123:             get { return commandId; }


 124:         }


 125:         public bool CandelDefault


 126:         {


 127:             get { return cancelDefault; }


 128:         }


 129:     }


 130: }