Custom Control Development: Simple code guidelines

Posted 26 August 2009  

Great custom control development is a skill that takes more than time and experience to perfect. It’s also not a widely documented practice. When it comes to Silverlight controls, there are similarities and differences from WPF custom control development, too – so that chapter on controls in your favorite WPF book often is not directly applicable.

While working with teams throughout Microsoft, and trolling Silverlight forums on Silverlight.net and StackOverflow, I’ve come across a few situations where I’ve wanted to share some guidelines for custom control development. Over the next few posts, I’d like to occasionally share my thoughts on this topic, and whatever tips seem pertinent at the time of posting.

I do admit that there’s a lot of flexibility in control development, so I’ll be basing a lot of my tips on both official and unofficial practices on the Silverlight Toolkit team. And I understand if you don’t agree with everything I have to say. But I do hope this information will be useful!

Contain your classes

Controls and classes should always be in their own unique file, instead of having multiple controls or classes in a single file. This includes enums and structs, etc.

So the Dock enum belongs in a file called Dock.cs.

Typically classes are broken up into folders of shared features or sub-namespaces.

Keep your regions under control

Regions that serve no real purpose have no need in your code. Grouping all “Public Methods" into a region and having another region with “Private Methods” isn’t helpful to most people.

However, grouping related concepts, interface implementations, and complex algorithms into regions can be a great way to make that 2,000 line file feel a little more navigable.

We always place DependencyProperty declarations and change handlers inside clear regions.

regions

Be consistent about where fields belong

Fields should always be private, only accessible to the control implementation. Whether you place them at the top of the file, the absolute bottom, or alongside related methods and properties (such as in a #region for a DependencyProperty), is a choice that should be consistent within a codebase.

We often try and place backing fields near their respective properties.

Break comments at 80 characters

By maintaining a visual break for comments, they become easier to digest while looking through code, regardless of monitor resolution.

comment_breaks

To help with this, you can use a nice registry hack to place a visual guide in the Visual Studio editor (pictured in the above screen capture). Here’s the registry key to set (you can change the color and/or character placement as well):

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\Text Editor]
"Guides"="RGB(220,220,220) 80"

Consider writing great XML comments

By writing “great” XML comments, as they would be written in programmer documentation by a writer, you’ll save the time for anyone documenting the controls, improve the IntelliSense experience, and over time find that your code is very clear and crisp.

You can even consider using SandCastle for generating MSDN-style documentation from your code in this case.

If you find yourself writing too much in the comments, your method or feature may be too complex – consider simplifying.

The style we use on the Silverlight Toolkit consists of this:

  • Comments end with a period
  • Properties start with “Gets or sets”, or “Gets or sets a value indicating whether”
  • Properties that are only a getter only have the “Gets” portion of “Gets or sets”, etc.
  • Constructors use wording such as “Initializes a new instance of Type.”

Here’s a set of example comments borrowed from the Silverlight team…

Class comments:

/// <summary>

/// Represents a control that displays hierarchical data in a tree structure

/// that has items that can expand and collapse.

/// </summary>

public partial class TreeView : ItemsControl, IUpdateVisualState

{

}

A private field:

        /// <summary>

        /// A value indicating whether a read-only dependency property change

        /// handler should allow the value to be set.  This is used to ensure

        /// that read-only properties cannot be changed via SetValue, etc.

        /// </summary>

        private bool _allowWrite;

A public property:

        /// <summary>

        /// Gets the selected item in a

        /// <see cref="T:System.Windows.Controls.TreeView" />.

        /// </summary>

        /// <value>

        /// The currently selected item or null if no item is selected. The

        /// default value is null.

        /// </value>

        public object SelectedItem

        { get; set; }

An event handler method:

        /// <summary>

        /// SelectedItemProperty property changed handler.

        /// </summary>

        /// <param name="d">TreeView that changed its SelectedItem.</param>

        /// <param name="e">Event arguments.</param>

        private static void OnSelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

        { /* ... */ }

Public event comments:

        /// <summary>

        /// Occurs when the value of the

        /// <see cref="P:System.Windows.Controls.TreeView.SelectedItem" />

        /// property changes.

        /// </summary>

        public event RoutedPropertyChangedEventHandler<object> SelectedItemChanged;

Constructor:

        /// <summary>

        /// Initializes a new instance of the

        /// <see cref="T:System.Windows.Controls.TreeView" /> class.

        /// </summary>

        public TreeView()

        {

            DefaultStyleKey = typeof(TreeView);

            ItemsControlHelper = new ItemsControlHelper(this);

            Interaction = new InteractionHelper(this);

        }

On template application:

        /// <summary>

        /// Builds the visual tree for the

        /// <see cref="T:System.Windows.Controls.TreeView" /> control when a new

        /// control template is applied.

        /// </summary>

        public override void OnApplyTemplate()

        {

            base.OnApplyTemplate();

        }

Bool property with getter and setters:

        /// <summary>

        /// Gets or sets a value indicating whether the drop-down portion of

        /// the control is open.

        /// </summary>

        /// <value>

        /// True if the drop-down is open; otherwise, false. The default is

        /// false.

        /// </value>

        public bool IsDropDownOpen

        {

            get { return (bool)GetValue(IsDropDownOpenProperty); }

            set { SetValue(IsDropDownOpenProperty, value); }

        }

Your event handlers should be thread-safe

Firing events should be done by first storing the handler in a local variable, similar to this:

public event PropertyChangedEventHandler PropertyChanged;



private void OnPropertyChanged(string propertyName)

{

    PropertyChangedEventHandler handler = PropertyChanged;

    if (handler != null)

    {

        handler(this, new PropertyChangedEventArgs(propertyName));

    }

}

Fire control events on the user interface thread

Silverlight only have a single UI thread. However, if you’re interacting with the network or worker threads, the users of your control should not have to worry about being on the UI thread.

Expect that all your events will likely end up changing UIElements that will be checking the thread.

Here’s a simple, efficient way to make sure that your events fire on the UI thread:

/// <summary>

/// Occurs when the DownloadProgress property has changed.

/// </summary>

public event RoutedEventHandler DownloadProgressChanged;



private void OnDownloadProgressChanged(object sender, RoutedEventArgs args)

{

    if (!Dispatcher.CheckAccess())

    {

        Dispatcher.BeginInvoke(() => OnDownloadProgressChanged(sender, args));

        return;

    }



    var handler = DownloadProgressChanged;

    if (handler != null)

    {

        handler(this, args);

    }

}

Miscellaneous C# things

  • Don’t qualify members using “this” or “base” unless required to disambiguate
  • Prefix private fields with an underscore
  • Feel free to use automatic properties, but do not use private automatic properties.
  • Use lowercase ‘string’ when calling methods on it such as ‘string.IsNullOrEmpty’, instead of ‘String.IsNullOrEmpty’.
  • Similar, refer to and use “object” instead of “Object” for locks and other references to generic objects

Organize your Using statements

Instead of placing fully qualified type names with namespaces (System.Windows.Controls.Button is not great – just add a using statement for System.Windows.Controls and refer to this as Button), use the built-in Using statement refactoring support in Visual Studio:

RefactorUsings[1]

You want your using statements to be the proper subset of namespaces, and in alphabetical order.

Mind the .NET Framework Design Guidelines

When you’re building custom controls, you’re designing a rich API that will enable interesting scenarios for your target developers.

The .NET Framework Design Guidelines book by Krzysztof Cwalina and Brad Abrams is a really important reference that should be regarded to help assist in the design of your framework.

Depending on the target audience of your work, this may be more or less important: an internal development project may not have the same goals as a custom control that you’d like to sell to other developers.

If you see a specific pattern in existing Silverlight or WPF controls, there’s precedent to use a similar implementation in your own controls for the convenience of developers used to the platform.

Off hand, you should consider some of these tips from the book:

Remain CLS Compliant

So that you don’t shut out VB.NET and other language users from the control API, make sure to remain CLS compliant.

This typically translates into situations where you want to expose a generic collection interface instead of an array, for instance. Don’t use the same name, differing only in case.

Good, compliant code:

public IList<interestingitem> InterestingItems { get; }

Code that shouldn’t be exposed in a custom control with public visibility:

public InterestingItem[] InterestingItems { get; }

Naming guidelines

All .NET components typically use PascalCasing for properties, members, and events (PMEs). You’ll find camelCasing in use for parameter names only.

You’ll want to be careful about the use of abbreviations and acronyms, and how compound words should appear in your programming interfaces.

What’s next?

Let me know in the comments what you think, and especially if you have any particular control development topic I should cover in a future post. Hope you like this!

Jeff Wilcox is a Principal Software Engineer at Microsoft in the Open Source Programs Office, helping Microsoft scale to 10,000+ engineers using, contributing to and releasing open source.

comments powered by Disqus