Visual Studio .NET

Building Windows Forms Controls and Components with Rich Design-Time Features, Part 2

Michael Weinhardt and Chris Sells

Code download available at:Design-TimeControls.exe(328 KB)

This article assumes you're familiar with Visual Studio .NET and C#

Level of Difficulty123

SUMMARY

This is the second of two articles discussing the extremely rich design-time features of the .NET Framework. Part 1 discussed the basics, showing you where to start and how to extend your control implementation through attributes and interfaces, as well as their effects on the property browser, code serialization, and other controls. Part 2 continues the journey by concentrating on design-time functionality that you can implement beyond your components and controls, including TypeConverters, UITypeEditors, and Designers. It would be impossible to cover everything you can do in two short articles, which is a testament to just how all-encompassing and flexible the design-time capability of the .NET Framework is.

Contents

TypeConverters
Custom TypeConverters
Custom Type Code Serialization With TypeConverters
UITypeEditors
Designers
Design Time-only Properties
Conclusion

Our article in the April 2003 issue of MSDN® Magazine established some design-time fundamentals for components and controls, which we illustrated with a clock control sample. One of the major themes that we highlighted showed how design-time attributes and interfaces can be used to influence the relationship of a component or control instance to its design-time surroundings, especially with the property browser. When you select a control on a form, the property browser's entries are rendered from the design-time control instance. When you edit properties in the property browser, the control's design-time instance is updated with the new property values. This synchronicity isn't straightforward since the property browser only displays properties as text, even though the source properties could be of any type, from simple numbers and dates, to complex custom types. As values shuttle between the property browser and design-time instance, they must be converted from one type to another.

TypeConverters

Enter the type converter, whose main goal in life is to convert from one type to another and back again (for example, between types and strings). String-to-type conversion is exactly the right tool to support synchronization between the property browser and controls, and is achieved by using a type converter for each property displayed in the property browser (see Figure 1).

Figure 1 Property Browser/Design-time Conversion

Figure 1** Property Browser/Design-time Conversion **

The Microsoft® .NET Framework offers System.ComponentModel.TypeConverter as the base implementation type converter, plus several further derivations of TypeConverter that support conversion to and from intrinsic .NET Framework types, including StringConverter, Int32Converter, and DateTimeConverter. These are alternatively called intrinsic type converters. If you know the type that needs conversion at compile time, you can then create an appropriate converter directly, as shown here:

// Type is known at compile time 
TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));

Or, if you don't know the type that needs conversion until run time, you can let System.ComponentModel.TypeDescriptor make the choice for you:

// Don't know the type before run time 
object myData = 0; 
TypeConverter converter = TypeDescriptor.GetConverter(myData.GetType());

TypeDescriptor provides information about a particular type or object, including methods, properties, events, and attributes. TypeDescriptor.GetConverter evaluates a type to determine a suitable TypeConverter. It does this by checking whether a type is adorned with an attribute that specifies a particular TypeConverter. It then compares the type against the set of intrinsic TypeConverter implementations. Finally, it returns TypeConverter if no other TypeConverters are found.

Because the property browser could display any controls or components with any type of property, it can't know those types in advance and consequently relies on TypeDescriptor.GetConverter to select the most appropriate TypeConverter for each property. Once a TypeConverter is chosen, the property browser and design-time instance can perform the required conversions using the same fundamental steps as those shown in Figure 2.

Figure 2 TypeConverter

// Create the appropriate type converter 
object myData = 0;
TypeConverter converter = TypeDescriptor.GetConverter(myData.GetType());
// Can converter convert int to string 
if (converter.CanConvertTo(typeof(string))) {
    // Convert it 
    object intToString = converter.ConvertTo(42, typeof(string));
}
// Can converter convert string to int 
if (converter.CanConvertFrom(typeof(string))) {
    // Convert it 
    object stringToInt = converter.ConvertFrom("42");
}

Some intrinsic TypeConverters can do more than just convert between simple types. To demonstrate, let's expose a Face property of type ClockFace, allowing developers to decide how the clock is displayed, including options for Analog, Digital, or Both:

public enum ClockFace {
    Analog = 0, Digital = 1, Both = 2
}
class ClockControl: Control {
    ClockFace _face = ClockFace.Both;
    public ClockFace Face {
        get {
            ...
        }
        set {
            ...
        }
    }
    •••
}

TypeDescriptor.GetConverter returns an EnumConverter, which examines the source enumeration and converts it to a dropdown list of descriptive string values (see Figure 3).

Figure 3 Enumeration Type

Figure 3** Enumeration Type **

Custom TypeConverters

Intrinsic TypeConverters are useful, but they aren't enough if components or controls expose properties of custom types, like the clock control's HourHand, MinuteHand, and SecondHand properties (see Figure 4). The idea is to give developers the option to customize the clock's hands with color and width values. The unfortunate result of not having a custom type converter is shown in Figure 5.

Figure 4 Clock Hand Properties

public class Hand {
    public Hand(Color color, int width) {
        _color = color;
        _width = width;
    }
    public Color Color {
        get {
            return _color;
        }
        set {
            _color = value;
        }
    }
    public int Width {
        get {
            return _width;
        }
        set {
            _width = value;
        }
    }
    private Color _color = Color.Black;
    private int _width = 1;
}
public class ClockControl: System.Windows.Forms.Control {
    public Hand HourHand {
        ...
    }
    public Hand MinuteHand {
        ...
    }
    public Hand SecondHand {
        ...
    }
    •••
}

Figure 5 Complex Properties

Figure 5** Complex Properties **

Just as the property browser can't know what property types it will be displaying, the .NET Framework can't know what custom types you'll be developing. Consequently, there aren't any TypeConverters capable of handling them. You can, however, hook into the TypeConverter infrastructure to provide your own. To facilitate value synchronization, both property browser and design-time control instances need a type converter to translate to and from a Hand type, starting by deriving from TypeConverter:

public class HandConverter : TypeConverter { ... }

HandConverter must initially override CanConvertFrom, ConvertTo, and ConvertFrom to support the required conversion, like so:

public class HandConverter: TypeConverter {
    public override bool CanConvertFrom(ITypeDescriptorContext context, 
      Type sourceType) {
          ...
    }
    public override object ConvertFrom(ITypeDescriptorContext context, 
      CultureInfo info, object value) {
          ...
    }
    public override object ConvertTo(ITypeDescriptorContext context, 
      CultureInfo culture, object value, Type destinationType) {
          ...
    }
}

CanConvertFrom lets clients know what it can convert from. In this case, HandConverter reports that it can convert from a string type to a Hand type, as shown here:

public class HandConverter: TypeConverter {
    public override bool CanConvertFrom(ITypeDescriptorContext context, 
      Type sourceType) {
        // We can convert from a string to a Hand type 
        if (sourceType == typeof(string)) {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }
    •••
}

Whether the string type is in the correct format is left up to ConvertFrom, which actually performs the conversions specified by CanConvertFrom. The clock control's string conversion expects a multi-valued string to be split into its atomic values, which are used to instantiate a Hand object (see Figure 6). Now, we need to provide the ability to convert from a Hand type back to a string, a task performed by ConvertTo (see Figure 7).

Figure 7 ConvertTo

public class HandConverter: TypeConverter {
    public override object ConvertTo(ITypeDescriptorContext context, 
      CultureInfo culture, object value, Type destinationType) {
        // If source value is a Hand type 
        if (value is Hand) {
            // Convert to string 
            if ((destinationType == typeof(string))) {
                Hand hand = (Hand) value;
                string color = (hand.Color.IsNamedColor ? hand.Color.Name : hand.Color.R + 
                  ", " + hand.Color.G + ", " + hand.Color.B);
                return string.Format("{0}; {1}", color, hand.Width.ToString());
            }
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
    •••
}

Figure 6 ConvertFrom

public class HandConverter: TypeConverter {
    public override object ConvertFrom(ITypeDescriptorContext context, 
      CultureInfo info, object value) {
        // If converting from a string 
        if (value is string) {
            // Build a Hand type 
            try {
                // Get Hand properties 
                string propertyList = (string) value;
                string[] properties = propertyList.Split(';');
                return new Hand(Color.FromName(properties[0].Trim()), 
                  Convert.ToInt32(properties[1]));
            } catch {}
            throw new ArgumentException("The arguments were not valid.");
        }
        return base.ConvertFrom(context, info, value);
    }
    •••
}

You might have noticed that HandConverter doesn't implement a CanConvertTo override. The base implementation of TypeConverter.CanConvertTo returns a value of true when queried for its ability to convert to a string type. Since this is the correct behavior for the HandConverter, there's no need to override it. CanConvertFrom doesn't cope with strings, however, and must be overridden.

When TypeDescriptor.GetConverter checks for an attribute specifying which converter to use, it looks for TypeConverterAttribute, which is applied to a control in the following way:

public class ClockControl: Control {
    [TypeConverter(typeof(HandConverter))] public Hand HourHand {
        ...
    }[TypeConverter(typeof(HandConverter))] public Hand MinuteHand {
        ...
    }[TypeConverter(typeof(HandConverter))] public Hand SecondHand {
        ...
    }
    •••
}

However, this is somewhat cumbersome. Luckily, moving the TypeConverterAttribute to the HandType class automatically ensures that TypeDescriptor.GetConverter returns HandConverter, resulting in much nicer code:

[TypeConverter(typeof(HandConverter))] public class Hand {
    ...
}
public class ClockControl: System.Windows.Forms.Control {
    •••
    public Hand HourHand {
        ...
    }
    public Hand MinuteHand {
        ...
    }
    public Hand SecondHand {
        ...
    }
}

Figure 8 shows the effect of HandConverter.

Figure 8 HandConverter in Action

Figure 8** HandConverter in Action **

While this is better than not being able to edit the property at all, there are still ways it could be improved. For instance, put yourself in a newbie's shoes. While it might be obvious to them what the first part of the property is, they might be a little disappointed by not being able to pick the color from one of those pretty dropdown color pickers. And what is the second part of the property meant to be? Length? Width? Degrees?

Figure 9 Subproperty Viewing

Figure 9** Subproperty Viewing **

As an example of what we'd like to see, the Font type supports browsing and editing of its subproperties, as shown in Figure 9. This capability makes it a lot easier to understand what the property represents and what sort of values you need to provide. This feature is exposed by the .NET Framework for your design-time pleasure as an ExpandableObjectConverter from which you can derive to inherit property expansion mechanics. All that's needed in order to allow subproperty editing is to change the base type from TypeConverter to ExpandableObjectConverter:

class HandConverter : ExpandableObjectConverter { ... }

The results are shown in Figure 10.

Figure 10 Subproperty Editing

Figure 10** Subproperty Editing **

Although we didn't have to write any code to make the Hand property expandable, we will have to write a little code to fix an irksome property update delay problem. In expanded mode, a change to the root property value is automatically reflected in the nested property value list. This occurs because the root property entry refers to the design-time property instance, while its nested property values are directly associated with the design-time instance's properties, illustrated in Figure 11.

Figure 11 Root and Nested Properties

Figure 11** Root and Nested Properties **

When the root property is edited, the property browser calls HandConverter.ConvertFrom to convert the property browser's string entry to a new SecondHand instance, which results in a refresh. Yet changing the nested values only changes the current instance's property values, rather than creating a new instance, which doesn't result in an immediate refresh of the root property. TypeConverters offer a mechanism you can use to force new instance creation whenever instance property values change, which is achieved by overriding GetCreateInstanceSupported and CreateInstance. GetCreateInstanceSupported returns whether this support is available and, if so, calls CreateInstance to implement it, as the following code shows:

internal class HandConverter: ExpandableObjectConverter {
    public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) {
        // Always force a new instance 
        return true;
    }
    public override object CreateInstance(ITypeDescriptorContext context, 
      IDictionary propertyValues) {
        // Use the dictionary to create a new instance 
        return new Hand((Color) propertyValues["Color"], 
          (int) propertyValues["Width"]);
    }
    •••
}

Custom Type Code Serialization With TypeConverters

While the Hand type now plays nicely with the property browser, it doesn't yet play nicely with code serialization. In fact, at this point it's not being serialized to InitializeComponent at all. To enable serialization of properties exposing complex types, you need to expose a public ShouldSerializePropertyName method that returns a Boolean:

class ClockControl: Control {
    public Hand SecondHand {
        ...
    }
    private bool ShouldSerializeSecondHand() {
        // Only serialize non-default values 
        return ((_secondHand.Color != Color.Red) || (_secondHand.Width != 1));
    }
    •••
}

It doesn't matter whether your ShouldSerializePropertyName is public or private from the property browser's point of view, but choosing private removes it from client visibility. You can also implement property browser reset functionality programmatically, using ResetPropertyName method:

class ClockControl: Control {
    public Hand SecondHand {
        ...
    }
    private void ResetSecondHand() {
        SecondHand = new Hand(Color.Red, 1);
    }
    •••
}

Implementing ShouldSerialize lets the design-time environment know whether the property should be serialized, but we also need to write custom code to help assist in the generation of appropriate InitializeComponent code. Specifically, we need to return an InstanceDescriptor, which provides the information needed to create an instance of a particular type. The code serializer gets an InstanceDescriptor for a Hand by asking the Hand TypeConverter, as shown in Figure 12.

Figure 12 InstanceDescriptor

internal class HandConverter: ExpandableObjectConverter {
    public override bool CanConvertTo(ITypeDescriptorContext context, 
      Type destinationType) {
        // We can be converted to an InstanceDescriptor 
        if (destinationType == typeof(InstanceDescriptor)) return true;
        return base.CanConvertTo(context, destinationType);
    }
    public override object ConvertTo(ITypeDescriptorContext context, 
      CultureInfo culture, object value, Type destinationType) {
        if (value is Hand) {
            // Convert to InstanceDescriptor 
            if (destinationType == typeof(InstanceDescriptor)) {
                Hand hand = (Hand) value;
                object[] properties = new object[2];
                Type[] types = new Type[2];
                // Color
                types[0] = typeof(Color);
                properties[0] = hand.Color;
                // Width 
                types[1] = typeof(int);
                properties[1] = hand.Width;
                // Build constructor 
                ConstructorInfo ci = typeof(Hand).GetConstructor(types);
                return new InstanceDescriptor(ci, properties);
            }
            •••
        }
    }
    •••
}

InstanceDescriptors require two pieces of information in order to be useful. First, they need to know what the constructor looks like and second, what property values should be used if the object is instantiated. The former is described by the ConstructorInfo type, and the latter is simply an array of values which should be in constructor parameter order. After rebuilding the control, and if ShouldSerializePropertyName permits, all Hand type properties will be serialized using the information provided by the HandConverter-provided InstanceDescriptor:

class ClockControlHostForm: Form {
    •••
    private void InitializeComponent() {
    •••
        // 
        // clockControl1 
        // 
        this.clockControl1.HourHand = 
          new ClockControlLibrary.Hand(System.Drawing.Color.Black, 2);
          •••
    }
}

UITypeEditors

ExpandableObjectConverters help break down a complex multi-value property into nested lists of its atomic values. While this technique simplifies the editing of a complicated property, it may not be suitable for other complex properties, such as single-value properties that exhibit certain behavior. This might include properties that are hard to construct, interpret, or validate, such as regular expressions. The property may also be one of a list of values so large it would be difficult to remember all of them. It might even be a visual property, such as ForeColor, which is not easily represented as a string.

Actually, the ForeColor property satisfies all three points. First, it would be hard to find the color you wanted by typing comma-separated Int32s like "33, 86, 24" or guessing a named color, like "PapayaWhip." Second, there are a lot of colors to choose from, and colors are just plain visual.

In addition to in-place editing in the property browser, properties like ForeColor help the developer by providing an alternative UI-based property editing mechanism, accessed from a dropdown arrow in the property browser, as shown in Figure 13.

Figure 13 Color Properties

Figure 13** Color Properties **

The result is a more attractive and intuitive way to select a property value. This style of visual editing is supported by a design-time feature known as a UITypeEditor, which you can leverage for a similar effect. There are two types of editor you can choose from: modal or dropdown. DropDown editors support single-click property selection from a dropdown UI attached to the property browser. This might be a nice way to enhance the clock control's Face property, allowing developers to visualize the clock face style as they make their selection (see Figure 14).

Figure 14 Custom Clock Face

Figure 14** Custom Clock Face **

You begin implementing an editor by deriving a class from System.Drawing.Design.UITypeEditor:

class FaceEditor : UITypeEditor { ... }

The next step requires you to override UITypeEditor.GetEditStyle and UITypeEditor.EditValue, shown here:

class FaceEditor: UITypeEditor {
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
        ...
    }
    public override object EditValue(ITypeDescriptorContext context, 
      IServiceProvider provider, object value) {
          ...
    }
}

The appropriate UITypeEditor, evaluated and returned by TypeDescriptor.GetEditor, is stored with each property, much like TypeConverters. In this case, when the property browser repaints itself to reflect a control selection in the designer, it queries GetEditStyle to determine whether it should show a dropdown button, an open dialog button, or nothing in the property value box when the property is selected. This behavior is determined by a value from the UITypeEditorEditStyle enumeration:

enum System.Drawing.Design.UITypeEditorEditStyle { 
  DropDown, // Display dropdown 
  UI Modal, // Display modal dialog 
  UI None // Don't display a UI 
}

Not overriding GetEditStyle is the same as returning UITypeEditorEditStyle.None, the default edit style. Neither option is useful if you have a pretty UI waiting in the wings. Luckily, the clock control does:

public class FaceEditor: System.Drawing.Design.UITypeEditor {
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
        if (context != null) return UITypeEditorEditStyle.DropDown;
        return base.GetEditStyle(context);
    }
    •••
}

ITypeDescriptorContext is passed into GetEditStyle to provide contextual information regarding the execution of this method. This includes:

  • The container and, subsequently, the designer and its components
  • The design-time instance represented by the property browser
  • A PropertyDescriptor type describing the property, including the TypeConverter and UITypeEditor assigned to the component
  • A PropertyDescriptorGridEntry type, which is a composite of the PropertyDescriptor and the property's associated grid entry in the property browser

Where GetEditStyle is used to initialize how the property browser behaves, EditValue actually implements the defined behavior. Whether the UI editor is dropdown or modal, you'll follow the same basic steps to edit the value. First, access the property browser's UI display service, IWindowsFormsEditorService, and create an instance of the UI editor, passing a reference to the editor service. Then pass it the current property value. Display the UI editor and then close it upon value selection. Finally, return the new property value from the editor. Figure 15 shows how the clock control implements these steps.

Figure 15 Editing the Value

public class FaceEditor: UITypeEditor {
    •••
    public override object EditValue(ITypeDescriptorContext context, 
      IServiceProvider provider, object value) {
        if ((context != null) && (provider != null)) {
            // Access the property browser's UI display service, 
            // IWindowsFormsEditorService 
            IWindowsFormsEditorService editorService = 
              (IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService));
            if (editorService != null) {
                // Create an instance of the UI editor, passing a reference to 
                // the editor service 
                FaceEditorControl dropDownEditor = 
                  new FaceEditorControl(editorService);
                // Pass the UI editor the current property value 
                dropDownEditor.Face = (ClockFace) value;
                // Display the UI editor 
                dropDownEditor.EditorService = editorService;
                editorService.DropDownControl(dropDownEditor);
                // Return the new property value from the editor 
                return dropDownEditor.Face;
            }
        }
        return base.EditValue(context, provider, value);
    }
}

When it comes to displaying the UI editor, we need to play nicely in the Visual Studio® .NET development environment, particularly regarding UI positioning in relation to the property browser. Specifically, dropdown UI editors must appear flush against the bottom of the property entry in the property browser and be sized to the width of the property entry. The property browser exposes a service, IWindowsFormsEditorService, to manage the loading and unloading of UI editors as well as their positioning inside the development environment. FaceEditor references it and calls IWindowsFormsEditorService.DropDownControl to display the FaceEditorControl, relative to the property entry. Once displayed, FaceEditorControl has the responsibility of capturing the user selection and returning control back to EditValue with the new value. This requires calling IWindowsFormsEditorService.CloseDropDown from FaceEditorControl, to which we pass the editorService reference, shown in Figure 16.

Figure 16 FaceEditorControl

public class FaceEditorControl: UserControl {
    public FaceEditorControl(IWindowsFormsEditorService editorService) {
        •••
    _editorService = editorService;
    }
    private ClockFace _face = ClockFace.Both;
    private IWindowsFormsEditorService _editorService = null;
    private void picBoth_Click(object sender, System.EventArgs e) {
        _face = ClockFace.Both;
        // Close the UI editor upon value selection 
        _editorService.CloseDropDown();
    }
    private void picAnalog_Click(object sender, System.EventArgs e) {
        ...
    }
    private void picDigital_Click(object sender, System.EventArgs e) {
        ...
    }
    •••
}

The final step is to associate the FaceEditor with the Face property, accomplished by adorning the property with the EditorAttribute, as shown here:

[Category("Appearance"), 
  Description("Determines which style of clock face to display"), 
  DefaultValue(ClockFace.Both), 
  Editor(typeof(FaceEditor), 
  typeof(System.Drawing.Design.UITypeEditor))] 
  public ClockFace Face {
      ...
}

While dropdown editors are suitable for single-click selection, there are times when unrestricted editing is required. In such situations, you would use a modal UITypeEditor, implemented as a dialog box. While you follow the same logical steps as a dropdown editor, there are three minor implementation differences.

  • Returning UITypeEditorEditStyle.Modal from UITypeEditor.GetEditStyle
  • Calling editorService.ShowDialog(modalEditor) from EditValue to open the editor dialog
  • Not requiring an editor service reference to be passed to the dialog, since a Windows® Form can close itself

A modal Digital Time Format UITypeEditor, shown in Figure 17, is included in the code sample to demonstrate these differences (see the link at the top of this article).

Figure 17 Digital Time Format UITypeEditor

Figure 17** Digital Time Format UITypeEditor **

Designers

So far, we have explored how properties are exposed to the developer at design time and discussed some of the key infrastructure provided by the .NET Framework to improve the experience, culminating in the UITypeEditor. While the focus has been on properties, they aren't the only aspect of a control that operates differently in design-time and run-time modes. In some situations, a control's UI might render differently between these modes. For example, the SplitterBar control displays a dashed border when its BorderStyle is set to BorderStyle.None, making it easier for developers to find on the form's design surface between two Panel controls (which also display a dashed border in the absence of visible one) (see Figure 18).

Figure 18 Border

Figure 18** Border **

Because BorderStyle.None means "don't render a border at run time," the dashed border is only drawn at design time for the developer's benefit. Of course, if BorderStyle is set to BorderStyle.FixedSingle or BorderStyle.Fixed3D, the dashed border is not necessary.

What's interesting about both splitter and panel controls is that the dashed border is not actually rendered from either control implementation. Instead, this work is conducted on behalf of them by a Designer, another .NET Framework design-time feature that follows the tradition honored by TypeConverters and UITypeEditors of separating out design-time logic from the control.

Designers are not the same as designer hosts or the Windows Forms designer, although a strong relationship exists between designers and designer hosts. Every control or component you add to a form has a matching designer created for it and stored by the DesignerHost. This occurs at the same time components and controls are "sited." Like TypeConverters and UITypeEditors, TypeDescriptor does the work of creating a designer from TypeDescriptor.CreateDesigner. Adorning a type with the DesignerAttribute ties it to the specified designer. For components and controls that don't provide their own custom designers, the .NET Framework provides base designer implementations—ComponentDesigner and ControlDesigner, respectively, both of which implement IDesigner:

public interface IDesigner: IDisposable {
    public void DoDefaultAction();
    public void Initialize(IComponent component);
    public IComponent Component {
        get;
    }
    public DesignerVerbCollection Verbs {
        get;
    }
}

The technique used by panel and splitter controls could also be used by our clock control. The clock face is round at design time when the clock control is either Analog or Analog and Digital, making it difficult to determine where the edges and corners of the control are, particularly when positioning against other controls. A dashed border will help out in those design-time situations, as shown in Figure 19.

Figure 19 Clock Control

Figure 19** Clock Control **

Because the clock is a control instead of just a component, we begin by deriving from the ControlDesigner base class since plain components would need a designer that is derived from ComponentDesigner, as shown here:

public class ClockControlDesigner : ControlDesigner { ... }

To paint the dashed border, ClockControlDesigner will need to override the Initialize and OnPaintAdornments methods:

public class ClockControlDesigner: ControlDesigner {
    •••
public override void Initialize(IComponent component) {
    ...
    }
    protected override void OnPaintAdornments(PaintEventArgs e) {
        ...
    }
    •••
}

Initialize is overridden to deploy initialization logic that's executed as the control is being sited. It's also a good location to create a shortcut reference to the designed control:

public class ClockControlDesigner: ControlDesigner {
    private ClockControl _clockControl = null;
    public override void Initialize(IComponent component) {
        base.Initialize(component);
        // Get clock control shortcut reference 
        _clockControl = (ClockControl) component;
    }
    •••
}

While you could manually register with Control.OnPaint to add your design time UI, you'll find that overriding OnPaintAdornments is a better option because it is only called after the control's design-time/run-time UI is painted, letting you put the icing on the cake (see Figure 20). Simply adding DesignerAttribute to the ClockControl class completes the association:

[ Designer(typeof(ClockControlDesigner)) ] 
class ClockControl : Control { ... }

Figure 20 Overriding OnPaintAdornments

public class ClockControlDesigner: ControlDesigner {
    •••
    protected override void OnPaintAdornments(PaintEventArgs e) {
        base.OnPaintAdornments(e);
        // Don't show border if it does not have an Analog face 
        if (_clockControl.Face == ClockFace.Digital) return;
        // Draw border 
        Graphics g = e.Graphics;
        using(Pen pen = new Pen(Color.Gray, 1)) {
            pen.DashStyle = DashStyle.Dash;
            g.DrawRectangle(pen, 0, 0, _clockControl.Width - 1, 
              _clockControl.Height - 1);
        }
    }
    •••
}

Design Time-only Properties

Now the clock control is working the way we hoped in Figure 19. One way to improve on this is to optionally show the border. Adding a design time-only ShowBorder property will do the trick, since this is not a feature that should be accessible at run time. Implementing it on the control itself is not ideal because the control operates in both design-time and run-time modes. Designers are exactly the right location for design-time properties, especially because they offer the infrastructure that is needed to expose this property. You start off by adding the basic property implementation, just like you would on a standard control or component implementation (see Figure 21).

Figure 21 Border Property Implementation

public class ClockControlDesigner: ControlDesigner {
    •••
    private bool _showBorder = true;
    •••
    protected override void OnPaintAdornments(PaintEventArgs e) {
        •••
        // Don't show border if hidden or does not have an Analog face 
        if ((!_showBorder) || (_clockControl.Face == ClockFace.Digital)) return;
        •••
    }
    // Provide implementation of ShowBorder to provide 
    // storage for created ShowBorder property 
    private bool ShowBorder {
        get {
            return _showBorder;
        }
        set {
            __showBorder = value;
            _clockControl.Refresh();
        }
    }
}

This isn't enough on its own, however, since the property browser won't examine a Designer for properties when the associated control is selected on the form. Somehow, we need to include ShowBorder in the list of properties returned to the property browser from TypeDescriptor.GetProperties. Fortunately, GetProperties uses the designer host's ITypeDescriptorFilterService that ultimately calls your designer's PreFilterProperties method, and it's this hook that you can leverage to dynamically add new properties before they are used, as shown in Figure 22. Now that we've added a design time-only property, it's shown in the property browser, but is unavailable at run time.

Figure 22 PreFilterProperties Method

public class ClockControlDesigner: ControlDesigner {
    •••
protected override void PreFilterProperties(IDictionary properties) {
        base.PreFilterProperties(properties);
        // Create design-time only property entry and add it to the 
        // property browser's Design category 
        properties["ShowBorder"] = 
          TypeDescriptor.CreateProperty(typeof(ClockControlDesigner), 
          "ShowBorder", typeof(bool), CategoryAttribute.Design, 
          DesignOnlyAttribute.Yes);
    }
    •••
}

Figure 23 Overriding Properties

Figure 23** Overriding Properties **

If you need to alter or remove existing properties, you can override PostFilterProperties. Pre/Post filter pairs can also be overridden for methods and events if you find it necessary. If you need to alter or remove existing properties, you can override PostFilterProperties (see Figure 23).

Conclusion

In these two articles we've shown how you can build and debug controls that target the .NET Framework design-time features, a must if you plan to integrate your components into the Visual Studio .NET development environment. In the April 2003 issue, we showed you how to use attributes and interfaces to enhance a control's relationship with the property browser. This article continued the discussion, introducing more advanced features like TypeConverters, UITypeEditors, and Designers. Taken together, you can create professional-level controls that will complement all of your .NET Framework-based projects.

For background information see:
Writing Custom Designers for .NET Components
Make Your Components Really RAD with Visual Studio .NET Property Browser

Michael Weinhardtis a software engineer at SERF, a retirement fund company where he develops apps using the .NET Framework. He can be reached at mikedub@optusnet.com.au.

Chris Sellsis an independent consultant, speaker, and author specializing in distributed applications in the .NET Framework and COM. More information about Chris and his various projects is available at https://www.sellsbrothers.com.