Design-Time Integration—Custom Designers

Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio


Jump to: navigation, search
CSharp-Online.NET:Articles
C# Articles

Design-Time Integration of Windows Forms Components

© 2004 Chris Sells

Custom Designers

So far, you have seen how properties are exposed to the developer at design time, and you've seen some of the key infrastructure provided by .NET to improve the property-editing experience, culminating in UITypeEditor. Although the focus has been on properties, they aren't the only aspect of a control that operates differently in design-time mode compared with run-time mode. In some situations, a control's UI might render differently between these modes.

For example, the Splitter control displays a dashed border when its BorderStyle is set to BorderStyle.None. This design makes it easier for developers to find this control on the form's design surface in the absence of a visible border, as illustrated in Figure 9.32.


Image:WinFormsCSharp9-32.jpg
Figure 9.32. Splitter Dashed Border When BorderStyle Is None


Because BorderStyle.None means "don't render a border at run time," the dashed border is drawn only 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, as illustrated by Figure 9.33.


Image:WinFormsCSharp9-33.jpg
Figure 9.33. Splitter with BorderStyle.Fixed3D


What's interesting about the splitter control is that the dashed border is not actually rendered from the control implementation. Instead, this work is conducted on behalf of them by a custom designer, another .NET design-time feature that follows the tradition, honored by type converters and UI type editors, of separating design-time logic from the control.

Custom designers are not the same as designer hosts or the Windows Forms Designer, although a strong relationship exists between designers and designer hosts. As every component is sited, the designer host creates at least one matching designer for it. As with type converters and UI type editors, the TypeDescriptor class does the work of creating a designer in the CreateDesigner method. Adorning a type with DesignerAttribute ties it to the specified designer. For components and controls that don't possess their own custom designers, .NET provides ComponentDesigner and ControlDesigner, respectively, both of which are base implementations of IDesigner:

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

For example, the clock face is round at design time when the clock control either is Analog or is Analog and Digital. This makes it difficult to determine where the edges and corners of the control are, particularly when the clock is being positioned against other controls. The dashed border technique used by the splitter would certainly help, looking something like Figure 9.34.


Image:WinFormsCSharp9-34.jpg
Figure 9.34. Border Displayed from ClockControlDesigner


Because the clock is a custom control, its custom designer will derive from the ControlDesigner base class (from the System.Windows.Forms.Design namespace):

public class ClockControlDesigner : ControlDesigner { ... }

To paint the dashed border, ClockControlDesigner overrides 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 cache a reference to the control being designed:

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

You could manually register with Control.OnPaint to add your design-time UI, but you'll find that overriding OnPaintAdornments is a better option because it is called only after the control's design-time or run-time UI is painted, letting you put the icing on the cake:

public class ClockControlDesigner : ControlDesigner {
  ...
  protected override void OnPaintAdornments(PaintEventArgs e) {
    // Let the base class have a crack
    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);
      }
    }
   ...
   }

Adding DesignerAttribute to the ClockControl class completes the association:

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


Previous_Page_.gif Next_Page_.gif

Today's Deals: Electronics

Personal tools