Z Code
Put Custom Property Editors on the Map
Use PropertyGrid to edit complex data structures by adding custom user interface editors.
Technology Toolbox: VB.NET
Visual Studio's PropertyGrid control provides a great way to get complex data from users. You probably use it daily; it's the area in Visual Studio where you enter the properties of your Windows Forms and Web Forms controls. But it's not just for defining controls anymore. You can add the PropertyGrid control to a form and let users edit class-based data. Better yet, you can enhance it with your own custom user-interface components, adding slick data-entry features that improve the user experience greatly.
I'll show you how to extend the PropertyGrid control to include a custom drop-down editor. My sample code implements a custom latitude/longitude editor that lets users indicate these property values by clicking on a map of the world.
The PropertyGrid control ships with Visual Studio .NET, but isn't included in the IDE toolbox by default. Add it by right-clicking on the toolbox, selecting Add/Remove Items, then selecting PropertyGrid from the list in the Customize Toolbox window. It's pretty easy to use once you install it. Simply drop the control on your form, create an instance of any class with public properties, call it, say, "myObject," and add one line of code:
PropertyGrid1.SelectedObject = myObject
Your properties will suddenly appear in the control. That's fast, but it's also basic. Professional software needs more, and PropertyGrid can help you deliver the goods. It works by examining your class for things such as the name and data type of each property. It can also examine what description to show when a particular property is active, whether a property should even appear, and more. It does this by looking at "attributes" that are added to each property.
Attributes are custom features you can attach to a property, class, event, method, and more. Other related language features include converters and descriptors. They are all part of the System.ComponentModel namespace. I'll refer to them all as attributes for the sake of simplicity.
Visual Studio .NET includes several predefined attributes, and you can even design your own. Attributes are added just before the definition of the property. For instance, use this syntax for an attribute used to identify the "color" of each property:
<ColorAttribute("red")> _
Public Property MyProperty() _
As String...
You can include multiple comma-separated attributes within the angle brackets. Also, you can omit "Attribute" for attribute names that end in "Attribute." For example, in the previous example, you can use Color() instead of ColorAttribute() (see Table 1 for a list of PropertyGrid-related property and class attributes).
The sample program supplied with this article uses the PropertyGrid control to edit a list of world cities. The primary City class includes properties for the city name, country name, continent name, and a flag that indicates whether the city is a capital. It also includes a latitude-longitude field; you'll attach your custom editor to this field.
Customize With Attributes
The public property Location is an instance of LatLong, a separate class made up of distinct latitude and longitude members. Using attributes and other PropertyGrid enhancements enables you to edit a location as a single comma-delimited value pair, edit its latitude and longitude values independently, or use the mouse to indicate the location on a world map using a drop-down editor (see Figure 1).
If you ignore all the attribute decorations in the City class, the Location property definition is pretty basic with only the simplest of Get and Set routines (see Listing 1). The fun really comes with the attributes.
The attributes attached to the City class and its Location property have considerable impact on City objects displayed in the PropertyGrid. The Name property is active by default when you first select a City object in the PropertyGrid. The browsable Location property appears in the Settings category when categories are displayed. The Location property isn't generally tied to a data source, which means it's not bindable. The control displays "The latitude ..." when Location is selected. Location doesn't appear (isn't mergeable) when displaying the properties for multiple objects at once.
The code imports the System.ComponentModel namespace. This is essential, because the attributes come from that namespace. System.Drawing.Design, System.Windows.Forms.Design, and System.Globalization are also used throughout the code.
The LatLong class is as simple as the City class. It's a basic class with two public String propertiesone for latitude and one for longitude. The attributes associated with the class itself are interesting:
<TypeConverter(GetType( _
LatLongConverter)), _
Editor(GetType(LatLongEditor), _
GetType(UITypeEditor))> _
Public Class LatLong...
These attributes define the type converter and custom editor used with the class. The PropertyGrid control displays every property as a string. However, many properties, such as the City.Location property, aren't strings. PropertyGrid morphs a string such as "47.62N, 122.35W" into a LatLong object (and vice versa) by using the type converter associated with the LatLong class. The sample code includes the LatLongConverter class that does the heavy lifting (see Listing 2).
LatLongConverter inherits from the ExpandableObjectConverter class, which implements the basic converter technology. Override the CanConvertFrom and CanConvertTo functions to indicate that the class can convert to and from Strings. Two more overrides complete the class. The ConvertFrom routine takes your comma-delimited location and returns a new LatLong instance from pieces of the string. ConvertTo reverses the procedure, generating a comma-delimited string from a LatLong object.
The definition of the LatLong class also includes an Editor attribute, which defines the user interface component for the drop-down world-map editor. You need two distinct classes to implement drop-down editors. LatLongEditorUI is the actual display portion, and is a basic borderless Windows Form instance. The second class, LatLongEditor, acts as a go-between for the LatLong and LatLongEditorUI classes. It's responsible primarily for creating an instance of the LatLongEditorUI form and displaying it when needed (see Listing 3).
The overridden GetEditStyle routine tells the caller that the class implements a drop-down editor. The display of the form is done in the overridden EditValue routine, which also retrieves the latitude and longitude values using the LatLongEditorUI class's GeogLocation member. That form's code simply monitors MouseMove and Click events on a PictureBox control (the world map), using simple formulas to convert pixels to latitude and longitude (see Listing 4). The CloseDropDown method call in the PictureBox's Click event tells PropertyGrid, "I'm done; retrieve those values." The control then returns to the LatLongEditor.EditValue routine for the actual retrieval.
|
A lot of code is involved in what seems to be a simple PropertyGrid feature. However, it's really not that much when you consider that PropertyGrid's flexibility lets you fully control the user experience. Microsoft could have imposed a few limited editors and string display formats on the PropertyGrid. Instead, it opted to permit enhancements to the control through custom attributes and your own handcrafted extensions. This gives you both the power and the responsibility to communicate effectively to users programmatically.
About the Author
Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.