UI Type Editors






UI Type Editors

ExpandableObjectConverters help break down a complex multivalue property into a nested list of its atomic values. Although this technique simplifies editing of a complicated property, it may not be suitable for other properties that exhibit the following behavior:

  • Hard to construct, interpret, or validate, such as a regular expression

  • One of a list of values so large it would be difficult to remember all of them

  • A visual propertysuch as ForeColor or BackColorthat is better represented as something other than a string

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

In addition to supporting in-place editing in the Properties window, properties such as ForeColor help the developer by providing an alternative UI-based property-editing mechanism. You access this tool, shown in Figure, from a drop-down arrow in the Properties window.

24. Color Property Drop-Down UI Editor (See Plate 18)


The result is a prettier, more intuitive way to select a property value. This style of visual editing is supported by the UI type editor, a design-time feature that you can use to similar effect. There are two kinds to choose from: modal or drop-down. Drop-down editors support single-click property selection from a drop-down UI attached to the Properties window. This UI might be a nice way to enhance the AlarmClockControl's Face property, allowing developers to visualize the clock face style as they make their selection, as illustrated in Figure.

25. Custom View Drop-Down UI Editor


You begin implementing a custom UI editor by deriving from the UITypeEditor class (from the System.Drawing.Design namespace):

// FaceEditor.cs
class FaceEditor : UITypeEditor {...}

Next, you override the GetEditStyle and EditValue methods from the UITypeEditor base class:

// FaceEditor.cs
class FaceEditor : UITypeEditor {
  public override UITypeEditorEditStyle GetEditStyle(
    ITypeDescriptorContext context) {...}

  public override object EditValue(
    ITypeDescriptorContext context,
    IServiceProvider provider,
    object value) {...}
}

As with type converters, the appropriate UI type editorprovided by the GetEditor method of the TypeDescription classis stored with each property. When the Properties window updates itself to reflect a control selection in the Windows Forms Designer, it queries GetEditStyle to determine whether it should show a drop-down 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:

namespace System.Drawing.Design {
  enum UITypeEditorEditStyle {
    None = 1 // Don't display a UI (default)
    Modal = 2, // Display a modal dialog UI
    DropDown = 3, // Display a drop-down UI
  }
}

Not overriding GetEditStyle is the same as returning UITypeEditorEditStyle.None, which is the default edit style. To show the drop-down UI editor, AlarmClockControl returns UITypeEditorEditStyle.DropDown:

// FaceEditor.cs
class FaceEditor : UITypeEditor {
  public override UITypeEditorEditStyle GetEditStyle(
    ITypeDescriptorContext context) {
    // Specify a drop-down UITypeEditor
    return UITypeEditorEditStyle.DropDown;
  }
  ...
}

ITypeDescriptorContext is passed to GetEditStyle to provide contextual information regarding the execution of this method, including the following:

  • The container and, subsequently, the designer host and its components

  • The component design-time instance being shown in the Properties window

  • A PropertyDescriptor type describing the property, including the TypeConverter and UITypeEditor assigned to the component

Whereas GetEditStyle is used to initialize the way the property behaves, EditValue actually implements the defined behavior. Whether the UI editor is drop-down or modal, you follow the same basic steps to edit the value:

1.
Access IWindowsFormsEditorService, the Properties window's UI display service.

2.
Create an instance of the editor UI implementation, which is a control that the Properties window will display.

3.
Pass the current property value to the UI editor control.

4.
Ask the Properties window to display the UI editor control.

5.
Let the user choose the value and close the UI editor control.

6.
Return the new property value from the editor.

Drop-Down UI Type Editors

Here's how AlarmClockControl implements these steps to show a drop-down editor for the Face property:

// FaceEditor.cs
class FaceEditor : UITypeEditor {
  ...
  public override object EditValue(
    ITypeDescriptorContext context,
    IServiceProvider provider,
    object value) {

    if( (context != null) && (provider != null) ) {
      // Access the Properties window's UI display service
      IWindowsFormsEditorService editorService =
        (IWindowsFormsEditorService)
          provider.GetService(typeof(IWindowsFormsEditorService));

      if( editorService != null ) {
        // Create an instance of the UI editor control,
        // passing a reference to the editor service
FaceEditorControl dropDownEditor =
          new FaceEditorControl(editorService);

         // Pass the UI editor control the current property value
         dropDownEditor.Face = (ClockFace)value;

         // Display the UI editor control
         editorService.DropDownControl(dropDownEditor);

         // Return the new property value from the UI editor control
         return dropDownEditor.Face;
     }
   }
   return base.EditValue(context, provider, value);
 }
}

When it comes to displaying the UI editor control, you must play nicely in the design-time environment, particularly regarding UI positioning in relation to the Properties window. Specifically, drop-down UI editors must appear flush against the bottom of the property entry.

To facilitate this, the Properties window exposes a servicean implementation of the IWindowsFormsEditorService interfaceto manage the loading and unloading of UI editor controls as well as their positioning inside the development environment. The FaceEditor type references this service and calls its DropDownControl method to display the FaceEditorControl, relative to the Properties window edit box. When displayed, FaceEditorControl captures the user selection and returns control to EditValue with the new value. This requires a call to IWindowsFormsEditorService.CloseDropDown from FaceEditorControl, something you do by passing to FaceEditorControl a reference to the IWindowsFormsEditorService interface via its constructor:

// FaceEditorControl.cs
partial class FaceEditorControl : UserControl {
  ...
  ClockFace face = ClockFace.Both;
  IWindowsFormsEditorService editorService = null;
  ...
  public FaceEditorControl(IWindowsFormsEditorService editorService) {
    InitializeComponent();
    this.editorService = editorService;
  }

  public ClockFace Face {
    get { return this.face; }
    set { this.face = value; }
  }

  void bothPictureBox_Click(object sender, EventArgs e) {
    this.face = ClockFace.Both;
    // Close the UI editor upon value selection
    this.editorService.CloseDropDown();
  }

  void analogPictureBox_Click(object sender, EventArgs e) {
    this.face = ClockFace.Analog;
    // Close the UI editor upon value selection
    this.editorService.CloseDropDown();
  }

  void digitalPictureBox_Click(object sender, EventArgs e) {
    this.face = ClockFace.Digital;
    // Close the UI editor upon value selection
    this.editorService.CloseDropDown();
  }
  ...
}

The final step is to associate FaceEditor with the Face property by adorning the property with the Editor attribute:

[Category("Appearance")]
[Description("Determines the clock face type to display.")]
[DefaultValue(ClockFace.Both)]
[Editor(typeof(FaceEditor), typeof(UITypeEditor))]
public ClockFace Face {...}

Now FaceEditor is in place for the Face property. When a developer edits that property in the Properties window, it shows a drop-down arrow and the FaceEditorControl as the UI the developer uses to choose a value of the ClockFace enumeration.

If the UI editor control you are using is resizable friendly, you can override UITypeEditor's IsDropDownResizable property to return true, rather than the default of false:

// FaceEditor.cs
partial class FaceEditor : UITypeEditor {
  ...
  // If the UI editor control is resizable, override this
  // property to include a sizing grip on the Properties
  // window drop-down
  public override bool IsDropDownResizable {
    get { return true; }
  }
  ...
}

This tiny update ensures that the UITypeEditor adds a size grip to your UI editing control, as illustrated in Figure.

26. Custom View Drop-Down UI Editor with Size Grip


Drop-down editors are a great way to enhance the usability of single-click value selection.

Modal UI Type Editors

Sometimes, single-click selection isn't the most appropriate; sometimes, unrestricted editing is more desirable. In such situations, you use a modal UITypeEditor implemented as a modal form. For example, AlarmClockControl's digital time format is sufficiently complex to edit in a separate dialog outside the Properties window:

// AlarmClockControl.cs
partial class AlarmClockControl : ... {
  ...
  string digitalTimeFormat = "dd/MM/yyyy hh:mm:ss tt";
  ...
  [Category("Appearance")]
  [Description("The digital time format, ... ")]
  [DefaultValue("dd/MM/yyyy hh:mm:ss tt")]
  public string DigitalTimeFormat {
    get {...}
    set {...}
 }
  ...
}

Date and Time format strings are composed of a complex array of format specifiers that are not easy to remember and certainly aren't intuitive in a Properties window, as shown in Figure.

27. The DigitalTimeFormat Property


Modal UITypeEditors are an ideal way to provide a more intuitive way to construct hard-to-format property values. By providing a custom form, you give developers whatever editing experience is the most suitable for that property type. Figure illustrates how the Digital Time Format Editor dialog makes it easier to edit AlarmClockControl's DigitalTimeFormat property.

28. Custom DigitalTimeFormat Modal UI Editor


A modal UITypeEditor actually requires slightly different code from that of its drop-down counterpart. You follow the same logical steps as with a drop-down editor, with three minor implementation differences:

  • You return UITypeEditorEditStyle.Modal from UITypeEditor.GetEditStyle.

  • You call IWindowsFormsEditorService.ShowDialog from EditValue to open the UI editor dialog.

  • You don't pass the dialog an editor service referenceto call its CloseDropDown methodbecause Windows Forms can close themselves, unlike user controls.

AlarmClockControl's modal UI type editor is shown here:

// DigitalTimeFormatEditor.cs
class DigitalTimeFormatEditor : UITypeEditor {
  public override UITypeEditorEditStyle GetEditStyle(
    ITypeDescriptorContext context) {
    // Specify a modal UITypeEditor
    return UITypeEditorEditStyle.Modal;
  }
  public override object EditValue(
    ITypeDescriptorContext context,
    IServiceProvider provider,
    object value) {

    if( (context != null) && (provider != null) ) {
      // Access the Properties window's UI display service
      IWindowsFormsEditorService editorService =
        (IWindowsFormsEditorService)
           provider.GetService(typeof(IWindowsFormsEditorService));
      if( editorService != null ) {
        // Create an instance of the UI editor dialog
        DigitalTimeFormatEditorForm modalEditor =
          new DigitalTimeFormatEditorForm();

      // Pass the UI editor dialog the current property value
      modalEditor.DigitalTimeFormat = (string)value;

      // Display the UI editor dialog
      if( editorService.ShowDialog(modalEditor) == DialogResult.OK ) {
        // Return the new property value from the UI editor dialog
        return modalEditor.DigitalTimeFormat;
      }
     }
    }
    return base.EditValue(context, provider, value);
  }
}

At this point, normal dialog activities (as covered in Chapter 3: Dialogs) apply for the UI editor's modal form:

// DigitalTimeFormatEditorForm.cs
partial class DigitalTimeFormatEditorForm : Form {
  ...
  string digitalTimeFormat = "dd/MM/yyyy hh:mm:ss tt";
  ...
  public string DigitalTimeFormat {
    get { return this.digitalTimeFormat; }
set { this.digitalTimeFormat = value; }
 }
  ...
  void okButton_Click(object sender, EventArgs e) {
   this.digitalTimeFormat = this.formatTextBox.Text;
  }
  ...
}

Again, to associate the new UI type editor with the property, you apply the Editor attribute:

[Category("Appearance")]
[Description("The digital time format, ...")]
[DefaultValue("dd/MM/yyyy hh:mm:ss tt")]
[Editor(typeof(DigitalTimeFormatEditor), typeof(UITypeEditor))]
public string DigitalTimeFormat {...}

After the Editor attribute is applied, developers access the modal UITypeEditor via an ellipsis-style button displayed in the Properties window, as shown in Figure.

29. Accessing a Modal UITypeEditor


UI type editors allow you to give developers a customized editing environment on a per-property basis, whether it's a drop-down UI to support selection from a list of possible values or a modal dialog to provide an entire editing environment outside the Properties window.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows