RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Gain Design-Time Power with Control Editors, Part 1 of 2 : Page 4

Learn how to support your controls with advanced visualization and property editors in the Properties window.

Building the Map Editor
If you select the Map property in the Properties window, an ellipsis appears on the right. When you click the ellipsis you'll see a file selection dialog that lets you pick a map file.

The first step in providing this feature is adding the Editor attribute to the Map property definition, as shown in the previous section. The following code fragment shows just the Editor attribute:

   <Editor(GetType(StatesMapEditor), GetType(UITypeEditor))> _
The Editor attribute takes as parameters the type of the class that the Properties window should use to edit the property and the base class from which that class inherits. In this example, the StatesMapEditor class edits the Map property, and StatesMapEditor inherits from the UITypeEditor base class.

Listing 1 shows the code for the StatesMapEditor class. The StatesMapEditor class actually inherits from the FileNameEditor class, which inherits from UITypeEditor. FileNameEditor is a pre-built class that can display a file selection dialog.

The StatesMapEditor class does all its interesting work by overriding two methods: InitializeDialog and EditValue. Again the code looks worse than it actually is because of the long parameter lists.

The Properties window calls the InitializeDialog method to prepare the file selection dialog before displaying the dialog. The code here calls the base class's InitializeDialog method and then sets the dialog's Title and Filter properties to values that are appropriate for the Map property.

The EditValue method actually edits the property value. It calls the base class's EditValue method to display the file selection dialog. That call should return the property's new value as determined by the base class's method. If the user selected a file and clicked OK, the result is a string giving the file's name. If the string makes sense, the editor uses the StatesMap class's FromFile method to load the map file and build a StatesMap object. (Download the code to see how the FromFile method works.)

If the user clicked the dialog's Cancel button, then the value returned by the base class's EditValue function is the original value. If the value returned by the call to EditValue is not a string, then it is the original StatesMap value assigned to the Map property. In that case, the code simply returns the original value, so the property in the Properties window is unchanged.

The FileNameEditor base class handles the rest of the details. It tells the Properties window that it can display a file selection dialog to edit its value.

There is a problem though, when the user wants to clear the current file. The file selection dialog does not give users an opportunity to click OK without selecting any file (clearing the file); it lets users pick a file or cancel the operation, but doesn't let them indicate that the Map property should be set to Nothing. To solve the problem, the Map property definition shown in the previous section includes a DefaultValue attribute:

   <DefaultValue(GetType(StatesMap), Nothing)> _
This attribute tells the Properties window to set the Map property to Nothing if the user right-clicks on it and selects Reset. The Properties window also resets the Map property if the user selects the textual representation (map) and presses the Delete key.

Building the LineStyle Editor
The LineStyle property's editor is less standard than the Map property's editor. Lots of properties might need to display a file selection dialog for an editor so the .NET Framework provides a FileNameEditor class to use as a starting point. But fewer properties require displaying a graphical dropdown list (giving users a choice of line style samples in this case), so you need to build the LineStyle property's editor from scratch.

Again, the first step is to add the appropriate attributes to the StateSelector control's LineStyle property. The following code shows how the control defines this property. The code is fairly straightforward. The only interesting point for right now is that the Editor attribute sets the property's editor to the LineStyleEditor class:

   ' Custom line styles for state borders.
   Public Enum LineStyles
      Solid = 0
      Dashed = 1
      Dotted = 2
   End Enum
   Private m_LineStyle As LineStyles = LineStyles.Solid
   <Category("Appearance")> _
   <Description("The line dash style used to draw state borders.")> _
   <Browsable(True)> _
   <Editor(GetType(LineStyleEditor), GetType(UITypeEditor))> _
   Public Property LineStyle() As LineStyles
         Return m_LineStyle
      End Get
      Set(ByVal value As LineStyles)
         m_LineStyle = value
      End Set
   End Property
The LineStyleEditor class inherits from UITypeEditor, and does its work by overriding that class's methods. Listing 2 shows the code.

In Listing 2, the GetEditStyle method simply returns the value DropDown to indicate that this editor displays a dropdown.

The EditValue function does the most interesting work. It first gets an IWindowsFormsEditorService object to help control the dropdown. It converts the function's value parameter into one of the LineStyles enumeration values. It then makes a LineStyleListBox control (described shortly) to use as the dropdown. The code calls the editor service's DropDownControl method to display the LineStyleListBox as a dropdown. Finally, the function returns the value selected in the LineStyleListBox suitably converted into a LineStyles value.

The GetEditStyle and EditValue functions let the Properties window edit the LineStyle property. The next two methods let it display a graphical representation of the LineStyle property's value:

  • Function GetPaintValueSupported simply returns True to indicate that the editor can provide a graphical representation.
  • Subroutine PaintValue is responsible for providing the representation. This code clears the sample rectangle and then calls subroutine DrawSamplePen to draw the sample.
The LineStyleListBox control also needs to draw line style samples, so to save code I put the DrawSamplePen subroutine in a separate module. The following code shows how the sample drawing code works:

   ' Return a properly styled pen.
   Public Function StyledPen(ByVal pen_color As Color, _
      ByVal line_style As LineStyles) As Pen
      Dim line_pen As New Pen(pen_color)
      Select Case line_style
         Case LineStyles.Solid
            line_pen.DashStyle = Drawing2D.DashStyle.Solid
         Case LineStyles.Dashed
            line_pen.DashStyle = Drawing2D.DashStyle.Custom
            line_pen.DashPattern = New Single() {5, 5}
         Case LineStyles.Dotted
            line_pen.DashStyle = Drawing2D.DashStyle.Custom
            line_pen.DashPattern = New Single() {1, 3}
      End Select
      Return line_pen
   End Function
   ' Draw a sample line.
   Public Sub DrawSamplePen(ByVal gr As Graphics, _
      ByVal sample_bounds As Rectangle, _
      ByVal line_color As Color, _
      ByVal line_style As LineStyles)
      Dim y As Integer = sample_bounds.Y + _
         sample_bounds.Height \ 2
      Using line_pen As Pen = StyledPen( _
         Color.Black, line_style)
         gr.DrawLine(line_pen, _
            sample_bounds.Left + 1, y, _
            sample_bounds.Right - 1, y)
      End Using
   End Sub
The StyledPen method returns a Pen object with a DashStyle appropriate for a LineStyles value. The DrawSamplePen method calls StyledPen to create an appropriate Pen and then draws a line with it inside the sample bounds.

The final piece to the LineStyle puzzle is the LineStyleListBox control. This control inherits from ListBox and displays LineStyles samples as shown in Figure 3. The code in Listing 3 shows how the LineStyleListBox control works. Note that the control includes the ToolboxItem attribute with the value False, which prevents the control from appearing in the Toolbox window.

The control's constructor takes two parameters: a LineStyles value and an editor service. The code saves a reference to the editor service for later use. It then adds three items to the control's inherited Items collection. It converts the ListStyles value into an integer and sets the SelectedIndex property to select the corresponding ListStyles value. The constructor sets its DrawMode property to OwnerDrawFixed to indicate that the control's code will draw its own fixed-height list items. Finally the code indicates that each item's height will be 16 pixels.

When the user clicks an item in the list box, the LineStyleListBox_Click event handler executes. If the editor service object is not Nothing, the control calls its CloseDropDown method to close the editing dropdown. At that point, the LineStyleEditor's EditValue method's call to the editor service's DropDownControl method returns, and the EditValue method returns the value selected in the control.

The last interesting bit in the LineStyleListBox control is the DrawItem event handler. When the control needs to draw an item, it raises the DrawItem event. The event handler erases the item's background and calls the DrawSamplePen method to draw a sample of the item's LineStyles value.

Points to Remember
As you can see, you can add powerful visualization and editing features to the Properties window but it takes a bit of work. The basic points to take away are these:

  • A type converter class can allow the Properties window to display a customized representation of a property. In this example, the StatesMapConverter class helps the window display (none) if the Map property is Nothing or (map) if it is not. More advanced type converters that inherit from ExpandableObjectConverter let you display an object as a string of comma-separated values and even expand the object into sub-properties.
  • A custom editor class can let the Properties window display a dropdown editor. The LineStyleEditor class described in this article displays a LineStyleListBox control in a dropdown so the user can select a line style graphically. It also draws a sample of the current LineStyle in the Properties window.
The second part of this article explains how to make an editor that displays a custom dialog where users can graphically select states to set the control's Selected property. It also explains how to build smart tags, command verbs, and property pages. Together with the techniques described in this article, these design-time additions provide a powerful toolkit for supporting properties in the controls you build.

Rod Stephens is a consultant and author who has written more than a dozen books and two hundred magazine articles, mostly about Visual Basic. During his career he has worked on an eclectic assortment of applications for repair dispatch, fuel tax tracking, professional football training, wastewater treatment, geographic mapping, and ticket sales. His VB Helper web site receives more than 7 million hits per month and provides three newsletters and thousands of tips, tricks, and examples for Visual Basic programmers.
Email AuthorEmail Author
Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date