# Sunday, June 06, 2010

One of the really useful Visual Studio add-ins is Smart Paster. It adds a "Paste As." context menu that allows you to paste in the clipboard text as a comment, a correctly quoted string or a string builder.

smartpaster

There are versions for VS 2003, 2005 and 2008. But not 2010.

Sometimes you can just copy in the dll and addin file into the VS 2010 Addins folder (.\Documents\Visual Studio 2010\Addins) and edit the addin file (it's just xml) to say "10.0" instead of "9.0". But that doesn't work for SmartPaster - VS 2010 shows an error and insists on disabling the addin.

The VS 2008 download includes the source, so I tried to upgrade it.

It turns out the problem is when it creates the context menus it sets the CommandBarButton.FaceId property (to show an image next to the text). But in VS2010 that throws a DeprecatedException.

Ok, simple fix, but the original source is old code with a fairly high WTF-per-line ratio (well, it was written 2004, .Net 1.1). Before long I had ported it from VB.Net to C# (thanks Telerik) and rewritten large parts (mostly refactoring with Coderush). I simplified by dropping the "regionize" stuff (never use it), the VB support and the configuration form. Here's my code- you can create a new Extensibility Addin project, replace the Connect class and add the SmartPaster class- see below.

It's still a port, so certainly not as clean as something just written from scratch. And perhaps VS2010 has nicer ways of doing all these things now the code window is a WPF control - the EnvDTE objects are ugly and hard to use. Anyway, thanks to Alex Papadimoulis for the original code.

Update: download the binary and unzip into your Addins folder. 

Update 2: source and binary are also on Codeplex. (It's exactly the same as shown here.)


   1:  using System;
   2:  using System.Collections;
   3:  using EnvDTE;
   4:  using EnvDTE80;
   5:  using Extensibility;
   6:  using Microsoft.VisualStudio.CommandBars;
   7:   
   8:  namespace SmartPaster2010
   9:  {
  10:      ///<summary>The object for implementing an Add-in.</summary>
  11:      ///<seealso class='IDTExtensibility2' />
  12:      public class Connect : IDTExtensibility2
  13:      {
  14:          private readonly ArrayList _pasteAsButtons;
  15:          private readonly SmartPaster _smartPaster;
  16:          private CommandBarPopup _pasteAsPopup;
  17:   
  18:          ///<summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
  19:          public Connect()
  20:          {
  21:              _pasteAsButtons = new ArrayList();
  22:              _smartPaster = new SmartPaster();
  23:          }
  24:   
  25:          ///<summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
  26:          ///<param term='application'>Root object of the host application.</param>
  27:          ///<param term='connectMode'>Describes how the Add-in is being loaded.</param>
  28:          ///<param term='addInInst'>Object representing this Add-in.</param>
  29:          ///<seealso class='IDTExtensibility2' />
  30:          public void OnConnection(object application, ext_ConnectMode connectMode, 
object addInInst, ref Array custom)
  31:          {
  32:              _applicationObject = (DTE2)application;
  33:              _addInInstance = (AddIn)addInInst;
  34:   
  35:   
  36:              //check for the commands
  37:              bool cmdExists = false;
  38:              foreach (Command cmd in _applicationObject.Commands)
  39:              {
  40:                  if (cmd.Name.EndsWith("PasteAsComment", StringComparison.OrdinalIgnoreCase))
  41:                  {
  42:                      cmdExists = true;
  43:                      break;
  44:                  }
  45:              }
  46:   
  47:              try
  48:              {
  49:                  if (!cmdExists)
  50:                  {
  51:                      AddPasteAsCommands();
  52:                  }
  53:   
  54:                  if (connectMode == ext_ConnectMode.ext_cm_Startup && _pasteAsPopup == null)
  55:                  {
  56:                      //Add items to the Context (Right-Click) Menu
  57:                      //find the position of the &Paste command
  58:                      int position = 0;
  59:   
  60:                      CommandBar codeWindow = _applicationObject.CommandBars["Code Window"];
  61:   
  62:                      for (int i = 1; i <= codeWindow.Controls.Count; i++)
  63:                      {
  64:                          if (codeWindow.Controls[i].Caption == "&Paste")
  65:                          {
  66:                              position = i;
  67:                              break;
  68:                          }
  69:                      }
  70:   
  71:                      //add the popup menu "Paste As...", which may already be on the menu
  72:                      _pasteAsPopup = (CommandBarPopup)codeWindow.Controls.Add(
(int)MsoControlType.msoControlPopup, 1, Type.Missing, position + 1, Type.Missing);
  73:                      _pasteAsPopup.Caption = "Paste As ...";
  74:                      AddPasteAsButtons();
  75:                  }
  76:   
  77:              }
  78:              catch (Exception ex)
  79:              {
  80:                  System.Diagnostics.Debug.WriteLine(ex.Message);
  81:              }
  82:          }
  83:   
  84:          private void AddPasteAsCommands()
  85:          {
  86:              //no configure or regionize because I never use 'em
  87:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsComment", "Paste As Comment", "Pastes clipboard text as a comment.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  88:   
  89:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsString", "Paste As String", "Pastes clipboard text as a string literal.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  90:   
  91:              _applicationObject.Commands.AddNamedCommand(_addInInstance, "PasteAsStringBuilder", "Paste As StringBuilder", "Pastes clipboard text as a stringbuilder.", true, 22, null, Convert.ToInt32(vsCommandStatus.vsCommandStatusSupported) + Convert.ToInt32(vsCommandStatus.vsCommandStatusEnabled));
  92:          }
  93:   
  94:   
  95:          private void AddPasteAsButtons()
  96:          {
  97:   
  98:              //now the buttons
  99:              CommandBarButton pasteAsButton;
 100:   
 101:              //add "Comment"
 102:              pasteAsButton = AddCommandButton();
 103:              pasteAsButton.Caption = "Comment";
 104:              pasteAsButton.TooltipText = "Inserts clipboard with each line prefixed with a comment character";
 105:              pasteAsButton.Click += PasteAsComment;
 106:              _pasteAsButtons.Add(pasteAsButton);
 107:   
 108:              //add "String"
 109:              pasteAsButton = AddCommandButton();
 110:              pasteAsButton.Caption = "String";
 111:              pasteAsButton.TooltipText = "Inserts enquoted clipboard text with line breaks and other characters escaped";
 112:              pasteAsButton.Click += PasteAsString;
 113:              _pasteAsButtons.Add(pasteAsButton);
 114:   
 115:              //add "StringBuilder"
 116:              pasteAsButton = AddCommandButton();
 117:              pasteAsButton.Caption = "StringBuilder";
 118:              pasteAsButton.TooltipText = "Inserts enquoted clipboard text built up by a stringbuilder.";
 119:              pasteAsButton.Click += PasteAsStringBuilder;
 120:              _pasteAsButtons.Add(pasteAsButton);
 121:   
 122:          }
 123:   
 124:          private CommandBarButton AddCommandButton()
 125:          {
 126:              var pasteAsButton = (CommandBarButton)_pasteAsPopup.Controls.Add((int)MsoControlType.msoControlButton);
 127:              //in 2010, CommandBarButton.FaceId throws a DeprecatedException.
 128:              //pasteAsButton.FaceId = 22;
 129:              pasteAsButton.Visible = true;
 130:              return pasteAsButton;
 131:          }
 132:   
 133:          #region "PasteAs Handlers"
 134:   
 135:          ///<summary>
 136:          ///Occurs when the user clicks the PasteAsString button.
 137:          ///</summary>
 138:          ///<param name="ctrl">
 139:          ///Denotes the CommandBarButton control that initiated the event. 
 140:          ///</param>
 141:          ///<param name="cancelDefault">
 142:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 143:          ///</param>
 144:          private void PasteAsString(CommandBarButton ctrl, ref bool cancelDefault)
 145:          {
 146:              _smartPaster.PasteAsString(_applicationObject);
 147:          }
 148:   
 149:   
 150:          ///<summary>
 151:          ///Occurs when the user clicks the PasteAsComment button.
 152:          ///</summary>
 153:          ///<param name="ctrl">
 154:          ///Denotes the CommandBarButton control that initiated the event. 
 155:          ///</param>
 156:          ///<param name="cancelDefault">
 157:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 158:          ///</param>
 159:          private void PasteAsComment(CommandBarButton ctrl, ref bool cancelDefault)
 160:          {
 161:              _smartPaster.PasteAsComment(_applicationObject);
 162:          }
 163:   
 164:          ///<summary>
 165:          ///Occurs when the user clicks the PasteAsStringBuilder button.
 166:          ///</summary>
 167:          ///<param name="ctrl">
 168:          ///Denotes the CommandBarButton control that initiated the event. 
 169:          ///</param>
 170:          ///<param name="cancelDefault">
 171:          ///False if the default behavior associated with the CommandBarButton control occurs, unless its canceled by another process or add-in. 
 172:          ///</param>
 173:          private void PasteAsStringBuilder(CommandBarButton ctrl, ref bool cancelDefault)
 174:          {
 175:              _smartPaster.PasteAsStringBuilder(_applicationObject);
 176:          }
 177:          #endregion
 178:   
 179:          #region "Exec"
 180:   
 181:          ///<summary>
 182:          /// Implements the Exec method of the IDTCommandTarget interface.
 183:          /// This is called when the command is invoked.
 184:          ///</summary>
 185:          ///<param name='commandName'>
 186:          ///        The name of the command to execute.
 187:          ///</param>
 188:          ///<param name='executeOption'>
 189:          ///        Describes how the command should be run.
 190:          ///</param>
 191:          ///<param name='varIn'>
 192:          ///        Parameters passed from the caller to the command handler.
 193:          ///</param>
 194:          ///<param name='varOut'>
 195:          ///        Parameters passed from the command handler to the caller.
 196:          ///</param>
 197:          ///<param name='handled'>
 198:          ///        Informs the caller if the command was handled or not.
 199:          ///</param>
 200:          ///<seealso class='Exec' />
 201:          public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
 202:          {
 203:              handled = false;
 204:              if ((executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault))
 205:              {
 206:                  handled = true;
 207:                  switch (commandName)
 208:                  {
 209:                      //case "SmartPaster.Connect.Configure":
 210:                      //    //show the config form
 211:                      //    SmartPasterForm myForm = new SmartPasterForm();
 212:                      //    myForm.ShowDialog();
 213:                      //    //since config may have changed, show/hide buttons
 214:                      //    EnableContextMenuButtons();
 215:   
 216:                      //    break;
 217:                      case "SmartPaster.Connect.PasteAsComment":
 218:                          _smartPaster.PasteAsComment(_applicationObject);
 219:                          break;
 220:                      case "SmartPaster.Connect.PasteAsString":
 221:                          _smartPaster.PasteAsString(_applicationObject);
 222:                          break;
 223:                      case "SmartPaster.Connect.PasteAsStringBuilder":
 224:                          _smartPaster.PasteAsStringBuilder(_applicationObject);
 225:                          break;
 226:                      //case "SmartPaster.Connect.PasteAsRegion":
 227:                      //    _smartPaster.PasteAsRegion(_applicationObject);
 228:                      //    break;
 229:                      default:
 230:                          handled = false;
 231:                          break;
 232:                  }
 233:              }
 234:          }
 235:          #endregion
 236:   
 237:          #region Standard Template Stuff
 238:          ///<summary>
 239:          ///Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.
 240:          ///</summary>
 241:          ///<param name="disconnectMode">The disconnect mode.</param>
 242:          ///<param name="custom">The custom.</param>
 243:          ///<seealso class="IDTExtensibility2"/>
 244:          public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
 245:          {
 246:              if (_pasteAsPopup != null && 
 247:                  (disconnectMode == ext_DisconnectMode.ext_dm_UserClosed || disconnectMode == ext_DisconnectMode.ext_dm_HostShutdown))
 248:              {
 249:                  _pasteAsPopup.Delete(true);
 250:              }
 251:          }
 252:   
 253:          ///<summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
 254:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 255:          ///<seealso class='IDTExtensibility2' />        
 256:          public void OnAddInsUpdate(ref Array custom)
 257:          {
 258:          }
 259:   
 260:          ///<summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
 261:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 262:          ///<seealso class='IDTExtensibility2' />
 263:          public void OnStartupComplete(ref Array custom)
 264:          {
 265:          }
 266:   
 267:          ///<summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
 268:          ///<param term='custom'>Array of parameters that are host application specific.</param>
 269:          ///<seealso class='IDTExtensibility2' />
 270:          public void OnBeginShutdown(ref Array custom)
 271:          {
 272:          }
 273:   
 274:          private DTE2 _applicationObject;
 275:          private AddIn _addInInstance;
 276:          #endregion
 277:   
 278:          ///<summary>
 279:          ///Queries the status.
 280:          ///</summary>
 281:          ///<param name="commandName">Name of the command.</param>
 282:          ///<param name="neededText">The needed text.</param>
 283:          ///<param name="statusOption">The status option.</param>
 284:          ///<param name="commandText">The command text.</param>
 285:          public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus statusOption, ref object commandText)
 286:          {
 287:              if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
 288:              {
 289:                  if (commandName.StartsWith("SmartPaster.Connect"))
 290:                  {
 291:                      if (((_applicationObject.ActiveDocument != null)) && ((_applicationObject.ActiveDocument.Object("TextDocument") != null)))
 292:                      {
 293:                          statusOption = vsCommandStatus.vsCommandStatusEnabled | vsCommandStatus.vsCommandStatusSupported;
 294:                      }
 295:                      else
 296:                      {
 297:                          statusOption = vsCommandStatus.vsCommandStatusSupported;
 298:                      }
 299:                  }
 300:                  else
 301:                  {
 302:                      statusOption = vsCommandStatus.vsCommandStatusUnsupported;
 303:                  }
 304:              }
 305:          }
 306:      }
 307:  }

Here's the SmartPaster class.

   1:  using System;
   2:  using System.IO;
   3:  using System.Text;
   4:  using System.Windows.Forms; //clipboard
   5:  using EnvDTE;
   6:  using EnvDTE80;
   7:   
   8:  namespace SmartPaster2010
   9:  {
  10:      /// <summary>
  11:      /// Class responsible for doing the pasting/manipulating of clipdata.
  12:      /// </summary>
  13:      internal sealed class SmartPaster
  14:      {
  15:          /// <summary>
  16:          ///  Convient property to retrieve the clipboard text from the clipboard
  17:          /// </summary>
  18:          private static string ClipboardText
  19:          {
  20:              get
  21:              {
  22:                  IDataObject iData = Clipboard.GetDataObject();
  23:                  if (iData.GetDataPresent(DataFormats.Text))
  24:                      return Convert.ToString(iData.GetData(DataFormats.Text));
  25:                  return string.Empty;
  26:              }
  27:          }
  28:   
  29:          #region "Stringinize"
  30:          /// <summary>
  31:          /// Stringinizes text passed to it for use in C#
  32:          /// </summary>
  33:          /// <param name="txt">Text to be Stringinized</param>
  34:          /// <returns>C# Stringinized text</returns>
  35:          private static string StringinizeInCs(string txt)
  36:          {
  37:              //c# quote character -- really just a "
  38:              const string qChr = "\"";
  39:   
  40:              //sb to work with
  41:              var sb = new StringBuilder(txt);
  42:   
  43:              //escape appropriately
  44:              //escape the quotes with ""
  45:              sb.Replace(qChr, qChr + qChr);
  46:   
  47:              //insert " at beginning and end
  48:              sb.Insert(0, "@" + qChr);
  49:              sb.Append(qChr);
  50:              return sb.ToString();
  51:          }
  52:          #endregion
  53:   
  54:          #region "Commentize"
  55:          /// <summary>
  56:          /// Commentizes text passed to it for use in C#
  57:          /// </summary>
  58:          /// <param name="txt">Text to be Stringinized</param>
  59:          /// <returns>C# Commentized text</returns>
  60:          private static string CommentizeInCs(string txt)
  61:          {
  62:              const string cmtChar = "//";
  63:   
  64:              var sb = new StringBuilder(txt.Length);
  65:   
  66:              //process the passed string (txt), one line at a time
  67:              //the original was horrible WTF code
  68:              using (var reader = new StringReader(txt))
  69:              {
  70:                  string line;
  71:                  while ((line = reader.ReadLine()) != null)
  72:                  {
  73:                      sb.AppendLine(cmtChar + line);
  74:                  }
  75:              }
  76:   
  77:              return sb.ToString();
  78:          }
  79:          #endregion
  80:   
  81:          #region "Stringbuilderize"
  82:          private static string StringbuilderizeInCs(string txt, string sbName)
  83:          {
  84:              //c# quote character -- really just a "
  85:              const string qChr = "\"";
  86:   
  87:              //sb to work with
  88:              var sb = new StringBuilder(txt);
  89:   
  90:              //escape \,", and {}
  91:              sb.Replace(qChr, qChr + qChr);
  92:   
  93:              //process the passed string (txt), one line at a time
  94:   
  95:              //dump the stringbuilder into a temp string
  96:              string fullString = sb.ToString();
  97:              sb.Clear(); //lovely .net 4 - sb.Remove(0, sb.Length);
  98:   
  99:              //the original was horrible WTF code
 100:              using (var reader = new StringReader(fullString))
 101:              {
 102:                  string line;
 103:                  while ((line = reader.ReadLine()) != null)
 104:                  {
 105:                      sb.Append(sbName + ".AppendLine(");
 106:                      sb.Append("@" + qChr);
 107:                      sb.Append(line.Replace("\t", "\\t"));
 108:                      sb.AppendLine(qChr + ");");
 109:                  }
 110:              }
 111:   
 112:              //TODO: Better '@"" + ' replacement to not cover inside strings
 113:              sb.Replace("@" + qChr + qChr + " + ", "");
 114:   
 115:              //add the dec statement
 116:              sb.Insert(0, "StringBuilder " + sbName + " = new StringBuilder(" + txt.Length + ");" + Environment.NewLine);
 117:   
 118:              //and return
 119:              return sb.ToString();
 120:          }
 121:          #endregion
 122:   
 123:          /// <summary>
 124:          /// Inserts text at current cursor location in the application
 125:          /// </summary>
 126:          /// <param name="application">application with activewindow</param>
 127:          /// <param name="text">text to insert</param>
 128:          private static void Paste(DTE2 application, string text)
 129:          {
 130:              //get the text document
 131:              var txt = (TextDocument)application.ActiveDocument.Object("TextDocument");
 132:   
 133:              //get an edit point
 134:              EditPoint ep = txt.Selection.ActivePoint.CreateEditPoint();
 135:   
 136:              //get a start point
 137:              EditPoint sp = txt.Selection.ActivePoint.CreateEditPoint();
 138:   
 139:              //open the undo context
 140:              bool isOpen = application.UndoContext.IsOpen;
 141:              if (!isOpen)
 142:                  application.UndoContext.Open("SmartPaster");
 143:   
 144:              //clear the selection
 145:              if (!txt.Selection.IsEmpty)
 146:                  txt.Selection.Delete();
 147:   
 148:              //insert the text
 149:              //ep.Insert(Indent(text, ep.LineCharOffset))
 150:              ep.Insert(text);
 151:   
 152:              //smart format
 153:              sp.SmartFormat(ep);
 154:   
 155:              //close the context
 156:              if (!isOpen)
 157:                  application.UndoContext.Close();
 158:          }
 159:   
 160:          #region "Paste As ..."
 161:   
 162:          /// <summary>
 163:          /// Public method to paste and format clipboard text as string the cursor 
 164:          /// location for the configured or active window's langage .
 165:          /// </summary>
 166:          /// <param name="application">application to insert</param>
 167:          public void PasteAsString(DTE2 application)
 168:          {
 169:              Paste(application, StringinizeInCs(ClipboardText));
 170:          }
 171:   
 172:          /// <summary>
 173:          /// Public method to paste and format clipboard text as comment the cursor 
 174:          /// location for the configured or active window's langage .
 175:          /// </summary>
 176:          /// <param name="application">application to insert</param>
 177:          public void PasteAsComment(DTE2 application)
 178:          {
 179:              Paste(application, CommentizeInCs(ClipboardText));
 180:          }
 181:   
 182:   
 183:          /// <summary>
 184:          /// Public method to paste format clipboard text into a specified region
 185:          /// </summary>
 186:          /// <param name="application">application to insert</param>
 187:          public void PasteAsRegion(DTE2 application)
 188:          {
 189:              //get the region name
 190:              const string region = "myRegion";
 191:   
 192:              //it's so simple, we really don't need a function
 193:              string csRegionized = "#region " + region + Environment.NewLine + ClipboardText + Environment.NewLine + "#endregion";
 194:   
 195:              //and paste
 196:              Paste(application, csRegionized);
 197:          }
 198:   
 199:          /// <summary>
 200:          /// Public method to paste and format clipboard text as stringbuilder the cursor 
 201:          /// location for the configured or active window's langage .
 202:          /// </summary>
 203:          /// <param name="application">application to insert</param>
 204:          public void PasteAsStringBuilder(DTE2 application)
 205:          {
 206:              const string stringbuilder = "sb";
 207:              Paste(application, StringbuilderizeInCs(ClipboardText, stringbuilder));
 208:          }
 209:   
 210:          #endregion
 211:      }
 212:  }
posted on Sunday, June 06, 2010 5:30:02 PM (Romance Daylight Time, UTC+02:00)  #    Comments [3]
# Tuesday, May 04, 2010

In a large Visual Studio solution, there are better ways of finding a particular class or method than visually hunting through the solution explorer tree.

Visual Studio 2008


Good old Goto Definition (F12) is great.
To quickly go back to where you pressed F12, Control - (minus) is useful but not very well known.

 vs_findallrefsBut what if you want to find where an interface is implemented, a method overridden or called from elsewhere?
Find All References (Control K, R) is painfully slow and it dumps everything in the Find Symbol Results window with the full file path (you can hack the registry to fix the format, but still...).

Within a source file, incremental search (Control I) is a great hidden secret - no dialog box, just start type and it immediately jumps to the first match. It has a big flaw: it doesn't look in hidden sections (closed regions).

To find a class or method which isn't in the source in front of you, you use Quick Find (Control F) or Find In Finds (Control Shift F). It's slow, and opens in a docked Find window. All those docked windows quickly become confusing.

 vs_quickfind

Visual Studio 2010


VS 2010 still has Slowly Find All References, but it now has a couple of extra goodies.

vs2010_callhierarchy View Call Hierarchy (Control K, T) has a nice graph of calls to/ calls from which is recursive. In other words, just like Reflector's Analyzer.

vs2010_navigateto There's a neat new Navigate To window (Control comma). It's not a docked window, and has a really cool incremental search of the solution. The search box allows camel hump searching (just the capitals), as well as sub-strings.

Coderush


coderush_referencesIf you're using Coderush Pro, you've had a nice version of call hierarchy in VS 2005 and 2008- it's the References window. There's a live-sync mode (overkill really), or you update it with Shift-F12 (or use the Refresh button). 

coderush_jumpto Also in Coderush, the jump to... context menu also shows context-sensitive overrides/ implementations and the next/prev reference can be tabbed to within a file (VS 2010 catches up with some features of this by highlighting other uses of a variable when the caret is on it).

My favourite feature of Coderush (and the free version, Coderush Xpress) is the Quick Nav dialog (Control Shift Q), which VS2010's Navigate To emulates. Quick incremental search, with camel-humps and substrings, and filter by members. coderush_quicknav

UPDATE: In the forthcoming Coderush Xpress for 2010, Quick Nav is gone. Damn. You must have the full Coderush or Resharper (see below). Okay, the built-in Navigate-To does the basic task, without the filtering, but still...

Resharper


resharper_findsymbol resharper_findtype

Resharper also has an equivalent of QuickNav/NavigateTo, with all the camel humps goodness (no substrings, but you can do * and ? wildcards). (Note that Resharper has two keyboard mappings; I'm using the Visual Studio mappings here).
Find Symbol - Shift Alt T  - finds types and type members. Find Type - Control T - just looks at types. 

resharper_contextnav The right-click context menus for implementors, base and especially usages very handy.
resharper_findusages You can get to Find Usages with Shift-F12. If there's only one usage (or base or inheritor), you go straight to it.

The reason that Coderush and Resharper can do the quick navigation is that they parse the solution, so for the first minute or so a large solution will show one of their processing messages (theoretically you can type away, but things are slow while they are working).

NDepend

I've just started playing with NDepend. There's a lot here to find your way around large code bases - even graphs - but for now I'll just note that the Visual Studio integration includes context menus which have similar functionality to the find usages described above:

ndepend_selectmethods

 

Wrap Up

Having been a Resharper user for many years, the powerful find symbol / type commands became something I used practically every few minutes while coding. It was a shock to find myself in an office with no Resharper, but fortunately the free Coderush Xpress came to the rescue. Visual Studio 2010 vanilla edition with no Resharper or Coderush is actually not as bad as VS 2008 and earlier were- Navigate To/control comma is quite handy and an easy key combo too. Of course I then installed the betas/ trials of the latest Coderush/ Resharper and saw what I was missing. Please boss, can I have a license??? :)

posted on Tuesday, May 04, 2010 10:18:54 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Thursday, April 22, 2010
Visual Studio 2010 has simplified the numbers of editions
Good riddance to the Visual Studio 2008 mess of Team System x / Team Suite confusion.

But what's in each version?




Express
What's missing: Unit testing (use NUnit), add ins (not even Coderush Express), source control integration (use file system level tortoiseSVN).
Good for: simple home use, and even then a serious developer will find it lacking (esp no Resharpher/ CodeRush)

Professional
Has addins, MsTest unit testing, TFS integration.
The basic business version. In TFS, checkin policies enforcing code analysis are widely used, but you just can't use it here. You can only use standalone FXCop.

Premium
Has Code analysis (use standalone FXcop), code coverage, coded UI Tests, test impact analysis (new feature, looks promising).
I expect this will be the most common business version, certainly most useful for larger teams.
But it's 5 times the price of Professional for features which aren't that exciting.

Ultimate
Adds historical debugging (intellitrace, it rocks), load tests, manual tests (look good), uml diagrams (really nice), architecture explorer.
The big problem: it's over $11000 / euro12800. Ten times the price of Professional and twice Premium. Very few organisations are big enough to buy that.

Ultimate has some great features, but it's just too expensive. And Premium looks a little thin for such a big price jump.
For the next version, I hope Microsoft will drop a few features from Ultimate to Premium or even Pro.

I reckon intelliTrace and UML diagrams are the ones which would really gain some interest if more people could get their hands on them.

posted on Thursday, April 22, 2010 12:27:35 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Friday, April 02, 2010
"Covariance" = specified or MORE derived type

So upcasting to a more basic class in polymorphism is covariant.         
object
s = new string('a', 3);


But you couldn't do it for IEnumerables, even though it seemed safe. Well, now in .net 4 you can.

IList<string> stringList = new List<string>(); //will not compile in .net 1-3, but does in net 4 IEnumerable<object> objectList = stringList;
Trivia: since .net 1, arrays were covariant.
object[] data = new string[3];
data[0] = "s";
data[0] = 1; //no compiler error- just a runtime ArrayTypeMismatchException
Now IEnumerable<T> and IQueryable<T> are covariant.

IList<Cat> cats = new List<Cat>();
//you can cast to IEnumerable (or IQueryable)
IEnumerable<Animal> pets = cats;
//but you cannot cast to the read-write classes
//IList<Animal> animals = cats; //compiler error
Covariant interfaces must be read-only (and not value types).

Contravariance = specified or LESS derived type
 
var cats = new List<Cat>();
//predicate is contravariant
cats.Find(HasFur); //private static bool HasFur(Animal animal)
//so is IComparable
cats.Sort(SortByName); //private static int SortByName(Animal x, Animal y)

posted on Friday, April 02, 2010 11:10:48 AM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Wednesday, March 31, 2010
I've been to several of these things over the years, the biggest being the TechEd Berlin at the end of 2009.

This was a country-one, but it headlined a couple of big names - Anders Hejlsberg and Scott Hanselman. The location (a big cinema) was good, but very crowded and the exhibition seemed small and much too warm. The bookstall was tiny, and while I got some nice swag from the stalls, there seemed to be less than normal (and with so many people it was hard to get around). The official conference bag was a paper bag with training course leaflets. Normally you get something like a Visual Studio Standard or Pro DVD, but there was nothing here- very disappointing.

Anders did the keynote and a C# 4.0 talk. He touched upon how the functional programming had influenced the recent evolution of C#, about declarative styles (showing intent- why not how- such as Linq), and concurrent programming. The C# 4 talk was a good demo of the dynamic object and that the ugly reflection/ interop/ javascript binding code disappeared behind code that looked like normal c#. The optional and named parameters are nicer ways of doing all those overloads, and COM interop (esp MS Office) interop becomes much less horrible.

Scott did his "ninja black belt" MVC 2 talk, with lots of humour. I'll have to try the "air quotes" gesture for generic "of type". I've seen a version of this on the MIX video before, so I was familiar with a lot of it even though I'm not doing MVC at the moment :(
Biggest applause was for the notepad trick to stop Windows shutting down - when an auto update suddenly pops up the restart now button, and you're typing away and click it, the only thing that will stop the shutdown is to open notepad and type something - the notepad save dialog halts the shutdown while every other app obediently kills itself.

A talk about VS 2010 branching and merging was very dry and a bit boring. Personally I think cascading branches or trunk development with release branches are simple and manageable. I can see the value of having a main (trunk) branch that is only merged into and branched from, so it's stable while the release/ feature branches aren't- but it requires lots of merging effort. People seem to say that distributed source control (git/ mercurial) solves the merging problem...

Also saw a talk on Entity Framework 2. Well, apparently it can do everything - read from database, or use templates or use pocos and write the mapping - , and it has nice designers. Still think NHibernate's mapping is not that difficult (I think hql and criteria are steeper learning curves, and pretty ugly compared to linq, but NHibernate's full linq implementation will soon be in place)


posted on Wednesday, March 31, 2010 10:11:52 PM (Romance Daylight Time, UTC+02:00)  #    Comments [0]
# Sunday, February 14, 2010

Visual Studio 2008 sp1 introduced background compilation so it can underline errors while editing (like Word underlines misspellings). It's been in Resharper for ages. You can have both Resharper and VS error underling- they don't conflict.

In Visual Studio 2010 it's off by default (and Resharper 5 isn't released yet), so you need to turn it on. It's Tools-Options-Text Editor-C#-Advanced. Check underline errors in editor and show live semantic errors.

intellisense

posted on Sunday, February 14, 2010 8:50:16 AM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Saturday, February 13, 2010
The release candidate of Visual Studio 2010/ .Net 4 is out at http://msdn.microsoft.com/en-us/vstudio/dd582936.aspx
You have to uninstall VS2010 and TFS 2010 before installing, which has two reboots.

The main changes are apparently performance improvements. I spent 20 minutes playing with my (small) projects and didn't notice much difference. The splash screen (now a darker blue I think) sits there for ages before VS2010 appears, but that's no worse than any Visual Studio version before. The real test will be building large multi-project solutions which I haven't tried yet.


posted on Saturday, February 13, 2010 8:01:54 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Saturday, January 30, 2010

I spent some time working in quite agile, test-focussed environment, but lately I've been in organisations that like the idea of testing, but don't really practice it.

Trying to use testing helps both in proving something works, and because it makes you structure your code so it can be tested (more modular code, reduce coupling) which makes it easier to understand and maintain.

"Pure" test-driven development is a big step, and the existing code base may make it very difficult. So start small, pick the battle fronts where you can win, refactor, organise your tests. Use integration tests but focussed and well structured.

Tests vs time

The standard argument is "Writing tests takes lots of time so only do it if you have time at the end".
Initially it takes time, but unless your code is bugless first time, the iterations of manual testing/ fixing may take more time. So it's (time to code automated test) vs (time to manual test). The latter can add up over the lifetime of the software, and the major win of automated tests is finding regressions. Assess the cost-benefit, it's doesn't have to be 100% coverage full TDD vs nothing.

"You have to F5 and manually use your application anyway".
True, but automated tests can cover all the permutations you don't do in manual tests.

TDD vs Visual Studio

Autocomplete make test-before-code difficult. The consume first mode in Visual Studio 2010 looks like it will help. Up until now, the closest to real TDD that I've done involved stubbing the classes and methods before writing the tests.

TDD is more than testing of course. It's supposed to force you to design simple loosely coupled components. It certainly makes you focus on your API.

Tests vs refactoring

Refactoring tools (Resharper, CodeRush) mitigate some of this, but most major refactoring will have a big impact on a large bank of tests. So yes, many of your tests may be thrown away and you start again. The solution to to keep the tests well structured and named (yes, easier said than done).

Unit tests structure and readability

Things I've found helpful:

  • the test project and it's subject should look identical: one test class per class. If the test class is too big, it's a good indication that the class is doing too much, but you can split up the test class with partial classes.
  • TDD guides suggest you don't test private methods, and so far this has worked well for me. I don't use the MSTest private accessors, although very rarely I will write a little reflection code to set a private field rather than use the public property. Internal methods and InternalsVisibleTo are very useful.
  • Internally in the test method, the "arrange / act / assert" comments split things up.
  • Underline-delimited words in test method names are more readable than Pascal case.
  • Test methods have to be readable, not elegant code : avoid simple refactorings (extract method) that hide what's going on, use lots of cut'n'paste code.


Unit tests vs Integration tests

Developers often have the idea that they should only do unit tests, and integration tests are a really bad thing. Of course you can and should refactor to use interfaces with DI / mocks so code can be unit tested without dependencies.

But data access and UI (at least non MVC flavours) are by definition mostly integrations and not easily testable. If you can test them with integration tests, you often have the most interesting and useful tests (unless you love manual testing). All those individual classes are pretty dumb and unit testing doesn't reveal as much.

Integration tests tend to be much longer and more complicated- there's almost always setup/ class or testInitialize and teardown / cleanup. But structure it well and after the writing the initial setup and first test, other tests just follow the same pattern and become very easy.

Integration tests tips

  • Break it down into small tests. The temptation is to do an end-to-end test (login-navigate-create record-find record-edit record-delete record-logoff). To start, one test = one operation/ form/ page (at most). For UI, you probably have to start with launch app/ login, but stop there- other tests can then call the login and carry on. If you change the login, one test changes and the others will be unchanged. 
  • Database tests: A local SqlExpress database file is a neat testing target in the test project, if you can keep it up to date.
  • Database tests: You can wrap tests in transactions that are not committed (and thus rolled back)
  • Database tests: Linq2Sql is helpful to verify your data access is doing the right thing.
  • Web UI tests: for asp.Net you should use Watin (a framework to run IE - and now Firefox- from tests). You really can write the test, then code the webpage, in true TDD fashion (I did!). Visual Studio 2010 coded UI tests work similarly, with a nice recorder- not just IE, but Firefox, winforms, and WPF (Chrome/Safari/IE6 aren't supported, and amazingly Silverlight isn't either). Telerik has a similar web-test UI studio (IE, FF, Safari and Silverlight), but it isn't cheap.
  • File IO: In MSTest this is fairly easy in the deployment directories, which are temporary directories created each test run (under solution-level folder Test Results). By default, deployment is enabled (see in LocalTestRun.testrunconfig). Environment.CurrentDirectory (and TestContext.TestDeploymentDir) have the full path. Test assembly contents with Copy to Output Directory = Always copy do not get copied (only the dlls) so add the attribute [DeploymentItem("Data.xml")]
posted on Saturday, January 30, 2010 8:25:12 AM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Sunday, December 13, 2009

Team Foundation Server 2005/2008 didn't make much sense for small teams. TFS 2010 Basic allows you to have a simple, quick source control and issue ("work item") tracking for individuals. It should at least see the end of SourceSafe. Whether it will draw back those who have already jumped to subversion remains to be seen.

Creating workspaces and team projects is not something I'll do very often, so I'll record the steps here.

Download VS2010 and TFS 2010 from Microsoft and install. The TFS configuration wizard kicks in after the install, and is pretty simple.

Connect to TFS.

connect to tfs Spot two ways to do it in this screenshot.

Select TFS server - click "Servers" button

select tfs server

The servers list will be empty at first so click "Add" button

select tfs server 2

Fill in the server name.

select tfs server 3

Now the project collection is shown (always DefaultCollection with basic). Click "Connect"

select tfs server 4

Workspaces

You need to set up a workspace - the local path on your computer (in VSS this would be your "working directory").

In VS2010, File-Source Control - Workspaces.

file-source control-workspaces

It's empty to start. Typically you only have one per TFS server. Click "Add"

tfs workspaces

Map the server folder ("$/") to your local folder (here, C:\Dev)

tfs workspaces 2 

New Team Project

Each TFS contains a number of collections (these root collections are new in 2010), Each collection contains a number of team projects. The team project is typically an application, comprising one or more solutions plus work items and documents. More information here (Codeplex guidance)

You can start a team project from the VS2010 File-New-Team Project or the team explorer.

new team project

It's a simple wizard. Name it, pick the type (you may have a Scrum template), Next, Next, Next.

new team project 2 new team project 3 new team project 4 new team project 5

new team project 6

Your source control is now empty. Don't just put a solution in there. Add New Folder, and use this to put your first code in. I'm calling it Trunk, but TFS people seem to call it "Main".

new source control folder

I created a Visual Studio solution within my workspace\project\trunk folder. I add it to source control.

add solution to source control

And check in (pending changes window)

pending changes

Why a subfolder called "Trunk" (or "main")? So I can create a branch.

I also create a "Branches" folder under the team project, and branch to a subfolder of that. Here I'm creating a branch for a new version (v4).

branching

There's detailed guidance on TFS branching on Codeplex.

posted on Sunday, December 13, 2009 6:07:51 PM (Romance Standard Time, UTC+01:00)  #    Comments [0]
# Saturday, December 12, 2009

scottguusualsuspects

Here I am at the ScottGu impersonators gathering. I'm in the red polo.

scottgucrowd

Ok, actually this was a Scott Guthrie presentation - vs2010, asp4, silverlight 4, asp mvc2. Plus some related stuff like the IIS SEO module and IIS media streaming. And even a demonstration of rickrolling.

Between the lines- the web stack is still evolving strongly (especially SL and MVC, but asp webforms is getting attention too), windows forms is dead, silverlight and silverlight out of browser will replace most of desktop WPF (which is just for Visual Studio scale apps)

scottgulineup I'm in the back row, under the "d" of msdn and looking to the side wondering if ScottGu was in that direction.

posted on Saturday, December 12, 2009 8:31:53 AM (Romance Standard Time, UTC+01:00)  #    Comments [0]