Introduction
There is a range of controls available as standard for Windows Forms in VisualStudio .NET 2003. These include TextBox to edit strings, CheckBox to edit booleans, and even a DateTimePicker. However, there is a vast range of types available in .NET. Furthermore, new types are added all the time both by Microsoft and the development community. Only a fraction of these types will contain data that we would expect to access via a user interface. This leaves us with the significant problem of ensuring our controls are capable of handling such types.
Background
This is a screen grab of the object editor from AgileStudio, which is capable of editing a large range of data types in DataSets that may include a complex set of related tables. As a service to the community, Sekos is freely making available the source code for various controls used to accomplish this, all of which work with data binding. These include:
- DataToolBar and DataButton, which support complex binding (to a table or list) for navigation purposes.
- PropertyEditor for editing a large range of discrete types.
- DataPicture to allow the editing of images.
- A range of additional columns for use with DataGrid.
- A GenericEditor tjat can automatically generate a UI appropriate to the tables in a DataSet.
We’ll kick off the process in this article by examining the PropertyEditor and DataPicture controls used both standalone and within columns in a DataGrid.
PropertyEditor
How do we allow for the editing of a large range of discrete types, some of which may not even have been written yet? This seems like a tall order, but Microsoft has already put all the infrastructure in place for us with System.ComponentModel.TypeConverter and System.Drawing.Design.UITypeEditor. It’s well documented how to go about creating a custom TypeConverter and/or UITypeEditor specific to a new data type using the TypeConverter and Editor attributes.
Among other things, this infrastructure is used by the PropertyGrid control to edit all the properties of a control at design time within Visual Studio. At runtime, it is unusual to want access to all the properties of a control. However, sometimes it might be desirable to allow the user to adjust individual properties; for example, to control the orientation of a 3D graph. (One way to handle this would be to have some kind of proxy object supporting ICustomTypeDescriptor to cut down the properties available to a PropertyGrid.) However, for maximum UI control, the best solution is to code a new control that implements IWindowsFormsEditorService. There are many aspects to supporting this service, including:
- The painting of value glyphs; for example, a colour box sample for a Color editor
- Providing a DropDown to pick from a set of Standard Values for enumerated types
- Allowing custom dropDown controls or popup dialogs as appropriate
For the PropertyEditor to have the appropriate behaviour when editing a specific Value, it has a PropertyType property that specifies the Type of the Value. When configuring a PropertyEditor at design time, the user enters the name of a type that is converted to the actual type by using TypeTypeConverter.ConvertFrom():
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { if (value is string) { System.ComponentModel.Design.ITypeResolutionService typeResolver= (System.ComponentModel.Design. ITypeResolutionService)context. GetService(typeof(System.ComponentModel. Design.ITypeResolutionService)); Type t= null; string typeName= (string)value; if (typeResolver!=null) t= typeResolver.GetType(typeName); else { // In the unlikely case that this property is bound to // another PropertyEditor so it can be changed at runtime, // ITypeResolutionService won't be available so we resort // to GetType instead. t= Type.GetType(typeName); // Hard Code System.Drawing in as an additional assembly // to search if (t==null) t= typeof(System.Drawing.Color).Assembly. GetType(typeName); } return t; } return base.ConvertFrom (context, culture, value); }
As you can see, this uses ITypeResolutionService in the usual case where it is called at design time. Of course, this would not be available at runtime, in which case it resorts to Type.GetType(). One thing to watch out for is that unless you give this function a full assembly qualified type name, this method only searches in the calling assembly and in mscorlib.
The final property that I want to mention is UseStringAsUnderlyingType. This is not needed when binding to properties in other controls. However, it is needed when binding to a column in a DataSet where the data type is not one of the few that are currently supported by ADO.NET. In the case of an unsupported data type, the Value is stored as a string in the data type and is converted to and from this using TypeConverter.ConvertToInvariantString() and TypeConverter.ConvertFromInvariantString(). When displaying data in the control, TypeConverter.ConvertToString() is used instead to ensure that the data format is culture specific.
So, now that we’ve got a control that will edit any data type, we must be finished? Alas, it is never that simple. The default editor for an Image assumes we’re going to embed the image in a resource file as opposed to, say, uploading it to a server. So, we’ll handle this as a special case with our next control.