Blog Stats
  • Posts - 276
  • Articles - 2
  • Comments - 305
  • Trackbacks - 660

 

Custom Forms Designer: Name Creation Service

Reminder: All posts in this series can be found here.

The first code I will look at is from one of the simplest services supporting the Windows Forms designer.  The Name Creation Service is called each time you drop a control/component onto the visual design surface.  Your job is to return a unique name for the new control that also means something.

The standard process, per convention, is to take the control/component Type name, camel case it (lower case the first letter) since it will be a member variable, and then append a digit suffix to make it unique from all other controls/components on the form.  The number ensures it remains unique for languages which are not case sensitive.

The sample code implements only the INameCreationService.CreateName method.  I modeled my implementation off of the sample code for INameCreationService here.  I found the sample CreateName code to be excessively complex:

    string INameCreationService.CreateName(IContainer container, Type type)
    {
      ComponentCollection cc = container.Components;
      int min = Int32.MaxValue;
      int max = Int32.MinValue;
      int count = 0;
      for (int i = 0; i < cc.Count; i++)
      {
        Component comp = cc[i] as Component;
        if (comp.GetType() == type)
        {
          count++;
          string name = comp.Site.Name;
          if (name.StartsWith(type.Name))
          {
            try
            {
              int value = Int32.Parse(name.Substring(type.Name.Length));
              if (value < min)
                min = value;
              if (value > max)
                max = value;
            }
            catch (Exception ex)
            {
              Trace.WriteLine(ex.ToString());
            }
          }
        }
      }
      if (count == 0)
        return type.Name + "1";
      else if (min > 1)
      {
        int j = min - 1;
        return type.Name + j.ToString();
      }
      else
      {
        int j = max + 1;
        return type.Name + j.ToString();
      }
    }

My implementation is simpler:

    String INameCreationService.CreateName(IContainer container, Type dataType)
    {
      if (dataType == null)
      {
        throw new ArgumentNullException("dataType");
      }
      // The base component name is the Type name with the first character lower case
      String name = Char.ToLower(dataType.Name[0]) + dataType.Name.Substring(1);
      int number = 1;
      // If the container parameter is null, no container search is needed
      if (container != null)
      {
        // Increment counter until we find a name that is not in use
        while (container.Components[name + number.ToString()] != null)
        {
          ++number;
        }
      }
      return (name + number.ToString());
    }

I also added implementations of INameCreationService.IsValidName and INameCreationService.ValidateName.  The IsValidName method returns a Boolean and does not throw any exceptions.  The ValidateName method can throw an exception to return the exact reason why the candidate name fails.  Generally, the code structure of the two methods should be similar, differing primarily in how they communicate an error back to the developer.

    bool INameCreationService.IsValidName(String name)
    {
      // Cannot be null or empty
      if (String.IsNullOrEmpty(name))
      {
        return false;
      }
      // Cannot start with a number
      UnicodeCategory uc = Char.GetUnicodeCategory(name, 0);
      if (uc == UnicodeCategory.DecimalDigitNumber)
      {
        return false;
      }
      // Cannot have anything other than letters and numbers
      foreach (Char c in name)
      {
        uc = Char.GetUnicodeCategory(c);
        switch (uc)
        {
          case UnicodeCategory.UppercaseLetter:
          case UnicodeCategory.LowercaseLetter:
          case UnicodeCategory.TitlecaseLetter:
          case UnicodeCategory.DecimalDigitNumber:
            break;
          default:
            return false;
        }
      }
      return true;
    }
    void INameCreationService.ValidateName(String name)
    {
      // Cannot be null
      if (name == null)
      {
        throw new ArgumentNullException("name");
      }
      // Cannot be empty
      if (name.Trim().Length == 0)
      {
        throw new ArgumentException("name cannot be of zero length");
      }
      // Cannot start with a number
      UnicodeCategory uc = Char.GetUnicodeCategory(name, 0);
      if (uc == UnicodeCategory.DecimalDigitNumber)
      {
        throw new ArgumentException("name cannot start with a number");
      }
      // Cannot have anything other than letters and numbers
      foreach (Char c in name)
      {
        uc = Char.GetUnicodeCategory(c);
        switch (uc)
        {
          case UnicodeCategory.UppercaseLetter:
          case UnicodeCategory.LowercaseLetter:
          case UnicodeCategory.TitlecaseLetter:
          case UnicodeCategory.DecimalDigitNumber:
            break;
          default:
            throw new ArgumentException("name must contain only letters and numbers");
        }
      }
      return;
    }

While there is a little more refactoring possible, that will be good enough for now.

Feedback

No comments posted yet.


Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 
 

 

 

Copyright © Mark Treadwell