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 argumentthis 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 interfacethe 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 firstthis 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


. Net Windows Forms in a Nutshell
.NET Windows Forms in a Nutshell
ISBN: 0596003382
EAN: 2147483647
Year: 2002
Pages: 794

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net