DekGenius.com
[ Team LiB ] Previous Section Next Section

8.3 Custom Type Editors

The PropertyGrid allows us to replace the built-in text-based editing. We can assign a custom editor that supplies its own user interface. The framework calls such editors UI Type Editors. Not only can these provide a special-purpose editing user interface, they can change how the property's value is displayed even when we are not editing its value.

Supplying a UI Type Editor is simple. We simply write a class that derives from System.Drawing.Design.UITypeEditor and associate it with the property or type in question using the Editor attribute. We only need to override two methods in our editor class. The first, GetEditStyle, is called to determine what style of editing UI we support; we can open a standalone modal dialog, drop down a UI in the PropertyGrid itself, or supply no editing UI. The second method, EditValue, is called when we are required to show our editing interface.

Let us add a new property to our CustomerDetails class (as shown in Example 8-22) so that we can supply a custom editing user interface for it. The new property is Happiness, and it indicates the level of customer satisfaction, on a range of 0 to 100%. It is shown in Examples Example 8-23 and Example 8-24. The editor has been specified with the Editor attribute. (The second parameter is always required to be UITypeEditor in the current version of the framework.) The property's type here is int or Integer, but we can provide custom UI editors for any type, whether it is a custom type or a built-in type.

Example 8-23. Happiness property with editor using C#
private int happy;

[Editor(typeof(HappinessEditor), typeof(UITypeEditor))]
public int Happiness
{
    get { return happy; }
    set { happy = value; }
}
Example 8-24. Happiness property with editor using VB
Private happy As Integer

<Editor(GetType( HappinessEditor), GetType(UITypeEditor))> _
Public Property Happiness As Integer
    Get
        Return happy
    End Get
    Set 
        happy = Value
    End Set
End Property

The simplest editing user interface that we can show is a modal dialog. Creating an editor class to provide this is very straightforward. Examples Example 8-25 and Example 8-26 show a custom type editor that simply presents a message box as its user interface. It asks the user if they are happy (yes or no), and sets their happiness as either 100% or 0% accordingly. (If the user hits cancel, the happiness is left unaltered.)

Example 8-25. Modal dialog custom type editor using C#
public class HappinessEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(
        ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.Modal;
    }

    public override object EditValue(ITypeDescriptorContext context,
        IServiceProvider provider, object value)
    {
        DialogResult rc = MessageBox.Show("Are you happy?",
  "Happiness", MessageBoxButtons.YesNoCancel);
        if (rc == DialogResult.Yes)
  return 100;
        if (rc == DialogResult.No)
  return 0;
        return value;
    }
}
Example 8-26. Modal dialog custom type editor using VB
Public Class HappinessEditor 
    Inherits UITypeEditor

    Public Overloads Overrides Function GetEditStyle( _
           context As ITypeDescriptorContext) _
           As UITypeEditorEditStyle 
        Return UITypeEditorEditStyle.Modal
    End Function

    Public Overloads Overrides Function EditValue( _
           context As ITypeDescriptorContext, _
           provider As IServiceProvider, _
           value As Object) As Object
        Dim rc As MsgBoxResult = MsgBox("Are you happy?", _
  MsgBoxStyle.YesNoCancel, "Happiness")
        If rc = MsgBoxResult.Yes Then
  Return 100
        ElseIf rc = MsgBoxResult.No Then
  Return 0
        End If
        Return value
    End Function
End Class

The PropertyGrid will indicate the availability of this editor by placing a small button with an ellipsis in the property's value field when it is given the focus, as shown in Figure 8-11. The PropertyGrid knows to show the button because of the value returned by our editor class's GetEditStyle method.

Figure 8-11. A modal editing UI offered in a PropertyGrid
figs/winf_0811.gif

Modal dialog editors are easy to write, but they are usually not the most convenient kind of editor to use. Most of the system-supplied editors use the drop-down style, because it is less disruptive to the use of the program and makes it feel as if the property is an integrated part of the PropertyGrid.

Showing a drop-down editor is almost as easy as showing a modal dialog. The main difference is that we have to supply a control rather than a form. A UserControl is likely to be the easiest option, although you can use a custom control. (In fact, you could even use one of the built-in controls.)

Examples Example 8-27 and Example 8-28 show a type editor that displays a control called HappinessControl. The most interesting part of this is the way in which it displays the control as a drop-down editor in the PropertyGrid. To do this, we must ask the grid for a service object. We do this through the IServiceProvider passed as the provider argument—this is a generic interface that allows hosted components to ask their environment for certain facilities. In this case, we are asking the grid control for the service that lets us display drop-down editors, which is provided through the IWindowsFormsEditorService interface.

Example 8-27. A drop-down type editor using C#
public class HappinessEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(
        ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }

    public override object EditValue(ITypeDescriptorContext context,
        IServiceProvider provider, object value)
    {
        IWindowsFormsEditorService wfes = provider.GetService(
  typeof(IWindowsFormsEditorService)) as
  IWindowsFormsEditorService;
        if (wfes != null)
        {
  HappinessControl hc = new HappinessControl();
  hc.Value = (int) value;
  wfes.DropDownControl(hc);
  value = hc.Value;
        }
        return value;
    }
}
Example 8-28. A drop-down type editor using VB
Public Class HappinessEditor 
    Inherits UITypeEditor

    Public Overloads Overrides Function GetEditStyle( _
           context As ITypeDescriptorContext) _
           As UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function

    Public Overloads Overrides Function EditValue( _
           context As ITypeDescriptorContext, _
           provider As IServiceProvider, _
           value As Object) As Object

        Dim wfes As IWindowsFormsEditorService = _
          CType(provider.GetService( _
          GetType(IWindowsFormsEditorService)), _
          IWindowsFormsEditorService)
        If Not wfes Is Nothing Then
  Dim hc As New HappinessControl()
  hc.Value = CInt(value)
  wfes.DropDownControl(hc)
  value = hc.Value
        End If
        Return value
    End Function

End Class

When we call the DropDownControl method on the service object, it displays our control in the appropriate position on the PropertyGrid. It adjusts its size so that it is the same width as the value field, as shown in Figure 8-12. (The HappinessControl is just a UserControl. The most interesting code is the part that draws the face, which we will see shortly.)

Figure 8-12. A drop-down editor in action
figs/winf_0812.gif

As you can see, in contrast to the rather dry yes/no interface provided by the MessageBox class or the VB MsgBox function, here we have gone for a slightly more emotive interface—the customer satisfaction level is indicated by how happy or sad the face looks. The code from the Happiness control that draws this is shown in Examples Example 8-29 and Example 8-30.

Example 8-29. Putting a happy face on the control using C#
public static void PaintFace(Graphics g, Rectangle r, int happiness)
{
    r.Width -= 1; r.Height -= 1;
    float w = r.Width;
    float h = r.Height;

    // Draw face
    g.FillEllipse(Brushes.Yellow, r);
    g.DrawEllipse(Pens.Black, r);

    // Draw eyes
    float eyeLevel =  h / 4;
    float eyeOffset = w / 4;
    float eyeSize =   w / 6;
    g.FillEllipse(Brushes.Black, w/2 - eyeOffset, eyeLevel,
        eyeSize, eyeSize);
    g.FillEllipse(Brushes.Black, w/2 + eyeOffset - eyeSize + 1, eyeLevel,
        eyeSize, eyeSize);

    // Draw smile
    float smileWidth = w/3;
    float smileLevel = (h*7)/10;
    float offs = ((happiness - 50) * h/4)/100;
    PointF[] points =
        {
  new PointF ((w - smileWidth)/2, smileLevel),
  new PointF ((w - smileWidth)/2, smileLevel + offs),
  new PointF ((w + smileWidth)/2+1, smileLevel + offs),
  new PointF ((w + smileWidth)/2+1, smileLevel)
    };
    g.DrawBeziers(Pens.Black, points);
}
Example 8-30. Putting a happy face on the control using VB
Public Shared Sub PaintFace(g As Graphics, r As Rectangle, _
                  happiness As Integer)
   r.Width -= 1
   r.Height -= 1
   Dim w As Single = r.Width
   Dim h As Single = r.Height

   ' Draw face
   g.FillEllipse(Brushes.Yellow, r)
   g.DrawEllipse(Pens.Black, r)

   ' Draw eyes
   Dim eyeLevel As Single =  h / 4
   Dim eyeOffset As Single = w / 4
   Dim eyeSize As Single = w / 6
   g.FillEllipse(Brushes.Black, w/2 - eyeOffset, eyeLevel, _
       eyeSize, eyeSize)
   g.FillEllipse(Brushes.Black, w/2 + eyeOffset - eyeSize + 1, _
       eyeLevel, eyeSize, eyeSize)

   ' Draw smile
   Dim smileWidth As Single = w/3
   Dim smileLevel As Single = (h*7)/10
   Dim offs As Single = ((happiness - 50) * h/4)/100
   Dim points() As PointF = _
     {   new PointF ((w - smileWidth)/2, smileLevel), _
         new PointF ((w - smileWidth)/2, smileLevel + offs), _ 
         new PointF ((w + smileWidth)/2+1, smileLevel + offs), _
         new PointF ((w + smileWidth)/2+1, smileLevel) }
   g.DrawBeziers(Pens.Black, points)
End Sub

The PropertyGrid control also lets us draw into the value field even when our editor is not running. Because the PaintFace method shown in Examples Example 8-29 and Example 8-30 has been written to scale its drawing to whatever space is available, we can call the same code from our HappinessEditor class to draw a small version of the face into the PropertyGrid, as shown in Examples Example 8-31 and Example 8-32.

Example 8-31. Custom value painting using C#
public override bool GetPaintValueSupported(
    ITypeDescriptorContext context)
{
    return true;
}

public override void PaintValue(PaintValueEventArgs e)
{
    System.Drawing.Drawing2D.SmoothingMode sm = e.Graphics.SmoothingMode;
    e.Graphics.SmoothingMode =
        System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    HappinessControl.PaintFace(e.Graphics, e.Bounds, (int) e.Value);
    e.Graphics.SmoothingMode = sm;
}
Example 8-32. Custom value painting using VB
Public Overloads Overrides Function GetPaintValueSupported( _
       context As ITypeDescriptorContext) As Boolean
    Return True
End Function

Public Overloads Overrides Sub PaintValue(e As PaintValueEventArgs)

    Dim sm As SmoothingMode = e.Graphics.SmoothingMode
    e.Graphics.SmoothingMode = SmoothingMode.HighQuality
    HappinessControl.PaintFace(e.Graphics, e.Bounds, CInt(e.Value))
    e.Graphics.SmoothingMode = sm

End Sub

To get our drawing into the value field, we are required to override the GetPaintValueSupported method and return true. Having done this, the PropertyGrid will call our PaintValue method whenever it repaints the value field. We just call the static PaintFace method supplied by the HappinessControl. Notice how we turn on the high-quality smoothing mode on the Graphics object first—this enables antialiasing, which is particularly important on small drawings. Without switching this on, the drawing would look rather ragged. Having changed the smoothing mode, it is important to restore it to its original value before the method terminates, because we are drawing with the same Graphics object that the PropertyGrid uses to draw itself. The results of this painting can be seen in Figure 8-13.

Figure 8-13. Drawing in the value field
figs/winf_0813.gif
    [ Team LiB ] Previous Section Next Section