Tải bản đầy đủ (.pdf) (32 trang)

Visual Basic 2005 Design and Development - Chapter 11 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (777.1 KB, 32 trang )

Property Support
Visual Studio developers take the Properties window for granted, but it’s actually a pretty remark-
able tool. When you click a control on a form, the window displays the control’s properties and
lets you edit them in ways that are appropriate for the different property data types. It lets you
enter text for properties that are strings or simple values such as numbers, it lets you select files
and colors from dialogs, and it lets you pick choices from a drop-down list for enumerated values.
If a property is a collection of objects, you can click an ellipsis to the right of the property to open a
collection editor where you can add and remove objects, and edit their properties.
When you build a custom control, you get all of this functionality for free. In many cases, this
default behavior is more than enough to let developers use your controls.
In fact, if you are developing controls for in-house use, I would recommend that you try to live
with the capabilities provided by the Properties window for you. Additional features (such as cus-
tom property editors) make developing with controls easier, but they are fairly tricky to imple-
ment. Unless you will be spending a lot of time working with the properties, it may not be worth
the additional effort of writing customized tools.
In contrast, if you are building controls that will be used by developers outside of your project, it
may be worthwhile providing some of these extra capabilities. You may also need to build some
of these tools if your controls’ properties will be directly accessible to end users. For example, the
PropertyGrid control lets users view and modify object properties at run-time. If you use that
control, you may need to streamline your properties so that they are easier for the users to under-
stand and manage.
Unfortunately, controls (and code, in general) often live far longer than was originally intended.
The control that you plan to use only once may be adopted by other projects, and you may end up
supporting it practically forever. To avoid unwelcome work, you should make your controls (and
all of your code) as bulletproof as possible, but you can still avoid adding gratuitous extras. Make
the control’s property procedures validate their data so developers cannot push bad data into the
control. You don’t necessarily need to give every property fancy property editors until you’re cer-
tain there’s a need for them.
16_053416 ch11.qxd 1/2/07 6:33 PM Page 297
This chapter describes some of the ways you can add design-time support for custom controls and their
properties. It shows how to add drop-down and dialog editors to the Properties window, how to add


smart tags to a control, and how to build property sheets.
Some of the attributes, interfaces, and classes used by the examples in this chapter require that you add a
reference to
System.Design and System.Windows.Forms.Design namespaces. Double-click My
Project, select the References tab, and check the namespaces in the list. If one of the namespaces (probably
System.Design) is missing from the list, click the Add button and select the namespace there.
Customizations Overview
This chapter describes several ways that you can enhance Visual Basic’s design time for custom controls. To
demonstrate most of these enhancements, this chapter uses an example control named
ScribbleControl.
This control displays a hand-drawn image. At design time, you can both see and edit the image. At run-
time, the user can view the image. If the control’s
Enabled property is True, the user can also edit the
image at run-time.
Figure 11-1 shows the control at design time displaying an image.
Figure 11-1: The
ScribbleControl displays a hand-drawn image at
design time and run-time.
This control represents its drawing as a series of polylines, where a polyline is a series of connected
points. The control’s
PolyPolyline property contains a reference to a PolyPolyline object. That object
contains an array of
Polyline objects, each of which contains an array of Points. Each of these classes
has support methods that perform such tasks as drawing a
Polyline or PolyPolyline.
298
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 298
Figure 11-1 shows several enhancements to Visual Basic’s design-time support for the ScribbleControl
class. Because the PolyPolyline property is an array of objects, the Properties window would normally

display a collection editor to allow the developer to add, remove, and modify
Polyline objects. That’s
not a very useful way to specify a drawing, however, so the control overrides this behavior.
If you click the ellipsis shown on the right of the
PolyPolyline property in Figure 11-1, Visual Basic
displays the editor dialog shown in Figure 11-2. On this editor, you can click the left mouse button and
drag to draw new polylines. If you click the right mouse button, the editor clears the picture. When you
are finished, you can click OK to save the new
PolyPolyline property, or Cancel to leave the control’s
PolyPolyline property unchanged.
Figure 11-2: You can edit a
ScribbleControl’s
picture at design time.
Notice also that the Properties window displays a tiny image of the drawing in the
PolyPolyline
property’s entry.
The
ScribbleControl provides several other custom properties. The LineColor property determines
the color that the control uses to draw its polylines. In this control, however,
LineColor isn’t just any
old color. Instead, it must be one of red, orange, yellow, green, blue, indigo, violet, black, or white.
You can see in Figure 11-1 that the
LineColor entry in the Properties window displays the name of the
currently selected color. If you select that property, the window displays a drop-down arrow on the right
as it normally would for a color property. If you click this arrow, however, you don’t see a normal color
selection dialog. Instead, you see the color selection drop-down shown in Figure 11-3.
The
LineWidth property determines the thickness of the lines that the control draws. The Properties
window displays
LineWidth by showing a sample of the line and the line’s width in pixels. Like the

LineColor property, LineWidth displays a custom drop-down when you click its drop-down arrow.
Figure 11-4 shows the
LineWidth drop-down.
299
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 299
Figure 11-3: The LineColor property displays
a custom selection drop-down.
Figure 11-4: The
LineWidth property displays
a custom selection drop-down.
Below the properties in the Properties window in Figure 11-1, you can see a link labeled “Draw Spiral.”
This is a command verb supported by the
ScribbleControl class. If you click this link, the control
clears its picture and draws a spiral. More generally, a command verb can take any action that is appropri-
ate to configure or modify the control. While the entries in the Properties window affect only a single
property, these verbs can perform any action on the control.
300
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 300
The ScribbleControl in the form designer is selected in Figure 11-1. If you look closely near the
control’s upper-right corner, you’ll see a small box holding a little rightward-pointing triangle. This is
called a smart tag. If you click a smart tag, a panel of actions pops up that lets you configure the associ-
ated control.
If you click the smart tag for the
ScribbleControl, you’ll see the smart tag panel shown in Figure 11-5.
Controls on this panel let you set the
ScribbleControl’s BackColor, LineColor, and LineWidth
properties.
Figure 11-5: The

ScribbleControl’s smart tag lets you configure the control.
The smart tag’s Draw Star command clears the control’s picture and draws a star. Figure 11-6 shows the
control after clicking this command.
Figure 11-6: The Draw Star command draws a star.
The Draw Spiral command clears the control’s picture and draws the spiral shown in Figure 11-7. The
Draw Spiral command is the same command displayed as a link below the properties in the Properties
window.
The bottom of the smart tag’s panel contains an Information area that displays the number of
Polylines in the current drawing. In Figure 11-5, this area shows that the shark drawing contains
14
Polylines.
301
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 301
Figure 11-7: The Draw Spiral command draws a spiral.
Finally, the
ScribbleControl also provides property pages. These have gone somewhat out of fashion
since Visual Basic .NET was released, but they are still useful for manipulating a control in very complex
ways. Like the smart tag panel, property sheets let you change all of the control’s properties in one place.
They can also provide more complex commands such as the Draw Star and Draw Spiral tools.
While the smart tag pop-up must display all of its tools at once, property sheets can include many pages.
In some very complex controls (such as graphing and charting controls), property sheets may contain
pages to set basic properties (colors, line types, point symbols), chart style (line graph, bar graph, pie
chart), data, axis labels, and so forth.
If you right-click the
ScribbleControl and select Properties, Visual Basic displays the property sheets
shown in Figure 11-8. (Normally, right-clicking and selecting Properties displays the Properties window.
But if the control has property pages, Visual Basic displays those instead.) You can also display the prop-
erty sheets by clicking the property pages button at the top of the Properties window. You can see this
button in Figure 11-4 to the right of the lightning bolt button that displays the control’s

Events.
Figure 11-8: Property sheets let you manipulate a control in complex ways.
The column on the left shows icons and names for the pages contained by the property sheet. The rest
of the dialog contains controls that manipulate the control you right-clicked in the form designer.
Figure 11-8 shows the first property page, which lets you set the control’s basic properties.
302
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 302
If you click the Drawing item in the left-hand column, you’ll see the second property page shown in
Figure 11-9. This page lets you use the mouse to draw a new picture.
Figure 11-9: This property sheet lets you draw pictures with the mouse.
When you make a change to any of the controls on the property pages, the Apply button is enabled. If
you click Apply, the property sheet applies all of the changes to the
ScribbleControl that you origi-
nally right-clicked in the form designer. If you click OK, the dialog applies the changes and closes. If you
click Cancel, the dialog closes and leaves the
ScribbleControl unchanged.
The following sections explain in detail how to implement each of these design-time features. This
control contains a lot of code for performing such chores as adding
Polyline objects to the control’s
PolyPolyline property, adding points to a Polyline, and drawing the control’s image. This code isn’t
central to creating the design-time customizations, so most of this code has been omitted to save space.
Download the example project from
www.vb-helper.com/one_on_one.htm to see the details.
Important Note: When you build the editors and other classes that provide the design-time support,
some of them seem to get cached or linked tightly to Visual Studio. If you make a change to the editor
class and recompile, Visual Studio may continue using the old version.
To make Visual Studio relinquish its hold on the older editor, save your changes, close Visual Studio,
delete the project’s
bin and obj directories, restart Visual Basic, and rebuild the application.

Note also that Visual Studio sometimes gets confused if you load a form containing a control that you
have modified. For example, suppose you save a form containing a
ScribbleControl, close the form,
and then remove the control’s
LineWidth property. When you reopen the form, the form designer tries
to reload the control’s properties from the form’s resources. Unfortunately, those resources include a
LineWidth property, so the form designer will probably become confused. You may need to manually
edit the resource file (which is just XML text) and remove the offending property.
One way to avoid this problem is to not save forms containing the controls you are currently building.
Open the application and then open a test form. Place a control on it and test the design-time features
you are working on. Then close the form without saving changes. Modify the control as needed, and
then, when you reopen the form, you can add a new control.
The chapter finishes by discussing another common scenario: providing design-time support for proper-
ties that are references to objects. It shows how you can allow developers to view and modify the object’s
sub-properties.
303
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 303
Displaying and Editing LineWidth
To display a property in a special format and to allow custom editing in the Properties window, you
need to associate the property with a property editor class. For example, the following code shows how
the
ScribbleControl declares its LineWidth property:
‘ The thicknesses of the lines.
Private m_LineWidth As Integer = 1
<EditorAttribute(GetType(LineWidthEditor), GetType(UITypeEditor))> _
Public Property LineWidth() As Integer
Get
Return m_LineWidth
End Get

Set(ByVal value As Integer)
If value < 1 Then value = 1
If value > 10 Then value = 10
m_LineWidth = value
Me.Invalidate()
End Set
End Property
The EditorAttribute attribute indicates that the PolyPolylineEditor class provides support
for editing the
PolyPolyline property in the Properties window. It provides that support with the
LineWidthEditor class.
The
LineWidthEditor class uses a LineWidthListBox object. The LineWidthEditor and Line
WidthListBox
classes are described in the following sections. (You can download these examples at
www.vb-helper.com/one_on_one.htm.)
LineWidthEditor
The LineWidthEditor class performs two tasks. First, it lets the user edit a LineWidth property by
selecting an item from a drop-down list. Second, it displays a sample line with the right thickness in
the Properties window. Figure 11-4 shows both the editing drop-down and the sample line drawn in the
Properties window.
The following code shows the
LineWidthEditor class perform these tasks:
Imports System.Drawing.Design
Public Class LineWidthEditor
Inherits System.Drawing.Design.UITypeEditor
‘ Indicates that this editor displays a dropdown.
Public Overloads Overrides Function GetEditStyle( _
ByVal context As System.ComponentModel.ITypeDescriptorContext) _
As System.Drawing.Design.UITypeEditorEditStyle

Return UITypeEditorEditStyle.DropDown
End Function
‘ Edit a line width.
Public Overloads Overrides Function EditValue(_
304
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 304
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal provider As System.IServiceProvider, ByVal value As Object) As Object
‘ Get an IWindowsFormsEditorService.
Dim editor_service As IWindowsFormsEditorService = _
CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
IWindowsFormsEditorService)
‘ If we failed to get the editor service, return the value.
If editor_service Is Nothing Then Return value
‘ Convert the generic value into a line width value.
Dim line_width As Integer = DirectCast(value, Integer)
‘ Make the editing control.
Dim editor_control As New LineWidthListBox(line_width, editor_service)
‘ Display the editing control.
editor_service.DropDownControl(editor_control)
‘ Save the new results.
Return editor_control.SelectedIndex + 1
End Function
‘ Indicate that we draw a representation for the Properties window’s value.
Public Overrides Function GetPaintValueSupported(_
ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
Return True
End Function
‘ Draw a representation for the Properties window’s value.

Public Overrides Sub PaintValue(_
ByVal e As System.Drawing.Design.PaintValueEventArgs)
e.Graphics.FillRectangle(Brushes.White, e.Bounds)
Dim line_width As Integer = DirectCast(e.Value, Integer)
Dim y As Integer = e.Bounds.Y + e.Bounds.Height \ 2
Using line_pen As New Pen(Color.Black, line_width)
e.Graphics.DrawLine(line_pen, _
e.Bounds.Left + 1, y, _
e.Bounds.Right - 1, y)
End Using
End Sub
End Class
LineWidthEditor inherits from the UITypeEditor class. That class provides basic support for the
Properties window.
The editor overrides its inherited
GetEditStyle function to return UITypeEditorEditStyle.DropDown,
indicating that the editor provides a drop-down property editor.
The
EditValue function actually does the editing. It casts the value it should edit into an Integer
LineWidth
value. It then makes a LineWidthListBox control (described in the following section) and
uses an
IWindowsFormsEditorService object to display the control. The function finishes by returning
305
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 305
the line width selected by the control. The Properties window sets the LineWidth property of the
ScribbleControl it is editing to this value.
The
LineWidthEditor’s GetPaintValueSupported function returns True to indicate that the class can

draw a special representation of the
LineWidth property. Subroutine PaintValue does the drawing. It
clears a rectangle inside the bounds where it should draw, and then draws a line with the appropriate
thickness.
See Figure 11-4 to see the result. Notice that the Properties window displays a textual representation of
the
LineWidth in addition to the sample line.
LineWidthListBox
To edit a LineWidth value, the LineWidthEditor class displays a LineWidthListBox control. This
control should be a single control that the Properties window can display as a drop-down.
The following code defines the
LineWidthListBox control:
Imports System.ComponentModel
<ToolboxItem(False)> _
Public Class LineWidthListBox
Inherits ListBox
‘ The editor service displaying us.
Private m_EditorService As IWindowsFormsEditorService
‘ A margin around the image in the list box.
Private Const ITEM_MARGIN As Integer = 2
Public Sub New(ByVal line_width As Integer, _
ByVal editor_service As IWindowsFormsEditorService)
MyBase.New()
m_EditorService = editor_service
‘ Give the list some items.
For i As Integer = 0 To 9
Me.Items.Add(i)
Next i
Me.SelectedIndex = line_width - 1
Me.DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed

Me.ItemHeight = 16 + 2 * ITEM_MARGIN
End Sub
‘ Close the dropdown.
Private Sub LineWidthEditorDropdown_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Click
If m_EditorService IsNot Nothing Then m_EditorService.CloseDropDown()
End Sub
‘ Draw a menu item.
Private Sub LineWidthEditorDropdown_DrawItem(ByVal sender As Object, _
ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles Me.DrawItem
306
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 306
‘ Clear the background background.
e.DrawBackground()
‘ Decide where to draw.
Dim x1 As Integer = e.Bounds.X + ITEM_MARGIN
Dim x2 As Integer = e.Bounds.Right - ITEM_MARGIN
Dim y As Integer = e.Bounds.Y + e.Bounds.Height \ 2
‘ Draw the appropriate line.
Using line_pen As New Pen(Color.Black, e.Index + 1)
e.Graphics.DrawLine(line_pen, x1, y, x2, y)
End Using
‘ Draw the focus rectangle if appropriate.
If e.State = DrawItemState.Selected Then e.DrawFocusRectangle()
End Sub
End Class
The control begins with a ToolboxItem attribute with the value False. That prevents the control from
appearing in the form designer’s toolbox. This control is used only by the
ScribbleControl’s design-

time support classes, so application developers don’t need to use it.
The control inherits from the
ListBox, so it’s basically a list control that draws its items.
The control’s constructor adds items to represent line widths from 1 to 10 to its
Items collection and
selects the appropriate item. It sets its
DrawMode property to indicate that the control’s code will draw the
items and that they will all have the same size. It sets
ItemHeight to 16 plus a margin to indicate that
the items will have this height.
When the user selects an item from the control’s list, the
Click event handler calls the editor service’s
CloseDropDown method. This tells the service that the user has finished using the drop-down. At that
point the
LineWidthEditor object’s call to the service’s DropDownControl method returns, so the
LineWidthEditor can save the new LineWidth value.
The
LineWidthListBox control’s DrawItem event handler is invoked when the drop-down must draw
an item. The control simply draws a sample line within the bounds on the Properties window. Then, if
the item it is drawing is currently selected in the list, the control draws a focus rectangle around it.
Displaying and Editing LineColor
The ScribbleControl handles its LineColor property much as it does its LineWidth property. The
following code shows how the
ScribbleControl declares its LineColor property:
‘ The color we use for the lines.
Public Enum LineColors
Red
Orange
Yellow
Green

Blue
307
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 307
Ingido
Violet
Black
White
End Enum
Public Const NumLineColors As Integer = 9
Private m_LineColor As LineColors = LineColors.Black
<EditorAttribute(GetType(ColorIndexEditor), GetType(UITypeEditor))> _
Public Property LineColor() As LineColors
Get
Return m_LineColor
End Get
Set(ByVal value As LineColors)
m_LineColor = value
Me.Invalidate()
End Set
End Property
The LineColors enumerated type lists the values that are allowed for the property and the property has
type
LineColor.
The property’s
EditorAttribute attribute indicates that the ColorIndexEditor class provides editing
features for the property. This class is similar to the
LineWidthEditor class, except that it displays a
ColorIndexListBox control instead of a LineWidthListBox control.
Like

LineWidthListBox, the ColorIndexListBox control inherits from the ListBox control and draws
its own items. The only important difference is that
ColorIndexListBox draws samples of colors rather
than sample lines.
Unlike the
LineWidthEditor, the ColorIndexEditor control does not provide an implementation of
its inherited
GetPaintValueSupported and PaintValue routines. That means the class does not dis-
play a sample of the color in the Properties window. It displays a drop-down, just as the control does for
editing
LineWidth, but it doesn’t show a sample of the color in the Properties window.
Instead the Properties window automatically generates a textual representation from the enumerated
type. Figure 11-3 shows the color drop-down that lets you pick a color value. The Properties window
shows the textual equivalent Yellow above it.
Displaying and Editing PolyPolyline
The ScribbleControl’s PolyPolyline property also provides support for special display and editing.
This property is different from
LineWidth and LineColor in a couple of respects. First, it wouldn’t
make much sense to display a textual representation of the property in the Properties window (what
string would represent the fish drawing?). To prevent confusion, the code overrides this behavior and
displays a blank string in the Properties window.
Second, this property provides the modal dialog editor shown in Figure 11-2 instead of a drop-down
editor.
308
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 308
The following code shows how the ScribbleControl defines its PolyPolyline property:
‘ The points we draw.
Private m_PolyPolyline As PolyPolyline = New PolyPolyline
<TypeConverter(GetType(PolyPolylineConverter))> _

<EditorAttribute(GetType(PolyPolylineEditor), GetType(UITypeEditor))> _
Public Property PolyPolyline() As PolyPolyline
Get
Return m_PolyPolyline
End Get
Set(ByVal value As PolyPolyline)
m_PolyPolyline = value
Me.Invalidate()
End Set
End Property
The TypeConverter attribute tells Visual Studio that the PolyPolylineConverter class can convert
a
PolyPolyline object into other data types. The Properties window uses this converter to translate the
PolyPolyline property to and from strings.
The
EditorAttribute attribute indicates that the PolyPolylineEditor class provides support for
editing the
PolyPolyline property in the Properties window.
PolyPolylineConverter
By default, the Properties window would display this property as a collection of Polyline objects and
would let the user edit the value by using a collection editor. Each of the
Polyline objects would have
a
Points property that was a collection of Point objects. Viewing and editing the PolyPolyline by
using these collection editors would be ridiculously difficult.
It also wouldn’t be very meaningful to display a
PolyPolyline object in a string representation. You
could display a list of X and Y coordinates separated by commas, parentheses, and brackets. This might
be useful for code loading and saving data, but it would be no more meaningful to the developer than a
series of collection editors.

To avoid displaying these useless representations, the
PolyPolylineConverter class converts a
PolyPolyline object into a blank string. The Properties window then displays the blank string instead
of a collection of
Polylines or a sequence of coordinates.
The following code shows how the
PolyPolylineConverter class works:
‘ Displays a blank string for a PolyPolyline property.
‘ Cannot convert from anything.
Imports System.ComponentModel
Public Class PolyPolylineConverter
Inherits TypeConverter
‘ We can convert into String.
Public Overrides Function CanConvertTo( _
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal destinationType As System.Type) As Boolean
309
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 309
If destinationType Is GetType(String) Then Return True
Return MyBase.CanConvertTo(context, destinationType)
End Function
‘ Convert into a blank String.
Public Overrides Function ConvertTo(_
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal culture As System.Globalization.CultureInfo, _
ByVal value As Object, ByVal destinationType As System.Type) As Object
If destinationType Is GetType(String) Then
Return “”
Else

Return MyBase.ConvertTo(context, culture, value, destinationType)
End If
End Function
End Class
The class inherits from TypeConverter. It overrides the inherited CanConvertTo function to return True
if the destination data type is String. This tells Visual Studio that the class can convert to a String.
The
ConvertTo function returns a blank string if the destination type is String. This is what the
Properties window displays.
The class does not override its inherited
CanConvertFrom and ConvertFrom methods, so the control
cannot convert other types such as
String into a PolyPolyline. If you wanted to allow the user to
enter new textual values for the property in the Properties window, you would override these routines
to translate
Strings into PolyPolylines.
PolyPolylineEditor
The PolyPolylineEditor class provides editing and display capabilities for the PolyPolyline prop-
erty in the Properties window. When you click the ellipsis to the right of the property in the Properties
window, Visual Studio displays the dialog shown in Figure 11-2. It also draws the tiny image of the
drawing shown in Figure 11-1.
The following code shows the first half of the
PolyPolylineEditor class. This is the code that provides
the editing dialog.
‘ This is an editor for a PolyPolyline property.
‘ It displays a ScribbleControl in a modal dialog.
Imports System.Drawing.Design
Public Class PolyPolylineEditor
Inherits System.Drawing.Design.UITypeEditor
#Region “Value Editing Code”

‘ Indicates that this editor displays a modal dialog.
Public Overloads Overrides Function GetEditStyle( _
ByVal context As System.ComponentModel.ITypeDescriptorContext) _
As System.Drawing.Design.UITypeEditorEditStyle
Return UITypeEditorEditStyle.Modal
310
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 310
End Function
‘ Edit a PolyPolyline.
Public Overloads Overrides Function EditValue(_
ByVal context As System.ComponentModel.ITypeDescriptorContext, _
ByVal provider As System.IServiceProvider, ByVal value As Object) As Object
‘ Get an IWindowsFormsEditorService.
Dim editor_service As IWindowsFormsEditorService = _
CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
IWindowsFormsEditorService)
‘ If we failed to get the editor service, return the value unchanged.
If editor_service Is Nothing Then Return value
‘ Convert the generic value into a PolyPolyline.
Dim ppline As PolyPolyline = DirectCast(value, PolyPolyline)
‘ Make the editing dialog.
Dim editor_dialog As New PolyPolylineEditorDialog()
editor_dialog.PolyPolyline = ppline
editor_dialog.EditorService = editor_service
‘ Display the editing dialog.
editor_service.ShowDialog(editor_dialog)
‘ Save the new results.
If editor_dialog.DialogResult = DialogResult.OK Then
‘ Return the new PolyPolyline.

Return editor_dialog.PolyPolyline
Else
‘ Return the old PolyPolyline.
Return ppline
End If
End Function
#End Region ‘ Value Editing Code
Like the LineWidthEditor and LineColorEditor classes, this class inherits from UITypeEditor.
Whereas the other classes override their
GetEditStyle functions to return UITypeEditorEditStyle
.DropDown
, this class returns UITypeEditorEditStyle.Modal to indicate that it displays a modal
editor dialog.
In the previous classes, the
EditValue function displays a drop-down. In this class, EditValue displays
the modal editor. First, it casts the value that it is editing into a
PolyPolyline object. It then makes a
new
PolyPolylineEditorDialog. This is a form defined in the ScribbleControl project, and is the
form shown in Figure 11-2. You can download the example program to see all of the dialog’s code.
The
PolyPolylineEditorDialog contains a ScribbleControl with the Enabled property set to True
so you can click the control’s surface to draw a new PolyPolyline.
The editor form has a public
PolyPolyline property. When the PolyPolylineEditor object sets the
form’s
PolyPolyline property, the form copies the PolyPolyline into its ScribbleControl.
311
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 311

After setting the dialog’s PolyPolyline property, the PolyPolylineEditor displays the dialog
modally. If the user clicks the OK button, the
EditValue method returns the dialog’s new PolyPolyline
property. If the user clicks Cancel, the function returns the original PolyPolyline value so the control’s
property is unchanged.
The following code shows the second half of the
PolyPolylineEditor class. This is the code that
lets the Properties window display the miniature image of the
ScribbleControl’s drawing shown in
Figure 11-1.
#Region “Property Display Code”
‘ Indicate that we draw a representation for the Properties window’s value.
Public Overrides Function GetPaintValueSupported(_
ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
Return True
End Function
‘ Draw a representation for the Properties window’s value
‘ by converting the scribble into a tiny image.
Public Overrides Sub PaintValue(_
ByVal e As System.Drawing.Design.PaintValueEventArgs)
‘ Get the points.
Dim ppline As PolyPolyline = DirectCast(e.Value, PolyPolyline)
If (ppline IsNot Nothing) Then
‘ Find the points’ bounds.
Dim ppline_rect As Rectangle = ppline.Bounds()
ppline_rect.Inflate(CInt(ppline_rect.Width * 0.1), _
CInt(ppline_rect.Height * 0.1))
‘ Make a bitmap to hold a small image of the scribble.
Dim pts_wid As Integer = ppline_rect.Width
Dim pts_hgt As Integer = ppline_rect.Height

Dim bm_wid As Integer = e.Bounds.Width - 2
Dim bm_hgt As Integer = e.Bounds.Height - 2
Dim x_scale As Double = bm_wid / pts_wid
Dim y_scale As Double = bm_hgt / pts_hgt
Dim scale As Single = CSng(Math.Min(x_scale, y_scale))
Dim bm As New Bitmap(bm_wid, bm_hgt)
Using gr As Graphics = Graphics.FromImage(bm)
Dim x0 As Integer = CInt(ppline_rect.X + _
(ppline_rect.Width - bm_wid / scale) / 2)
Dim y0 As Integer = CInt(ppline_rect.Y + _
(ppline_rect.Height - bm_hgt / scale) / 2)
gr.TranslateTransform(-x0, -y0)
gr.ScaleTransform(scale, scale, Drawing2D.MatrixOrder.Append)
gr.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
ppline.Draw(gr, Pens.Black)
End Using
‘ Copy the little bitmap onto the drawing surface.
e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.High
e.Graphics.DrawImage(bm, e.Bounds.X + 1, e.Bounds.Y + 1)
End If
312
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 312
End Sub
#End Region ‘ Property Display Code
End Class
The class overrides its inherited GetPaintValueSupported function, so it returns True to indicate that
this class can draw a
PolyPolyline’s value in the Properties window.
The

PaintValue subroutine draws the value. It converts its e.Value parameter into the PolyPolyline
value that it must draw. It uses the PolyPolyline object’s Bounds property to get a bounding box for
the drawing, and inflates the bounds to include a small margin around the drawing.
Next,
PaintValue make a Bitmap that fits the bounds where the Properties window needs the image
drawn. It builds a graphical transformation that translates and scales the image so that it fits on the
Bitmap and draws the image. Finally PaintValue draws the Bitmap onto the Properties window.
Displaying Smart Tags
To display a smart tag, you use the Designer attribute to associate the control with a designer. The
following code shows the
Designer attribute that associates the ScribbleControl class with the
ScribbleControlDesigner class. You’ll see this Class statement with a bit more context later in
this chapter.

<Designer(GetType(ScribbleControlDesigner))> _

Public Class ScribbleControl

End Class
The ScribbleControlDesigner class is relatively simple. The following shows the class’s code:
‘ Designer for the ScribbleControl.
‘ This lets the control display a smart tag.
Public Class ScribbleControlDesigner
Inherits ControlDesigner
Private lists As DesignerActionListCollection
Public Overrides ReadOnly Property ActionLists() _
As DesignerActionListCollection
Get
If lists Is Nothing Then
lists = New DesignerActionListCollection()

lists.Add(New ScribbleActionList(Me.Component))
End If
Return lists
End Get
End Property
End Class
313
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 313
ScribbleControlDesigner inherits from the ControlDesigner designer class and overrides its
inherited
ActionLists function. This function returns a DesignerActionListCollection that con-
tains a list of classes that represent the lists of actions that the smart tag should contain. This example
uses the single action list class named
ScribbleActionList.
The
ScribbleActionList class is longer, but it’s not too complicated. The following code shows how the
class works:
‘ This is the action list displayed by the ScribbleControl’s smart tag.
Imports System.ComponentModel.Design
Imports System.Drawing.Design
Imports System.Math
Public Class ScribbleActionList
Inherits DesignerActionList
‘ The control we are manipulating.
Private m_Scribble As ScribbleControl
Private m_DesignerService As DesignerActionUIService = Nothing
‘ Save a reference to our control.
Public Sub New(ByVal component As IComponent)
MyBase.New(component)

m_Scribble = DirectCast(component, ScribbleControl)
‘ Save a reference to DesignerActionUIService we can refresh it.
m_DesignerService = _
CType(GetService(GetType(DesignerActionUIService)), _
DesignerActionUIService)
End Sub
#Region “Smart Tag Items”
‘ LineColor.
<EditorAttribute(GetType(ColorIndexEditor), GetType(UITypeEditor))> _
Public Property LineColor() As ScribbleControl.LineColors
Get
Return m_Scribble.LineColor
End Get
Set(ByVal value As ScribbleControl.LineColors)
SetControlProperty(“LineColor”, value)
End Set
End Property
‘ BackColor.
Public Property BackColor() As Color
Get
Return m_Scribble.BackColor
End Get
Set(ByVal value As Color)
SetControlProperty(“BackColor”, value)
End Set
314
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 314
End Property
‘ LineWidth.

<EditorAttribute(GetType(LineWidthEditor), GetType(UITypeEditor))> _
Public Property LineWidth() As Integer
Get
Return m_Scribble.LineWidth
End Get
Set(ByVal value As Integer)
SetControlProperty(“LineWidth”, value)
End Set
End Property
‘ Make a star Polyline.
Public Sub DrawStar()
‘ Make a new PolyPolyline containing a star.
Dim ppline As New PolyPolyline
ppline.Clear()
ppline.AddPolyline()
Dim cx As Integer = m_Scribble.ClientSize.Width \ 2
Dim cy As Integer = m_Scribble.ClientSize.Height \ 2
Dim dtheta As Double = 2 * PI * 2 / 5
For i As Integer = 0 To 6
Dim x As Integer = CInt(cx + Cos(i * dtheta - PI / 2) * cx)
Dim y As Integer = CInt(cy + Sin(i * dtheta - PI / 2) * cy)
ppline.AddPoint(x, y)
Next i
‘ Save the new property.
SetControlProperty(“PolyPolyline”, ppline)
‘ Refresh the smart tag list to show the new Polyline count.
Me.m_DesignerService.Refresh(Me.Component)
End Sub
‘ Make a spiral Polyline.
Public Sub DrawSpiral()

‘ Make a new PolyPolyline containing a star.
Dim ppline As New PolyPolyline
ppline.Clear()
ppline.AddPolyline()
Dim cx As Integer = m_Scribble.ClientSize.Width \ 2
Dim cy As Integer = m_Scribble.ClientSize.Height \ 2
Dim theta As Double = 0
Const NUM_POINTS As Integer = 100
Dim dtheta As Double = 4 * PI / NUM_POINTS
Dim rx As Double = 0
Dim drx As Double = cx / NUM_POINTS
Dim ry As Double = 0
Dim dry As Double = cy / NUM_POINTS
For i As Integer = 0 To NUM_POINTS - 1
Dim x As Integer = CInt(cx + Cos(theta) * rx)
Dim y As Integer = CInt(cy + Sin(theta) * ry)
315
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 315
ppline.AddPoint(x, y)
theta += dtheta
rx += drx
ry += dry
Next i
‘ Save the new property.
SetControlProperty(“PolyPolyline”, ppline)
‘ Refresh the smart tag list to show the new Polyline count.
Me.m_DesignerService.Refresh(Me.Component)
End Sub
‘ Set a property value for the control.

‘ You could just set the property directly
‘ as in m_ScribbleControl.LineWidth = 1 but then
‘ the IDE’s undo/redo feature doesn’t work properly and
‘ the form is not marked as modified in the IDE.
Private Sub SetControlProperty(ByVal property_name As String, _
ByVal value As Object)
TypeDescriptor.GetProperties(m_Scribble)(property_name). _
SetValue(m_Scribble, value)
End Sub
#End Region ‘ Smart Tag Items
‘ Return a collection representing the smart tag items.
Public Overrides Function GetSortedActionItems()_
As DesignerActionItemCollection
Dim items As New DesignerActionItemCollection()
‘ Define section headers.
items.Add(New DesignerActionHeaderItem(“Appearance”))
items.Add(New DesignerActionHeaderItem(“Information”))
‘ Appearance entries.
items.Add( _
New DesignerActionPropertyItem( _
“BackColor”, _
“Back Color”, _
“Appearance”, _
“Sets the background color.”))
items.Add( _
New DesignerActionPropertyItem( _
“LineColor”, _
“Line Color”, _
“Appearance”, _
“Sets the line drawing color.”))

items.Add( _
New DesignerActionPropertyItem( _
“LineWidth”, _
“Line Width”, _
“Appearance”, _
“Sets the line thickness.”))
316
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 316
‘ The DrawStar subroutine.
items.Add( _
New DesignerActionMethodItem( _
Me, _
“DrawStar”, _
“Draw Star”, _
“Appearance”, _
“Draws a star.”, _
False))
‘ The DrawSpiral subroutine.
‘ Also appears in the context menu.
items.Add( _
New DesignerActionMethodItem( _
Me, _
“DrawSpiral”, _
“Draw Spiral”, _
“Appearance”, _
“Draws a spiral.”, _
True))
‘ Information entries.
Dim txt As String = “# Polylines: “ & _

m_Scribble.PolyPolyline.Polylines.Length
items.Add( _
New DesignerActionTextItem( _
txt, _
“Information”))
Return items
End Function
End Class
The ScribbleActionList class inherits from DesignerActionList. It has private variables to refer
to the
ScribbleControl that the smart tag is editing, and to the designer service it is using. The class’s
constructor saves references to those objects.
The class then contains a series of properties to represent values that it can manipulate. It delegates the
properties to the
ScribbleControl it is editing.
One oddity here is the way the property set routines work. These routines could just set the control’s
property directly. For example, the
LineColor property set routine could use the following statement:
m_ScribbleControl.LineColor = value
Unfortunately, this method doesn’t work well for three reasons. First, it doesn’t make the smart tag
update the control, so the control doesn’t show any changes. In this case, that means it wouldn’t redraw
to use the new
LineColor. (You could fix this by invalidating the control, but there are other problems.)
Second, this doesn’t integrate with the IDE’s undo and redo features, so undo and redo won’t work
properly.
317
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 317
Finally, this method doesn’t flag the form as modified. If you open a form in the form designer, use the
smart tag to change a property, and then close the form, the IDE doesn’t realize that you made a change,

so it doesn’t save the new property value.
To work around all of these problems, the property set procedures call helper subroutine
SetControl
Property
. That routine uses a TypeDescriptor to get the control’s property information, and then
calls the
SetValue method to set the property’s new value. This makes the IDE properly aware of the
change.
Notice that the
LineColor and LineWidth properties use EditorAttribute attributes to associate the
properties with the same
ColorIndexEditor and LineWidthEditor classes used by the Scribble
Control
itself. This allows the smart tag panel to use the associated editor controls to display and edit
the properties just as the Properties window does. Figure 11-10 shows the smart tag panel displaying its
LineWidth editor.
Figure 11-10: The smart tag panel can use
UITypeEditors, too.
The
LineColor, BackColor, and LineWidth properties are relatively straightforward. The DrawStar
and DrawSpiral subroutines are almost as simple. They each create a new PolyPolyline object and
add points to it to make a star or spiral. They then use the
SetControlProperty subroutine to set the
ScribbleControl’s PolyPolyline property to the new object.
The routines finish by calling the designer service’s
Refresh method to make the smart tag panel refresh
itself. In this example, that updates the
Polyline count displayed on the panel.
After the properties and action subroutines, the
ScribbleActionList class overrides its inherited

GetSortedActionItems function. This function provides instructions for laying out the smart tag
panel. If you omit this function, the panel includes the properties and action subroutines in alphabetical
order. Usually, you will want to override
GetSortedActionItems to group related items in sections,
and to give them a more meaningful order.
In this example, the function creates a new
DesignerActionItemCollection. It then adds two section
headers that display the text
Appearance and Information.
Next, the function adds objects to the
DesignerActionItemCollection to determine how the items are
arranged. For a property, the code adds a
DesignerActionPropertyItem object. The object’s construc-
tor specifies the property’s name, the text that should be displayed on the smart tag panel, the section that
should include it (
Appearance or Information in this example), and a tooltip.
318
Part II: Meta-Development
16_053416 ch11.qxd 1/2/07 6:33 PM Page 318
For the DrawStar and DrawSpiral subroutines, the code adds a DesignerActionMethodItem object.
The constructor includes the object providing the method, the method’s name, the text that should be
displayed on the smart tag panel, the section that should include it, a tooltip, and a Boolean indicating
whether the item should also be displayed as an action verb below the Properties window. This example
sets this Boolean to
False for the DrawStar method and True for the DrawSpiral method, so only
DrawSpiral appears below the Properties window in Figure 11-1.
Finally, the
GetSortedActionItems function adds a DesignerActionTextItem to the Designer
ActionItemCollection
. This item displays some static text; in this case, showing the number of

Polylines drawn by the ScribbleControl. When the DrawStar or DrawSpiral subroutine calls the
designer service’s
Refresh method, the GetSortedActionItems function is called again so this value
is updated. (You can download this example at
www.vb-helper.com/one_on_one.htm.)
Displaying Property Sheets
The final design-time enhancement provided by the ScribbleControl is support for property sheets.
To display property sheets, you use an
EditorAttribute statement to associate the control class with
an editor class. The following code shows how the
ScribbleControl class is associated with the
ScribbleControlEditor class:
<ToolboxBitmap(GetType(ScribbleControl), “tbxScribble”)> _
<Designer(GetType(ScribbleControlDesigner))> _
<EditorAttribute(GetType(ScribbleControlEditor), GetType(ComponentEditor))> _
Public Class ScribbleControl
Inherits Control

End Class
The following code shows how the ScribbleControlEditor class works:
‘ The editor that displays the component editor pages.
‘ when you right-click the control at design time
‘ and select Properties.
Public Class ScribbleControlEditor
Inherits System.Windows.Forms.Design.WindowsFormsComponentEditor
‘ Return an array containing the types of the component editor pages
‘ to display.
Protected Overrides Function GetComponentEditorPages() As Type()
Return New Type() { _
GetType(ScribbleControlEditorBasicsPage), _

GetType(ScribbleControlEditorPolyPolylinePage) _
}
End Function
‘ Return the index of the first page to display.
Protected Overrides Function GetInitialComponentEditorPageIndex() As Integer
Return 0
End Function
End Class
319
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 319
The ScribbleControlEditor class inherits from WindowsFormsComponentEditor. It overrides its
inherited
GetComponentEditorPages function to return an array containing type information describing
the classes that implement the editor’s property pages. This example uses two property page classes
named
ScribbleControlEditorBasicsPage and ScribbleControlEditorPolyPolylinePage.
The
ScribbleControlEditor class also overrides its GetInitialComponentEditorPageIndex func-
tion to return the index of the property page that should initially be displayed. This example simply
returns
0 to display the first page.
The following code shows the
ScribbleControlEditorBasicsPage class:
‘ The property editor’s basic property page.
<ToolboxItem(False)> _
Public Class ScribbleControlEditorBasicsPage
Inherits System.Windows.Forms.Design.ComponentEditorPage
Private m_ScribbleControl As ScribbleControl
Friend WithEvents lblLineWidth As System.Windows.Forms.Label

Friend WithEvents lwlLineWidthEditorListBox As LineWidthListBox
Friend WithEvents lblBackColor As System.Windows.Forms.Label
Friend WithEvents picBackColor As System.Windows.Forms.PictureBox
Friend WithEvents dlgBackColor As System.Windows.Forms.ColorDialog
Friend WithEvents lblLineColor As System.Windows.Forms.Label
Friend WithEvents cilColorIndexListBox As ColorIndexListBox
‘ Initialize the page.
Public Sub New()
Me.Size = New Size(400, 200)
Me.Text = “Basics”
Me.Icon = My.Resources.icoScribbleBasics
Me.lblLineWidth = New System.Windows.Forms.Label
Me.lblLineWidth.AutoSize = True
Me.lblLineWidth.Location = New System.Drawing.Point(8, 8)
Me.lblLineWidth.Name = “lblLineWidth”
Me.lblLineWidth.Size = New System.Drawing.Size(61, 13)
Me.lblLineWidth.TabIndex = 0
Me.lblLineWidth.Text = “Line Width:”
Me.lwlLineWidthEditorListBox = New LineWidthListBox(1, Nothing)
Me.lwlLineWidthEditorListBox.Location = New System.Drawing.Point(112, 8)
Me.lwlLineWidthEditorListBox.Name = “lwdLineWidthEditorDropdown”
Me.lwlLineWidthEditorListBox.Size = New System.Drawing.Size(72, 134)
Me.lwlLineWidthEditorListBox.TabIndex = 1
‘ Other control creation code omitted
Me.dlgBackColor = New System.Windows.Forms.ColorDialog
Me.Controls.AddRange(New Control() { _
lblLineWidth, lwlLineWidthEditorListBox, _
lblBackColor, picBackColor, _
320
Part II: Meta-Development

16_053416 ch11.qxd 1/2/07 6:33 PM Page 320
lblLineColor, cilColorIndexListBox _
})
End Sub
‘ The LoadComponent method is raised when the ComponentEditorPage is displayed.
Protected Overrides Sub LoadComponent()
m_ScribbleControl = DirectCast(Me.Component, ScribbleControl)
picBackColor.BackColor = m_ScribbleControl.BackColor
lwlLineWidthEditorListBox.SelectedIndex = m_ScribbleControl.LineWidth - 1
cilColorIndexListBox.SelectedIndex = _
CType(m_ScribbleControl.LineColor, Integer)
End Sub
‘ Save the selected LineWidth and BackColor.
Protected Overrides Sub SaveComponent()
m_ScribbleControl.LineWidth = lwlLineWidthEditorListBox.SelectedIndex + 1
m_ScribbleControl.BackColor = picBackColor.BackColor
m_ScribbleControl.LineColor = _
CType(cilColorIndexListBox.SelectedIndex, ScribbleControl.LineColors)
End Sub
‘ Let the user select a color from a ColorDialog.
Private Sub picBackColor_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles picBackColor.Click
If dlgBackColor.ShowDialog() = Windows.Forms.DialogResult.OK Then
picBackColor.BackColor = dlgBackColor.Color
‘ Let the editor know that the user changed a property value.
Me.SetDirty()
End If
End Sub
‘ Let the editor know that the user changed a property value.
Private Sub lwlLineWidthEditorListBox_SelectedIndexChanged(_

ByVal sender As Object, ByVal e As System.EventArgs) _
Handles lwlLineWidthEditorListBox.SelectedIndexChanged
Me.SetDirty()
End Sub
‘ Let the editor know that the user changed a property value.
Private Sub cilColorIndexListBox_SelectedIndexChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cilColorIndexListBox.SelectedIndexChanged
Me.SetDirty()
End Sub
End Class
This class includes a ToolboxItem attribute with value set to False so the page does not appear in the
form designer’s toolbox. It makes little sense to allow developers to place the property page on a form.
The
ScribbleControlEditorBasicsPage class inherits from ComponentEditorPage. It declares a
reference to the control it is editing. It then declares
WithEvents variables to represent the controls it
will display.
The class’s constructor creates those controls and sets their properties such as name, position, size, and text.
321
Chapter 11: Property Support
16_053416 ch11.qxd 1/2/07 6:33 PM Page 321

×