Getting the Most Out of the .NET Framework PropertyGrid Control
Mark Rideout
Microsoft Corporation
Applies to:
Microsoft® .NET® Framework
Microsoft® Visual Studio® .NET
Summary: Helps you understand the PropertyGrid control in the Microsoft .NET Framework and how to customize it for your application. (37 printed pages)
Introducing the PropertyGrid Control
Creating a PropertyGrid Control
Where to Use the PropertyGrid Control
Selecting an Object
Customizing the PropertyGrid Control
Displaying Complex Properties
Providing a Custom UI for Your Properties
Conclusion
If you have used Microsoft® Visual Basic® or Microsoft Visual Studio .NET then you have used a property browser to browse, view, and edit the properties of one or more objects. The .NET Framework PropertyGrid control is core to the property browser that is in Visual Studio .NET. The PropertyGrid control displays properties for any object or type, and it retrieves the item's properties, primarily using reflection. (Reflection is a technology that provides type information at run time.)
The following screen shot shows how a PropertyGrid looks when placed on a form.
Figure 1. PropertyGrid on a form
The PropertyGrid contains the following parts:
- Properties
- Expandable Properties
- Property Category Headings
- Property Descriptions
- Property Editors
- Property Tabs
- Commands Pane (displays designer verbs that a control's designer exposes)
To create a PropertyGrid control using Visual Studio .NET, you need to add the PropertyGrid control to the toolbox, since it is not included by default. From the Tools menu, select Customize Toolbox. In the dialog box, select the Framework Components tab and then select PropertyGrid.
If you are compiling your code from the command line, use the /reference option and specify System.Windows.Forms.dll.
The following code demonstrates creating a PropertyGrid control and adding it to a form.
' Visual Basic
Imports System
Imports System.Drawing
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Globalization
Public Class OptionsDialog
Inherits System.Windows.Forms.Form
Private OptionsPropertyGrid As System.Windows.Forms.PropertyGrid
Public Sub New()
MyBase.New()
OptionsPropertyGrid = New PropertyGrid()
OptionsPropertyGrid.Size = New Size(300, 250)
Me.Controls.Add(OptionsPropertyGrid)
Me.Text = "Options Dialog"
End Sub
End Class
//C#
using System;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms;
using System.Globalization;
public class OptionsDialog : System.Windows.Forms.Form
{
private System.Windows.Forms.PropertyGrid OptionsPropertyGrid;
public OptionsDialog()
{
OptionsPropertyGrid = new PropertyGrid();
OptionsPropertyGrid.Size = new Size(300, 250);
this.Controls.Add(OptionsPropertyGrid);
this.Text = "Options Dialog";
}
[STAThread]
static void Main()
{
Application.Run(new OptionsDialog());
}
}
There are numerous places in an application where you might want to provide a richer editing experience by having the user interact with a PropertyGrid. One example is an application that has several "settings" or options, some of them complex, that a user can set. You could use radio buttons, combo boxes, or text boxes to represent these options. Instead, this paper will step you though the process of using the PropertyGrid control to create an options window for setting application options. The OptionsDialog
form that you created above will be the start of the options window. Now create a class called AppSettings
that contains all of the properties that map to the application settings. The settings are much easier to manage and maintain if you create a separate class rather than using individual variables.
' Visual Basic
Public Class AppSettings
Private _saveOnClose As Boolean = True
Private _greetingText As String = "Welcome to your application!"
Private _maxRepeatRate As Integer = 10
Private _itemsInMRU As Integer = 4
Private _settingsChanged As Boolean = False
Private _appVersion As String = "1.0"
Public Property SaveOnClose() As Boolean
Get
Return _saveOnClose
End Get
Set(ByVal Value As Boolean)
SaveOnClose = Value
End Set
End Property
Public Property GreetingText() As String
Get
Return _greetingText
End Get
Set(ByVal Value As String)
_greetingText = Value
End Set
End Property
Public Property ItemsInMRUList() As Integer
Get
Return _itemsInMRU
End Get
Set(ByVal Value As Integer)
_itemsInMRU = Value
End Set
End Property
Public Property MaxRepeatRate() As Integer
Get
Return _maxRepeatRate
End Get
Set(ByVal Value As Integer)
_maxRepeatRate = Value
End Set
End Property
Public Property SettingsChanged() As Boolean
Get
Return _settingsChanged
End Get
Set(ByVal Value As Boolean)
_settingsChanged = Value
End Set
End Property
Public Property AppVersion() As String
Get
Return _appVersion
End Get
Set(ByVal Value As String)
_appVersion = Value
End Set
End Property
End Class
//C#
public class AppSettings{
private bool saveOnClose = true;
private string greetingText = "Welcome to your application!";
private int itemsInMRU = 4;
private int maxRepeatRate = 10;
private bool settingsChanged = false;
private string appVersion = "1.0";
public bool SaveOnClose
{
get { return saveOnClose; }
set { saveOnClose = value;}
}
public string GreetingText
{
get { return greetingText; }
set { greetingText = value; }
}
public int MaxRepeatRate
{
get { return maxRepeatRate; }
set { maxRepeatRate = value; }
}
public int ItemsInMRUList
{
get { return itemsInMRU; }
set { itemsInMRU = value; }
}
public bool SettingsChanged
{
get { return settingsChanged; }
set { settingsChanged = value; }
}
public string AppVersion
{
get { return appVersion; }
set { appVersion = value; }
}
}
The PropertyGrid on the options window will manipulate this class, so add the class definition to your application project, either in a new file or at the bottom of the source code for the form.
To identify what the PropertyGrid displays, set the PropertyGrid.SelectedObject property to an object instance. The PropertyGrid does the rest. Each time you set SelectedObject, the PropertyGrid refreshes the properties shown. This provides an easy way to force the refresh of properties, or to switch between objects at run time. You can also call the PropertyGrid.Refresh method to refresh properties.
To continue, update the code in the OptionsDialog
constructor to create an AppSettings
object and set it to the PropertyGrid.SelectedObject property.
' Visual Basic
Public Sub New()
MyBase.New()
OptionsPropertyGrid = New PropertyGrid()
OptionsPropertyGrid.Size = New Size(300, 250)
Me.Controls.Add(OptionsPropertyGrid)
Me.Text = "Options Dialog"
' Create the AppSettings class and display it in the PropertyGrid. Dim appset as AppSettings = New AppSettings() OptionsPropertyGrid.SelectedObject = appset
End Sub
//C#
public OptionsDialog()
{
OptionsPropertyGrid = new PropertyGrid();
OptionsPropertyGrid.Size = new Size(300, 250);
this.Controls.Add(OptionsPropertyGrid);
this.Text = "Options Dialog";
// Create the AppSettings class and display it in the PropertyGrid. AppSettings appset = new AppSettings(); OptionsPropertyGrid.SelectedObject = appset;
}
Compile and run the application. The following screen shot shows how it should look.
Figure 2. AppSettings class selected in the PropertyGrid
You can modify some visual aspects of the PropertyGrid to fit your needs. You might want to change how some properties are displayed, and even choose to not display some properties. How customizable is the PropertyGrid?
Many visual aspects of the PropertyGrid are customizable. Here is a partial list:
- Change the background color, change the font color, or hide the description pane through the HelpBackColor, HelpForeColor, and HelpVisible properties.
- Hide the toolbar through the ToolbarVisible property, change its color through the BackColor property, and display large toolbar buttons through the LargeButtons property.
- Sort the properties alphabetically, and categorized them using the PropertySort property.
- Change the splitter color through the BackColor property.
- Change the grid line and borders through the LineColor property.
For the options window in this example the toolbar is not needed, so set ToolbarVisible to false. Keep the other default settings.
To change how some properties are displayed, you can apply different attributes to the properties. Attributes are declarative tags used to annotate programming elements such as types, fields, methods, and properties that can be retrieved at run time using reflection. Here is a partial list:
- DescriptionAttribute. Sets the text for the property that is displayed in the description help pane below the properties. This is a useful way to provide help text for the active property (the property that has focus). Apply this attribute to the
MaxRepeatRate
property. - CategoryAttribute. Sets the category that the property is under in the grid. This is useful when you want a property grouped by a category name. If a property does not have a category specified, then it will be assigned to the Misc category. Apply this attribute to all properties.
- BrowsableAttribute – Indicates whether the property is shown in the grid. This is useful when you want to hide a property from the grid. By default, a public property is always shown in the grid. Apply this attribute to the
SettingsChanged
property. - ReadOnlyAttribute – Indicates whether the property is read-only. This is useful when you want to keep a property from being editable in the grid. By default, a public property with get and set accessor functions is editable in the grid. Apply this attribute to the
AppVersion
property. - DefaultValueAttribute – Identifies the property's default value. This is useful when you want to provide a default value for a property and later determine if the property's value is different than the default. Apply this attribute to all properties.
- DefaultPropertyAttribute – Identifies the default property for the class. The default property for a class gets the focus first when the class is selected in the grid. Apply this attribute to the
AppSettings
class.
Now apply some of these attributes to the AppSettings
class to change the way the properties are displayed in the PropertyGrid.
' Visual Basic
<DefaultPropertyAttribute("SaveOnClose")> _
Public Class AppSettings
Private _saveOnClose As Boolean = True
Private _greetingText As String = "Welcome to your application!"
Private _maxRepeatRate As Integer = 10
Private _itemsInMRU As Integer = 4
Private _settingsChanged As Boolean = False
Private _appVersion As String = "1.0"
<CategoryAttribute("Document Settings"), _ DefaultValueAttribute(True)> _
Public Property SaveOnClose() As Boolean
Get
Return _saveOnClose
End Get
Set(ByVal Value As Boolean)
SaveOnClose = Value
End Set
End Property
<CategoryAttribute("Global Settings"), _ ReadOnlyAttribute(True), _ DefaultValueAttribute("Welcome to your application!")> _
Public Property GreetingText() As String
Get
Return _greetingText
End Get
Set(ByVal Value As String)
_greetingText = Value
End Set
End Property
<CategoryAttribute("Global Settings"), _ DefaultValueAttribute(4)> _
Public Property ItemsInMRUList() As Integer
Get
Return _itemsInMRU
End Get
Set(ByVal Value As Integer)
_itemsInMRU = Value
End Set
End Property
<DescriptionAttribute("The rate in milliseconds that the text will repeat."), _ CategoryAttribute("Global Settings"), _ DefaultValueAttribute(10)> _
Public Property MaxRepeatRate() As Integer
Get
Return _maxRepeatRate
End Get
Set(ByVal Value As Integer)
_maxRepeatRate = Value
End Set
End Property
<BrowsableAttribute(False), DefaultValueAttribute(False)> _
Public Property SettingsChanged() As Boolean
Get
Return _settingsChanged
End Get
Set(ByVal Value As Boolean)
_settingsChanged = Value
End Set
End Property
<CategoryAttribute("Version"), _ DefaultValueAttribute("1.0"), _ ReadOnlyAttribute(True)> _
Public Property AppVersion() As String
Get
Return _appVersion
End Get
Set(ByVal Value As String)
_appVersion = Value
End Set
End Property
End Class
//C#[DefaultPropertyAttribute("SaveOnClose")]
public class AppSettings{
private bool saveOnClose = true;
private string greetingText = "Welcome to your application!";
private int maxRepeatRate = 10;
private int itemsInMRU = 4;
private bool settingsChanged = false;
private string appVersion = "1.0";
[CategoryAttribute("Document Settings"), DefaultValueAttribute(true)]
public bool SaveOnClose
{
get { return saveOnClose; }
set { saveOnClose = value;}
}
[CategoryAttribute("Global Settings"), ReadOnlyAttribute(true), DefaultValueAttribute("Welcome to your application!")]
public string GreetingText
{
get { return greetingText; }
set { greetingText = value; }
}
[CategoryAttribute("Global Settings"), DefaultValueAttribute(4)]
public int ItemsInMRUList
{
get { return itemsInMRU; }
set { itemsInMRU = value; }
}
[DescriptionAttribute("The rate in milliseconds that the text will repeat."), CategoryAttribute("Global Settings"), DefaultValueAttribute(10)]
public int MaxRepeatRate
{
get { return maxRepeatRate; }
set { maxRepeatRate = value; }
}
[BrowsableAttribute(false), DefaultValueAttribute(false)]
public bool SettingsChanged
{
get { return settingsChanged; }
set { settingsChanged = value; }
}
[CategoryAttribute("Version"), DefaultValueAttribute("1.0"), ReadOnlyAttribute(true)]
public string AppVersion
{
get { return appVersion; }
set { appVersion = value; }
}
}
With these attributes applied to theAppSettings
class, compile and run the application. The following screen shot shows how it should look.
Figure 3. Properties displayed with categories and default values in the PropertyGrid
After working with this version of the options window, you might notice the following things:
- The
SaveOnClose
property gets focus when the window is displayed. - The
MaxRepeatRate
property displays "The rate in milliseconds that the text will repeat" in the description help pane when selected. - The
SaveOnClose
property is displayed under the "Document Settings" category. The other properties are displayed under two other categories named "Global Settings" and "Version." - The
SettingsChanged
property is no longer displayed. - The
AppVersion
property is read-only. Read-only properties are displayed with dimmed text. - When the
SaveOnClose
property has a value other than true, it is displayed in bold text. The PropertyGrid uses bold text to indicate properties that have a non-default value.
So far, the options window has displayed simple types like integers, Booleans, and strings. What about more complex types? What if your application needs to track something like window size, document font, or toolbar color? Some data types provided by the .NET Framework have special display abilities that help make them more usable in the PropertyGrid.
First, update the AppSettings
class to add new properties for window size (Size type), window font (Font type), and toolbar color (Color type).
' Visual Basic
<DefaultPropertyAttribute("SaveOnClose")> _
Public Class AppSettings
Private _saveOnClose As Boolean = True
Private _greetingText As String = "Welcome to your application!"
Private _maxRepeatRate As Integer = 10
Private _itemsInMRU As Integer = 4
Private _settingsChanged As Boolean = False
Private _appVersion As String = "1.0"
Private _windowSize As Size = New Size(100, 100) Private _windowFont As Font = New Font("Arial", 8, FontStyle.Regular) Private _toolbarColor As Color = SystemColors.Control
<CategoryAttribute("Document Settings"), _
DefaultValueAttribute(True)> _
Public Property SaveOnClose() As Boolean
Get
Return _saveOnClose
End Get
Set(ByVal Value As Boolean)
SaveOnClose = Value
End Set
End Property
<CategoryAttribute("Document Settings")> _ Public Property WindowSize() As Size Get Return _windowSize End Get Set(ByVal Value As Size) _windowSize = Value End Set End Property <CategoryAttribute("Document Settings")> _ Public Property WindowFont() As Font Get Return _windowFont End Get Set(ByVal Value As Font) _windowFont = Value End Set End Property <CategoryAttribute("Global Settings")> _ Public Property ToolbarColor() As Color Get Return _toolbarColor End Get Set(ByVal Value As Color) _toolbarColor = Value End Set End Property
<CategoryAttribute("Global Settings"), _
ReadOnlyAttribute(True), _
DefaultValueAttribute("Welcome to your application!")> _
Public Property GreetingText() As String
Get
Return _greetingText
End Get
Set(ByVal Value As String)
_greetingText = Value
End Set
End Property
<CategoryAttribute("Global Settings"), _
DefaultValueAttribute(4)> _
Public Property ItemsInMRUList() As Integer
Get
Return _itemsInMRU
End Get
Set(ByVal Value As Integer)
_itemsInMRU = Value
End Set
End Property
<DescriptionAttribute("The rate in milliseconds that the text will repeat."), _
CategoryAttribute("Global Settings"), _
DefaultValueAttribute(10)> _
Public Property MaxRepeatRate() As Integer
Get
Return _maxRepeatRate
End Get
Set(ByVal Value As Integer)
_maxRepeatRate = Value
End Set
End Property
<BrowsableAttribute(False),
DefaultValueAttribute(False)> _
Public Property SettingsChanged() As Boolean
Get
Return _settingsChanged
End Get
Set(ByVal Value As Boolean)
_settingsChanged = Value
End Set
End Property
<CategoryAttribute("Version"), _
DefaultValueAttribute("1.0"), _
ReadOnlyAttribute(True)> _
Public Property AppVersion() As String
Get
Return _appVersion
End Get
Set(ByVal Value As String)
_appVersion = Value
End Set
End Property
End Class
//C#
[DefaultPropertyAttribute("SaveOnClose")]
public class AppSettings{
private bool saveOnClose = true;
private string greetingText = "Welcome to your application!";
private int maxRepeatRate = 10;
private int itemsInMRU = 4;
private bool settingsChanged = false;
private string appVersion = "1.0";
private Size windowSize = new Size(100,100); private Font windowFont = new Font("Arial", 8, FontStyle.Regular); private Color toolbarColor = SystemColors.Control;
[CategoryAttribute("Document Settings"),
DefaultValueAttribute(true)]
public bool SaveOnClose
{
get { return saveOnClose; }
set { saveOnClose = value;}
}
[CategoryAttribute("Document Settings")] public Size WindowSize { get { return windowSize; } set { windowSize = value;} } [CategoryAttribute("Document Settings")] public Font WindowFont { get {return windowFont; } set { windowFont = value;} } [CategoryAttribute("Global Settings")] public Color ToolbarColor { get { return toolbarColor; } set { toolbarColor = value; } }
[CategoryAttribute("Global Settings"),
ReadOnlyAttribute(true),
DefaultValueAttribute("Welcome to your application!")]
public string GreetingText
{
get { return greetingText; }
set { greetingText = value; }
}
[CategoryAttribute("Global Settings"),
DefaultValueAttribute(4)]
public int ItemsInMRUList
{
get { return itemsInMRU; }
set { itemsInMRU = value; }
}
[DescriptionAttribute("The rate in milliseconds that the text will repeat."),
CategoryAttribute("Global Settings"),
DefaultValueAttribute(10)]
public int MaxRepeatRate
{
get { return maxRepeatRate; }
set { maxRepeatRate = value; }
}
[BrowsableAttribute(false),
DefaultValueAttribute(false)]
public bool SettingsChanged
{
get { return settingsChanged; }
set { settingsChanged = value; }
}
[CategoryAttribute("Version"),
DefaultValueAttribute("1.0"),
ReadOnlyAttribute(true)]
public string AppVersion
{
get { return appVersion; }
set { appVersion = value; }
}
}
The following screen shot shows how the new properties look in the PropertyGrid.
Figure 4. .NET Framework data types displayed in the PropertyGrid
Notice that the WindowFont
property has an ellipsis ("...") button that displays a font selection dialog when pressed. In addition, the property can be expanded to display more Font properties. Some of the Font properties provide a drop-down list of values and details about the font. The WindowSize
property can be expanded to show more properties of the Size type. And finally, notice that the ToolbarColor
property has a swatch of the selected color and a custom drop-down list to select different colors. For these data types and others, the .NET Framework provides additional classes to make editing in the PropertyGrid easier.
Now add two more properties to the AppSettings
class, one called DefaultFileName
and the other called SpellCheckOptions
. The DefaultFileName
property gets or sets a string and the SpellCheckOptions
property gets or sets an instance of the SpellingOptions
class.
The SpellingOptions
class is a new class that will manage the application's spell checking properties. There is no hard and fast rule about when to create a separate class to manage an object's properties; it depends upon your overall class design. Add the SpellingOptions
class definition to your application project, either in a new file or at the bottom of the source code for the form.
' Visual Basic
<DescriptionAttribute("Expand to see the spelling options for the application.")> _
Public Class SpellingOptions
Private _spellCheckWhileTyping As Boolean = True
Private _spellCheckCAPS As Boolean = False
Private _suggestCorrections As Boolean = True
<DefaultValueAttribute(True)> _
Public Property SpellCheckWhileTyping() As Boolean
Get
Return _spellCheckWhileTyping
End Get
Set(ByVal Value As Boolean)
_spellCheckWhileTyping = Value
End Set
End Property
<DefaultValueAttribute(False)> _
Public Property SpellCheckCAPS() As Boolean
Get
Return _spellCheckCAPS
End Get
Set(ByVal Value As Boolean)
_spellCheckCAPS = Value
End Set
End Property
<DefaultValueAttribute(True)> _
Public Property SuggestCorrections() As Boolean
Get
Return _suggestCorrections
End Get
Set(ByVal Value As Boolean)
_suggestCorrections = Value
End Set
End Property
End Class
//C#
[DescriptionAttribute("Expand to see the spelling options for the application.")]
public class SpellingOptions{
private bool spellCheckWhileTyping = true;
private bool spellCheckCAPS = false;
private bool suggestCorrections = true;
[DefaultValueAttribute(true)]
public bool SpellCheckWhileTyping
{
get { return spellCheckWhileTyping; }
set { spellCheckWhileTyping = value; }
}
[DefaultValueAttribute(false)]
public bool SpellCheckCAPS
{
get { return spellCheckCAPS; }
set { spellCheckCAPS = value; }
}
[DefaultValueAttribute(true)]
public bool SuggestCorrections
{
get { return suggestCorrections; }
set { suggestCorrections = value; }
}
}
Compile and run the options window application again. The following screen shot shows what it should look like.
Figure 5. Custom data types without a type converter, displayed in the PropertyGrid
Notice how the SpellcheckOptions
property looks. Unlike the .NET Framework types, it doesn't expand or display a custom string representation. What if you want to provide the same editing experience that the .NET Framework types have to your own complex type? The .NET Framework types use the TypeConverter and UITypeEditor classes to provide most of the PropertyGrid editing support, and you can use them too.
To get the PropertyGrid to expand the SpellingOptions
property, you will need to create a TypeConverter. A TypeConverter provides a way to convert from one type to another. The PropertyGrid uses the TypeConverter to convert your object type to a String, which it uses to display the object value in the grid. During editing, the TypeConverter converts back to your object type from a String. The .NET Framework provides the ExpandableObjectConverter class to make this easier.
Create a class that inherits from ExpandableObjectConverter.
' Visual Basic Public Class SpellingOptionsConverter Inherits ExpandableObjectConverter End Class //C# public class SpellingOptionsConverter:ExpandableObjectConverter { }
Override the CanConvertTo method and return true if the
destinationType
parameter is the same type as the class that uses this type converter (theSpellingOptions
class in your example); otherwise, return the value of the base class CanConvertTo method.' Visual Basic Public Overloads Overrides Function CanConvertTo( _ ByVal context As ITypeDescriptorContext, _ ByVal destinationType As Type) As Boolean If (destinationType Is GetType(SpellingOptions)) Then Return True End If Return MyBase.CanConvertFrom(context, destinationType) End Function //C# public override bool CanConvertTo(ITypeDescriptorContext context, System.Type destinationType) { if (destinationType == typeof(SpellingOptions)) return true; return base.CanConvertTo(context, destinationType); }
Override the ConvertTo method and ensure that the
destinationType
parameter is a String and that the value is the same type as the class that uses this type converter (theSpellingOptions
class in your example). If either case is false, return the value of the base class ConvertTo method; otherwise return a string representation of the value object. The string representation needs to separate each property of your class with a unique delimiter. Since the whole string will be displayed in the PropertyGrid you will want to choose a delimiter that doesn't detract from the readability; commas usually work well.' Visual Basic Public Overloads Overrides Function ConvertTo( _ ByVal context As ITypeDescriptorContext, _ ByVal culture As CultureInfo, _ ByVal value As Object, _ ByVal destinationType As System.Type) _ As Object If (destinationType Is GetType(System.String) _ AndAlso TypeOf value Is SpellingOptions) Then Dim so As SpellingOptions = CType(value, SpellingOptions) Return "Check while typing: " & so.SpellCheckWhileTyping & _ ", check CAPS: " & so.SpellCheckCAPS & _ ", suggest corrections: " & so.SuggestCorrections End If Return MyBase.ConvertTo(context, culture, value, destinationType) End Function //C# public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, System.Type destinationType) { if (destinationType == typeof(System.String) && value is SpellingOptions){ SpellingOptions so = (SpellingOptions)value; return "Check while typing:" + so.SpellCheckWhileTyping + ", check CAPS: " + so.SpellCheckCAPS + ", suggest corrections: " + so.SuggestCorrections; } return base.ConvertTo(context, culture, value, destinationType); }
(Optional) You can enable editing of the object's string representation in the grid by specifying that your type converter can convert from a string. To do this, first override the CanConvertFrom method and return true if the source Type parameter is of type String; otherwise, return the value of the base class CanConvertFrom method.
' Visual Basic Public Overloads Overrides Function CanConvertFrom( _ ByVal context As ITypeDescriptorContext, _ ByVal sourceType As System.Type) As Boolean If (sourceType Is GetType(String)) Then Return True End If Return MyBase.CanConvertFrom(context, sourceType) End Function //C# public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType) { if (sourceType == typeof(string)) return true; return base.CanConvertFrom(context, sourceType); }
To enable editing of the object's base class, you also need to override the ConvertFrom method and ensure that the value parameter is a String. If it is not a String, return the value of the base class ConvertFrom method; otherwise return a new instance of your class (the
SpellingOptions
class in your example) based on the value parameter. You will need to parse the values for each property of your class from the value parameter. Knowing the format of the delimited string you created in the ConvertTo method will help you perform the parsing.' Visual Basic Public Overloads Overrides Function ConvertFrom( _ ByVal context As ITypeDescriptorContext, _ ByVal culture As CultureInfo, _ ByVal value As Object) As Object If (TypeOf value Is String) Then Try Dim s As String = CStr(value) Dim colon As Integer = s.IndexOf(":") Dim comma As Integer = s.IndexOf(",") If (colon <> -1 AndAlso comma <> -1) Then Dim checkWhileTyping As String = s.Substring(colon + 1, _ (comma - colon - 1)) colon = s.IndexOf(":", comma + 1) comma = s.IndexOf(",", comma + 1) Dim checkCaps As String = s.Substring(colon + 1, _ (comma - colon - 1)) colon = s.IndexOf(":", comma + 1) Dim suggCorr As String = s.Substring(colon + 1) Dim so As SpellingOptions = New SpellingOptions() so.SpellCheckWhileTyping = Boolean.Parse(checkWhileTyping) so.SpellCheckCAPS = Boolean.Parse(checkCaps) so.SuggestCorrections = Boolean.Parse(suggCorr) Return so End If Catch Throw New ArgumentException( _ "Can not convert '" & CStr(value) & _ "' to type SpellingOptions") End Try End If Return MyBase.ConvertFrom(context, culture, value) End Function //C# public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { try { string s = (string) value; int colon = s.IndexOf(':'); int comma = s.IndexOf(','); if (colon != -1 && comma != -1) { string checkWhileTyping = s.Substring(colon + 1 , (comma - colon - 1)); colon = s.IndexOf(':', comma + 1); comma = s.IndexOf(',', comma + 1); string checkCaps = s.Substring(colon + 1 , (comma - colon -1)); colon = s.IndexOf(':', comma + 1); string suggCorr = s.Substring(colon + 1); SpellingOptions so = new SpellingOptions(); so.SpellCheckWhileTyping =Boolean.Parse(checkWhileTyping); so.SpellCheckCAPS = Boolean.Parse(checkCaps); so.SuggestCorrections = Boolean.Parse(suggCorr); return so; } } catch { throw new ArgumentException( "Can not convert '" + (string)value + "' to type SpellingOptions"); } } return base.ConvertFrom(context, culture, value); }
Now that you have a type converter class, you need to identify the target class that will use it. You do this by applying the TypeConverterAttribute to the target class (the
SpellingOptions
class in your example).' Visual Basic ' The TypeConverter attribute applied to the SpellingOptions class. <TypeConverter(GetType(SpellingOptionsConverter)), _ DescriptionAttribute("Expand to see the spelling options for the application.")> _ Public Class SpellingOptions ... End Class //C# // The TypeConverter attribute applied to the SpellingOptions class. [TypeConverterAttribute(typeof(SpellingOptionsConverter)), DescriptionAttribute("Expand to see the spelling options for the application.")] public class SpellingOptions{ ... }
Compile and run the options window application again. The following screen shot shows how the options window should now look.
Figure 6. Custom data types with a type converter, displayed in the PropertyGrid
**Note ** If you only want the expandable object support, but not the custom string representation, you can simply apply the TypeConverterAttribute to your class. Specify ExpandableObjectConverter as the type converter type.
For properties that return an enumeration based upon the Enum type, the PropertyGrid automatically displays the enumeration values in a drop-down list. The EnumConverter also provides this functionality. For you own properties, you might want to provide a list of valid values to the user, sometimes called a pick list or domain list, with types not based upon Enum. This is the case when the domain values are not known until run time, or when the values can change.
Modify the options window to provide a domain list of default file names that the user can choose from. You have already added the DefaultFileName
property to the AppSettings
class. The next step is to display the drop-down for the property in the PropertyGrid, in order to provide the domain list.
Create a class that inherits from a type converter class. Since the
DefaultFileName
property is of String type, you can inherit from StringConverter. If a type converter for the type of your property doesn't exist, you can inherit from TypeConverter; in this case it isn't necessary.' Visual Basic Public Class FileNameConverter Inherits StringConverter End Class //C# public class FileNameConverter: StringConverter { }
Override the GetStandardValuesSupported method and return true to indicate that this object supports a standard set of values that can be picked from a list.
' Visual Basic Public Overloads Overrides Function GetStandardValuesSupported( _ ByVal context As ITypeDescriptorContext) As Boolean Return True End Function //C# public override bool GetStandardValuesSupported( ITypeDescriptorContext context) { return true; }
Override the GetStandardValues method and return a StandardValuesCollection filled with your standard values. One way to create a StandardValuesCollection is to provide an array of values in the constructor. For the options window application you can use a String array filled with proposed default file names.
' Visual Basic Public Overloads Overrides Function GetStandardValues( _ ByVal context As ITypeDescriptorContext) _ As StandardValuesCollection Return New StandardValuesCollection(New String() {"New File", _ "File1", _ "Document1"}) End Function //C# public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { return new StandardValuesCollection(new string[]{"New File", "File1", "Document1"}); }
(Optional) If you want the user to be able to type in a value that is not in the drop-down list, override the GetStandardValuesExclusive method and return false. This basically changes the drop-down list style to a combo box style.
' Visual Basic Public Overloads Overrides Function GetStandardValuesExclusive( _ ByVal context As ITypeDescriptorContext) As Boolean Return False End Function //C# public override bool GetStandardValuesExclusive( ITypeDescriptorContext context) { return false; }
Now that you have your own type converter class for displaying a drop-down list, you need to identify the target that will use it. In this case the target is the
DefaultFileName
property, since the type converter is specific to the property. Apply the TypeConverterAttribute to the target property.' Visual Basic ' The TypeConverter attribute applied to the DefaultFileName property. <TypeConverter(GetType(FileNameConverter)),_ CategoryAttribute("Document Settings")> _ Public Property DefaultFileName() As String Get Return _defaultFileName End Get Set(ByVal Value As String) _defaultFileName = Value End Set End Property //C# // The TypeConverter attribute applied to the DefaultFileName property. [TypeConverter(typeof(FileNameConverter)), CategoryAttribute("Document Settings")] public string DefaultFileName { get{ return defaultFileName; } set{ defaultFileName = value; } }
Compile and run the options window application again. The following screen shot shows how the options window should now look. Notice how the DefaultFileName
property displays.
Figure 7. Displaying a drop-down domain list in the PropertyGrid
As mentioned earlier, the .NET Framework types use the TypeConverter and UITypeEditor classes (along with other classes) to provide PropertyGrid editing support. The Support for Custom Types section looked at using a TypeConverter; you can also use the UITypeEditor class to perform your own PropertyGrid customization.
You can provide a small graphical representation along with the property value in the PropertyGrid, similar to what is provided for the Image and Color classes. To do this for your customization, inherit from UITypeEditor, override GetPaintValueSupported and return true. Next, override the UITypeEditor.PaintValue method, and in your method, use the PaintValueEventArgs.Graphics parameter to draw your graphic. Finally, apply the Editor attribute to the class or property that uses your UITypeEditor class.
The following screen shot shows what your results might look like.
Figure 8. Displaying a custom graphic for a property in the PropertyGrid
You can also provide your own drop-down list control, similar to what the Control.Dock property uses to give the user docking selections. To do this, inherit from UITypeEditor, override GetEditStyle, and return a UITypeEditorEditStyle enum value, such as DropDown. Your custom drop-down control must inherit from Control or a Control-derived class (like UserControl). Next, override the UITypeEditor.EditValue method. Use the IServiceProvider parameter to call the IServiceProvider.GetService method to get an IWindowsFormsEditorService instance. Finally, call the IWindowsFormsEditorService.DropDownControl method to show your custom drop-down list control. Remember to apply the Editor attribute to the class or property that uses your UITypeEditor class.
The following screen shot shows what your results might look like.
Figure 9. Displaying a custom drop-down control for a property in the PropertyGrid
In addition to using the TypeEditor and UITypeEditor classes, the PropertyGrid can also be customized to display additional property tabs. Property tabs inherit from the PropertyTab class. You might have seen a custom PropertyTab if you've used the property browser in Microsoft Visual C#™ .NET—the Events tab (the button with lightning bolt) is a custom PropertyTab. The following screen shot shows another example of a custom PropertyTab is shown below. This PropertyTab provides the ability to create a custom button shape by editing bounding points of a button.
Figure 10. Displaying a custom tab in the PropertyGrid
You can find more information about using the UITypeEditor class to customize the PropertyGrid, along with code examples of the above custom user interfaces, Shawn Burke's article, Make Your Components Really RAD with Visual Studio .NET Property Browser.
The ProperyGrid control provided by the .NET Framework provides a rich editing experience that you can use to enhance your user interface. With your new knowledge on how easy the PropertyGrid is to customize, you will be using the PropertyGrid throughout your applications. In addition, since the Visual Studio .NET property browser is based upon the PropertyGrid, you can use these techniques to provide a richer design time experience.