How Visual Studio names controls when dropped on a form from the toolbox

One popular feature of my MZ-Tools extension allows you to apply custom default properties to a new Windows Forms control (either when it is dropped on a form from the toolbox or on demand clicking a button). It happens that when your extension is notified that a new control has been added to a form or usercontrol with the IComponentChangeService.ComponentAdded event, to guess if the controls is “new” or not (pasted, undo, etc.) is very tricky. One of the checks that I use to guess if the control is “new” is to check if its name has the pattern typename+number(such as “TextBox1”) because it is unlikely that an existing control has remained with the final number. As you know, given a typename such as “TextBox”, in VB.NET the default control name would be “TextBox1” and in C# it would be camel case “textBox1”. This has been working well all these years but last week a customer reported me that using the Janus controls the feature didn’t work. It happens that Janus has controls whose type name is something like “UIButton”, “UIGroupBox”, etc. So, given that typename, which name does Visual Studio assign to a new control? The answer is the following:

  • For C#, it assigns names like “uiButton1” (but not “uIButton1”)
  • For VB.NET, it assigns names like “UiButton1” (but not “UIButton1”)

Since I am the kind of person that likes to know how and why things work, I was curious about how and where Visual Studio applies those naming rules. So, I used .NET Reflector to debug the Visual Studio assemblies. The most difficult part was to guess which assembly to debug and where to put a breakpoint. I succeeded and soon I found the System.ComponentModel.Design.Serialization.CodeDomDesignerLoader class (in the System.Design.dll assembly) that implements the INameCreationService interface whose CreateName(IContainer container, Type dataType) method is used by the Windows Forms designer to generate the name. This method:

1) Takes the name of the type of the control and converts to lower case:

a) The initial character

b) Subsequent characters if they are upper case, until the last upper case character:

string name = dataType.Name;
StringBuilder builder = new StringBuilder(name.Length);
for (int i = 0; i < name.Length; i++)
{
   if (char.IsUpper(name[i]) && (((i == 0) || (i == (name.Length - 1))) || char.IsUpper(name[i + 1])))
   {
      builder.Append(char.ToLower(name[i], CultureInfo.CurrentCulture));
   }
   else
   {
      builder.Append(name.Substring(i));
      break;
   }
}

2) Appends a unique number
3) Calls the code generator to create a valid identifier:

str = this._codeGenerator.CreateValidIdentifier(str);

In turn, this code generator is the Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomGenerator class (in the Microsoft.VisualStudio.Design.dll assembly) that can use the camel (C#) or VB.NET case (depending on the value of the property __VSHPROPID.VSHPROPID_DesignerVariableNaming of the IVsHierarchy of the designer) converting just the first character (not subsequent ones!)

public string CreateValidIdentifier(string value)
{
   string str = this.innerGenerator.CreateValidIdentifier(value);
   if ((str != null) && (str.get_Length() > 0))
   {
      switch (this.VariableNaming)
      {
         case VSDESIGNER_VARIABLENAMING.VSDVN_Camel:
            return (((char) char.ToLower(str.get_Chars(0), CultureInfo.InvariantCulture)) + str.Substring(1));

         case VSDESIGNER_VARIABLENAMING.VSDVN_VB:
            return (((char) char.ToUpper(str.get_Chars(0), CultureInfo.InvariantCulture)) + str.Substring(1));
      }
   }
   return str;
}