Sunday, 2 February 2014

Extending WinForms controls

I've been looking at ways to extend and enhance .NET WinForms controls recently and thought I'd summarise what I've found/learned so far.

Inheritance

The most obvious way to extend a control is to create a new class that inherits from the existing class. You then have full access to add new properties and methods and access any protected methods from the base class.

It does mean that if you've already used the control in your application, you're going to have to replace references to the base class with your new class – not always easy.

IExtenderProvider

Properties window showing Tooltip propertyAnother option is to create a class that implements the IExtenderProvider interface. You can then create a component that can be added to a form or user control that extends specific types. An example of this that ships in the framework is the ToolTip class. When you drag this control onto a form it doesn't add a visible control to the design surface. Instead it adds a new ToolTip property to appropriate controls on the same form.


Custom ComponentResourceManager

Visual Studio designer showing properties window with Localizable set to trueA more specific area of WinForms controls that you might want to customise is the localisation support. Localisation for controls on a form is enabled by setting the Localizable property of the form to true. This also alters the designer-generated InitializeComponent method to add a new resources variable of type System.ComponentModel.ComponentResourceManager. This allows you to use .resx resource files to load locale-specific values for properties. This is the standard way that you would provide alternate language translations for your application.

A question on Stack Overflow asked about replacing this implementation with another that could load resources from an alternate location (such as an XML file or a database). The accepted answer pointed to some samples from Guy Smith-Ferrier's book, .NET Internationalization: The Developer's Guide to Building Global Windows and Web Applications.

Impressively, even though the book was published in 2006 Guy has been releasing regular updates to the code samples at http://www.dotneti18n.com/Downloads.aspx. His sample for a custom ComponentResourceManager includes a ResourceManagerSetter class that has the DesignerSeralizer attribute. This was new to me, but it turns out this is the mechanism that generates the code that appears in a forms's .Designer.cs file. This uses the CodeDom to generate code that inserts the replacement ComponentResourceManager instance.

private void InitializeComponent()
{
    System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UserControl1));
    this.resourceManagerSetter1 = new Internationalization.Resources.ResourceManagerSetter();
    this.label1 = new System.Windows.Forms.Label();
    this.SuspendLayout();
    Internationalization.Resources.ResourceManagerProvider.GetResourceManager(typeof(UserControl1), out resources);
    // 
    // label1
    // 
    resources.ApplyResources(this.label1, "label1");
    this.label1.Name = "label1";
    // 

Using CodeDom means that the code is generated for the appropriate language automatically. The only requirement is that this component must be the first one added to the form. If you're adding this to an existing form with controls, just go to the Designer.cs file and move the line calls the constructor to the top of the InitializeComponent method. (The order of the calls to the constructors seems to determine the order in which the serialized code generators are called)

You can see from the code sample above that the original instantiation is still there, but is effectively replaced by the call to the GetResmourceManager method.

MSDN has more info on Globalizing Windows Forms.