Building a custom UITypeEditor
This walkthrough shows you how to build a custom property editor, as described in Adding designer support. The editor shows a drop-down list in the CodeSmith Generator property grid, filled with the table names of a database.
You need two classes: a property type that holds the data, and an editor that derives from .NET's UITypeEditor class. Build them in a .NET Framework class library, and reference the assembly from your template.
Note
Generator's property grid is built with WPF and has its own editors for common types. Prefer those for new templates; custom Windows Forms UITypeEditors like this one are a legacy option for existing templates.
Tip
The complete source code for both classes, and a template that uses them, is in the DropDownList folder of the CustomPropertiesSample project. It's also on GitHub.
Building the property type
First, create a public class that holds the drop-down list's data. This example calls it DropDownListProperty. Add the properties and constructors:
public class DropDownListProperty
{
private List<string> _values = new List<string>();
public DropDownListProperty()
{
SelectedItem = "None";
}
public DropDownListProperty(List<String> values)
{
if(values.Count > 0)
SelectedItem = values[0];
else
SelectedItem = "None";
Values = values;
}
public List<string> Values
{
get
{
if (_values == null)
_values = new List<String>();
return _values;
}
set
{
if(value != null)
_values = value;
}
}
[Browsable(false)]
public string SelectedItem { get; set; }
}
Values holds the items in the list. SelectedItem holds the item the user picks; the constructors set it to the first value, or to "None" if there are no values. The Browsable(false) attribute keeps the property grid from showing SelectedItem as a property of its own.
Showing the selected value in the property grid
Override ToString(), so the property grid shows the selected value:
/// <summary>
/// The value that we return here will be shown in the property grid.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return SelectedItem;
}
Building the editor
Next, create the class that controls how the property is edited in the property grid. It inherits from UITypeEditor:
/// <summary>
/// Provides a user interface for selecting a state property.
/// </summary>
public class DropDownListPropertyEditor : UITypeEditor
{
private IWindowsFormsEditorService _service = null;
}
The _service field holds the property grid's editor service. You need it again later, in an event handler.
Overriding EditValue
EditValue is called when the user opens the editor:
/// <summary>
/// Displays a list of available values for the specified component than sets the value.
/// </summary>
/// <param name="context">An ITypeDescriptorContext that can be used to gain additional context information.</param>
/// <param name="provider">A service provider object through which editing services may be obtained.</param>
/// <param name="value">An instance of the value being edited.</param>
/// <returns>The new value of the object. If the value of the object hasn't changed, this method should return the same object it was passed.</returns>
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
// This service is in charge of popping our ListBox.
_service = ((IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)));
if (_service != null && value is DropDownListProperty)
{
var property = (DropDownListProperty) value;
var list = new ListBox();
list.Click += ListBox_Click;
foreach (string item in property.Values)
{
list.Items.Add(item);
}
// Drop the list control.
_service.DropDownControl(list);
if (list.SelectedItem != null && list.SelectedIndices.Count == 1)
{
property.SelectedItem = list.SelectedItem.ToString();
value = property;
}
}
}
return value;
}
Step by step, the method:
- Gets the IWindowsFormsEditorService of the property grid and stores it in
_service. - Checks that
value, the object being edited, is aDropDownListProperty, and casts it. - Creates a
ListBox, fills it with theValuesof the property, and handles itsClickevent, so the list can close when the user picks an item. Without that handler, the list stays open. - Calls
DropDownControlto show the list below the property. - Sets
SelectedItemto the item the user picked.
Add the Click handler, which closes the drop-down:
private void ListBox_Click(object sender, EventArgs e)
{
if(_service != null)
_service.CloseDropDown();
}
Overriding GetEditStyle
Override GetEditStyle to tell the property grid that this editor is a drop-down:
/// <summary>
/// Gets the editing style of the <see cref="EditValue"/> method.
/// </summary>
/// <param name="context">An ITypeDescriptorContext that can be used to gain additional context information.</param>
/// <returns>Returns the DropDown style, since this editor uses a drop down list.</returns>
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
// We're using a drop down style UITypeEditor.
return UITypeEditorEditStyle.DropDown;
}
Connecting the editor to the property type
Finally, add an Editor attribute to the DropDownListProperty class. It tells the property grid to use your editor for every property of this type:
[Editor(typeof(DropDownListPropertyEditor), typeof(System.Drawing.Design.UITypeEditor))]
public class DropDownListProperty
Using the editor in a template
This template, adapted from DropDownListSample.cst in the sample project, fills the list with the tables of the selected database. When the user changes SourceDatabase, the OnChanged handler creates a new DropDownListProperty with the table names. CodeSmith.Samples is the assembly that contains the two classes:
<%@ CodeTemplate Language="C#" TargetLanguage="Text" %>
<%@ Property Name="SourceDatabase" Type="SchemaExplorer.DatabaseSchema" Optional="False" Category="Required" OnChanged="OnSourceDatabaseChanged" %>
<%@ Property Name="SelectedTable" Type="CodeSmith.Samples.DropDownListProperty" Optional="True" Category="DropDownList Example" %>
<%@ Assembly Name="CodeSmith.CustomProperties" %>
<%@ Assembly Name="CodeSmith.Samples" %>
<%@ Assembly Name="SchemaExplorer" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="CodeSmith.CustomProperties" %>
<%@ Import Namespace="CodeSmith.Samples" %>
<%@ Import Namespace="SchemaExplorer" %>
<%foreach(string str in SelectedTable.Values){%>
<%= str %>
<%}%>
<script runat="template">
public void OnSourceDatabaseChanged(object sender, System.EventArgs args)
{
SelectedTable = new DropDownListProperty();
if (SourceDatabase != null)
{
foreach (TableSchema table in SourceDatabase.Tables)
{
SelectedTable.Values.Add(table.FullName);
}
}
}
</script>