Property Editors
Property editors are configured to tell the property grid how to choose a user interface for displaying/editing each property. For instance, boolean properties show a check box in the value cell by default. But a custom property editor could be used to change this to a toggle switch control instead.
Property editors have the ability to match properties based on object type, property name, and property type. When a match is made, the property editor gives the user interface (via DataTemplate
objects) for the property. Both the name and value cells can have a custom DataTemplate
applied.
A number of built-in property editors are included with property grid, and these can be extended with any number of custom property editors. The Actipro Editors can even be easily integrated with property grid through the use of property editors, providing advanced editing features for common .NET types like numbers, dates, enums, colors, etc.
Name/Value Cells
DataTemplate
objects can be used to customize the display of the name and/or value cells for one or more properties. Typically, only the value cell is customized, but this is not a requirement. Cell customization is accomplished by assigning a DataTemplate
to a property-related cell through property models or the use of PropertyEditor objects.
Property editors can be defined in several locations, which are discussed in detail below.
Mapping UI to Properties
Whenever the PropertyGrid control goes to generate a name or value cell for a property, it uses its NameTemplateSelector and ValueTemplateSelector objects respectively. The NameTemplateSelector property is pre-assigned with a PropertyGridNameTemplateSelector instance that has logic for matching property editors to a property and selecting a DataTemplate
to use in the name cell. Likewise the ValueTemplateSelector property is pre-assigned with a PropertyGridValueTemplateSelector instance that has logic for matching property editors to a property and selecting a DataTemplate
to use in the value cell.
Both template selectors use the following general prioritization for selecting a DataTemplate
for an IPropertyModel. The steps are followed in order until a valid DataTemplate
is found.
IPropertyModel.NameTemplateKey / ValueTemplateKey - Attempts to locate a
DataTemplate
resource with the suppliedx:Key
.IPropertyModel.ValueTemplateKind (value cells only) - If a kind other than
DefaultValueTemplateKind.None
, uses a specified built-in template.Find the best PropertyEditor to use for subsequent logic. If a IPropertyModel.NamePropertyEditor / ValuePropertyEditor is specified, which can be done using
EditorAttribute
as described below, use it. Otherwise, use property editor matching logic described below to try and locate a property editor.PropertyEditor.NameTemplateKey / ValueTemplateKey - Attempts to locate a
DataTemplate
resource with the suppliedx:Key
.PropertyEditor.ValueTemplateKind (value cells only) - If a kind other than
DefaultValueTemplateKind.None
, uses a specified built-in template.
If none of the above steps supply a DataTemplate
for a name cell, the fallback PropertyGrid.DefaultStringNameTemplate object will be used.
If none of the above steps supply a DataTemplate
for a value cell, one of the following templates will be selected as fallback:
For property models with HasStandardValues and string-based IsLimitedToStandardValues, the PropertyGrid.DefaultLimitedStringValueTemplate object will be used.
For property models with HasStandardValues and non-string-based IsLimitedToStandardValues, the PropertyGrid.DefaultLimitedObjectValueTemplate object will be used.
For property models with HasStandardValues that aren't limited, the PropertyGrid.DefaultSuggestedStringValueTemplate object will be used.
Otherwise the PropertyGrid.DefaultStringValueTemplate object will be used.
Property Editor Selection Process
As mentioned in the template selection process flow above, there are scenarios where an attempt is made to locate the property editor that best matches a property. The IPropertyModel.NamePropertyEditor and The ValuePropertyEditor properties are first examined to see if a property editor is already available. If not, the property editors defined in the PropertyGrid.PropertyEditors and PropertyGrid.DefaultPropertyEditors collections are the ones that are examined next.
The PropertyGrid.PropertyEditors collection contains property editors that should only be used by that particular property grid control instance. The PropertyGrid.DefaultPropertyEditors collection on the other hand is a global collection that is shared by all property grid controls and has a number of pre-defined property editors built-in. This collection is where property editors should be defined that apply to all property grid controls in an application. Any number of custom property editors can be added to either collection.
Each PropertyEditor object has several properties that are used to determine the best match: ObjectType, PropertyName, and PropertyType. Property names must match exactly, while types offer a few more comparison options. In addition to exact matches, types can match if one of their base classes is specified in the property editor, or if it (or one of its base classes) implements a specified interface.
This table defines the precedence of property editor defintions, with the highest priority at the top ('-' indicates the given property is null or undefined):
Source | Object Type | Property Name | Property Type |
---|---|---|---|
PropertyEditor defined directly on the property via EditorAttribute | |||
Instance PropertyEditors | Exact Match | Exact Match | Exact Match |
Global DefaultPropertyEditors | Exact Match | Exact Match | Exact Match |
Instance PropertyEditors | Exact Match | Exact Match | Sub-class Match |
Global DefaultPropertyEditors | Exact Match | Exact Match | Sub-class Match |
Instance PropertyEditors | Exact Match | Exact Match | Interface Match |
Global DefaultPropertyEditors | Exact Match | Exact Match | Interface Match |
Instance PropertyEditors | Exact Match | Exact Match | Generic Interface Match |
Global DefaultPropertyEditors | Exact Match | Exact Match | Generic Interface Match |
Instance PropertyEditors | Sub-class Match | Exact Match | Exact Match |
Global DefaultPropertyEditors | Sub-class Match | Exact Match | Exact Match |
Instance PropertyEditors | Sub-class Match | Exact Match | Sub-class Match |
Global DefaultPropertyEditors | Sub-class Match | Exact Match | Sub-class Match |
Instance PropertyEditors | Sub-class Match | Exact Match | Interface Match |
Global DefaultPropertyEditors | Sub-class Match | Exact Match | Interface Match |
Instance PropertyEditors | Sub-class Match | Exact Match | Generic Interface Match |
Global DefaultPropertyEditors | Sub-class Match | Exact Match | Generic Interface Match |
Instance PropertyEditors | Interface Match | Exact Match | Exact Match |
Global DefaultPropertyEditors | Interface Match | Exact Match | Exact Match |
Instance PropertyEditors | Interface Match | Exact Match | Sub-class Match |
Global DefaultPropertyEditors | Interface Match | Exact Match | Sub-class Match |
Instance PropertyEditors | Interface Match | Exact Match | Interface Match |
Global DefaultPropertyEditors | Interface Match | Exact Match | Interface Match |
Instance PropertyEditors | Interface Match | Exact Match | Generic Interface Match |
Global DefaultPropertyEditors | Interface Match | Exact Match | Generic Interface Match |
Instance PropertyEditors | Generic Interface Match | Exact Match | Exact Match |
Global DefaultPropertyEditors | Generic Interface Match | Exact Match | Exact Match |
Instance PropertyEditors | Generic Interface Match | Exact Match | Sub-class Match |
Global DefaultPropertyEditors | Generic Interface Match | Exact Match | Sub-class Match |
Instance PropertyEditors | Generic Interface Match | Exact Match | Interface Match |
Global DefaultPropertyEditors | Generic Interface Match | Exact Match | Interface Match |
Instance PropertyEditors | Generic Interface Match | Exact Match | Generic Interface Match |
Global DefaultPropertyEditors | Generic Interface Match | Exact Match | Generic Interface Match |
Instance PropertyEditors | - | Exact Match | Exact Match |
Global DefaultPropertyEditors | - | Exact Match | Exact Match |
Instance PropertyEditors | - | Exact Match | Sub-class Match |
Global DefaultPropertyEditors | - | Exact Match | Sub-class Match |
Instance PropertyEditors | - | Exact Match | Interface Match |
Global DefaultPropertyEditors | - | Exact Match | Interface Match |
Instance PropertyEditors | - | Exact Match | Generic Interface Match |
Global DefaultPropertyEditors | - | Exact Match | Generic Interface Match |
Instance PropertyEditors | - | Exact Match | - |
Global DefaultPropertyEditors | - | Exact Match | - |
Instance PropertyEditors | - | - | Exact Match |
Global DefaultPropertyEditors | - | - | Exact Match |
Instance PropertyEditors | - | - | Sub-class Match |
Global DefaultPropertyEditors | - | - | Sub-class Match |
Instance PropertyEditors | - | - | Interface Match |
Global DefaultPropertyEditors | - | - | Interface Match |
Instance PropertyEditors | - | - | Generic Interface Match |
Global DefaultPropertyEditors | - | - | Generic Interface Match |
Instance PropertyEditors | - | - | - |
Global DefaultPropertyEditors | - | - | - |
Note
The search through the PropertyEditors and PropertyGrid.DefaultPropertyEditors collections will take the best match per the table above. If two or more matches are made with the same precedence level, the last one in the collection is chosen. This ensures that any user-defined property editors have a higher priority than built-in ones.
Built-in DataTemplates and Property Editors
The property grid comes with several built-in DataTemplate
objects based on native controls and property editors that reference them. Each of the built-in DataTemplate
objects is a property on PropertyGrid, such as DefaultStringValueTemplate. This way, it's easy to alter the built-in DataTemplate
objects as needed.
The following table lists the built-in DataTemplate
object for use in name cells:
Property | Description |
---|---|
DefaultStringNameTemplate | A DataTemplate with a read-only TextBlock that can be used for name column cells. This is used by default for all name cells. |
The following table lists the built-in DataTemplate
objects and property editors for use in value cells:
Property | Description |
---|---|
DefaultBooleanValueTemplate | A DataTemplate for editing a Boolean value with a two-state CheckBox . The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.Boolean is used. The BooleanPropertyEditor class also selects this template. |
DefaultBrushValueTemplate | A DataTemplate for editing a Brush value with a swatch that renders the value, and a TextBox for editing the value. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.Brush is used. The BrushPropertyEditor class also selects this template. |
DefaultColorValueTemplate | A DataTemplate for editing a Color value with a swatch that renders the value, and a TextBox for editing the value. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.Color is used. The ColorPropertyEditor class also selects this template. |
DefaultExtendedStringValueTemplate | A DataTemplate for editing a String value (or Object value via ValueAsString ) with a TextBox and ... (ellipses) button. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.ExtendedString is used. The ExtendedStringPropertyEditor class also selects this template. |
DefaultFontFamilyValueTemplate | A DataTemplate for editing a FontFamily value with a ComboBox that contains system font families. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.FontFamily is used. The FontFamilyPropertyEditor class also selects this template. |
DefaultFontStretchValueTemplate | A DataTemplate for editing a FontStretch value with a ComboBox that contains font stretch options. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.FontStretch is used. The FontStretchPropertyEditor class also selects this template. |
DefaultFontStyleValueTemplate | A DataTemplate for editing a FontStyle value with a ComboBox that contains font style options. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.FontStyle is used. The FontStylePropertyEditor class also selects this template. |
DefaultFontWeightValueTemplate | A DataTemplate for editing a FontWeight value with a ComboBox that contains font weight options. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.FontWeight is used. The FontWeightPropertyEditor class also selects this template. |
DefaultLimitedObjectValueTemplate | A DataTemplate for editing an Object value with a non-editable ComboBox (drop-down only) that contains suggested options. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.LimitedObject is used. The LimitedObjectPropertyEditor class also selects this template. |
DefaultLimitedStringValueTemplate | A DataTemplate for editing a String value (or Object value via ValueAsString ) with a non-editable ComboBox (drop-down only) that contains suggested options. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.LimitedString is used. The LimitedStringPropertyEditor class also selects this template. |
DefaultNullableBooleanValueTemplate | A DataTemplate for editing a nullable Boolean with a three-state CheckBox . The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.NullableBoolean is used. The NullableBooleanPropertyEditor class also selects this template. |
DefaultStringValueTemplate | A DataTemplate for editing a String value (or Object value via ValueAsString ) with a TextBox . The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.String is used. The StringPropertyEditor class also selects this template. |
DefaultSuggestedStringValueTemplate | A DataTemplate for editing a String value (or Object value via ValueAsString ) with an editable ComboBox that contains suggested options. The PropertyGridValueTemplateSelector selects this template when DefaultValueTemplateKind.SuggestedString is used. The SuggestedStringPropertyEditor class also selects this template. |
ImmediateStringValueTemplate | A DataTemplate that is the same as DefaultStringValueTemplate, but the edited string value is immediately updated as text is typed instead of only on focus loss. |
Integrating with Actipro Editors
The powerful edit box controls in the Actipro Editors product (sold separately from Grids) can be easily integrated with the property grid for handling advanced editing of numeric types, enumerations, and more. Often this functionality is enabled by adding one line of code.
See the Actipro Editors / PropertyGrid Interoperability topic for details.
Creating DataTemplates
The data context passed to each DataTemplate
is the IPropertyModel for which the DataTemplate
was selected. This means that any UI controls in the value cell template should generally two-way bind to properties like Value or ValueAsString if the UI control requires a string value. Properties like IsReadOnly can also be used to ensure that the UI control is read-only when appropriate.
This code shows the default DataTemplate
used in PropertyGrid.DefaultStringValueTemplate:
<shared:NoOpConverter x:Key="NoOpConverter" />
<DataTemplate>
<TextBox Text="{Binding ValueAsString, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True, Converter={StaticResource NoOpConverter}}"
IsReadOnly="{Binding IsReadOnly}"
Style="{StaticResource {x:Static themes:SharedResourceKeys.EmbeddedTextBoxStyleKey}}"
/>
</DataTemplate>
Note
There is an issue when binding the ValueAsString
property to a string
property, such as the TextBox.Text
. When setting ValueAsString
, if the underlying object or TypeConverter
modifies the value such that the ValueAsString
value is updated then the bound control will not update accordingly. The problem arises from an optimization present in the WPF binding system, and can be corrected by using a no-op (no operation) converter, such as NoOpConverter. The use of the converter on the binding statement, regardless of whether it actually converts the value, forces the control to update itself appropriately.
When ValueAsString is used, the Value is converted to a string representation with the virtual PropertyModelBase.ConvertToString method. Likewise, the string value can be converted back to an appropriate object with the virtual ConvertFromString method. The logic of these methods can be overridden in custom property model classes.
Additionally, a TypeConverter
can be defined on the value class/struct/enum and can be used to convert the object to and from a string (e.g. for editing in a TextBox). Many .NET primitives have built-in type converters that support string conversion.
This code shows the default DataTemplate
used in PropertyGrid.DefaultLimitedObjectValueTemplate:
<shared:NoOpConverter x:Key="NoOpConverter" />
<DataTemplate>
<ComboBox SelectedItem="{Binding Value, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
ItemsSource="{Binding StandardValues}" IsEditable="False" IsReadOnly="{Binding IsReadOnly}"
Style="{StaticResource {x:Static themes:SharedResourceKeys.EmbeddedComboBoxStyleKey}}"
/>
</DataTemplate>
If a PropertyEditor is used to select the DataTemplate
, it will be available in the IPropertyModel.NamePropertyEditor or ValuePropertyEditor properties. Sometimes custom property editors may have additional option properties set on them. As an example, the DataTemplate
could bind to a Format
property on a custom value property editor with this sort of binding syntax: {Binding ValuePropertyEditor.Format}
. Since the IPropertyModel itself is the binding context, the property editor and its options can be accessed in this way.
Specifying a Property Editor in XAML
It's very easy to define custom property editors in XAML, both at instance and global levels.
This example shows how a custom property editor can be applied to a property named "Foo" via the PropertyGrid.PropertyEditors property, but only on the single property grid instance upon which the property editor is defined.
<grids:PropertyGrid DataObject="{Binding}">
<grids:PropertyGrid.PropertyEditors>
<grids:PropertyEditor PropertyName="Foo">
<grids:PropertyEditor.ValueTemplate>
<DataTemplate>
<!-- NOTE: Put your UI here -->
</DataTemplate>
</grids:PropertyEditor.ValueTemplate>
</grids:PropertyEditor>
</grids:PropertyGrid.PropertyEditors>
</grids:PropertyGrid>
This example shows how a custom property editor can be applied to a property named "Foo" via a PropertyGridPropertyEditorsModifier, which is a mechanism used to alter the global PropertyGrid.DefaultPropertyEditors collection (applies to all property grid instances in the application). The PropertyGridPropertyEditorsModifier.Clear property can be set to true
to force the built-in default property editors collection to first clear. Then any property editors added to the modifier are added to the collection.
<grids:PropertyGridPropertyEditorsModifier x:Key="{x:Static grids:PropertyGrid.PropertyEditorsModifierKey}">
<grids:PropertyEditor PropertyName="Foo">
<grids:PropertyEditor.ValueTemplate>
<DataTemplate>
<!-- NOTE: Put your UI here -->
</DataTemplate>
</grids:PropertyEditor.ValueTemplate>
</grids:PropertyEditor>
</grids:PropertyGridPropertyEditorsModifier>
Note
The PropertyGridPropertyEditorsModifier needs to be placed in your app's Application.Resources
using the x:Key
specified above, or it will not be applied properly.
Specifying a Property Editor at the Property Level
As described above, the IPropertyModel.NamePropertyEditor or ValuePropertyEditor properties can provide a specific PropertyEditor to use for a property model. These properties or even the DataTemplate
-related selection properties on IPropertyModel can be set in a data factory or in the case of the PropertyModel, set directly in XAML.
EditorAttribute
can be used to designate a PropertyEditor-based type as the property editor for a property.
For instance, if integrating with Actipro Editors and a date-only property editor should be used on a DateTime
-based property, you can apply a DatePropertyEditor to the the property like this:
[Editor(typeof(DatePropertyEditor), typeof(PropertyEditor))]
public DateTime MyProperty {
...
}
Since EditorAttribute
only allows a Type
to be specified, if you wish to have any non-default options set on a property editor or if you are building a custom property editor, you need to make a class that directly or indirectly inherits PropertyEditor and updates/sets those options in its constructor. Then reference that derived type in the EditorAttribute
.
Starting Property Value Editing
PropertyGridItem is the "container" UI control for each row in a PropertyGrid. When a PropertyGridItem has keyboard focus, any text input can be forwarded to the first focusable control. The first focusable control is typically located in the associated value cell. This makes it easier to start to input data for a given property.
This behavior is controlled by the static PropertyGrid.StartPropertyValueEditHandlers dictionary. This dictionary maps a Type
to a PropertyGridItemActionHandler delegate. Using the type of the first focusable control, the property grid will search for an associated delegate to execute. The search will start with the concrete type, then search up the base class types until a delegate is found or until no more base types are available.
Once a delegate is found, it will be executed. The delegate is responsible for moving the focus and/or passing the text input. The dictionary is initialized with several entries to support TextBox
, ToggleButton
, and UIElement
.
Committing Property Value Changes
When the property grid receives a key down event for the Enter
key, it will attempt to find an appropriate action to perform. The action would generally commit any pending changes for controls like TextBox
that don't push changes to binding sources immediately. For other control types, such as CheckBox
, the value is always immediately committed back, and custom actions aren't needed. After the action is executed, focus is automatically moved back to the parent PropertyGridItem if focus was within a property editor.
This behavior is control by the static PropertyGrid.CommitPropertyValueEditHandlers dictionary. This dictionary maps a Type
to a PropertyGridItemActionHandler delegate. Using the type of the focused control, the property grid will search for an associated delegate to execute. The search will start with the concrete type, then search up the base class types until a delegate is found or until no more base types are available.
Once a delegate is found, it will be executed. The delegate is responsible for committing any changes. The dictionary is initialized with a two entries to support TextBox
and ComboBox
.
This sample code shows the default handler for TextBox
, which can be used as a basis for how to handle custom controls:
private bool CommitForTextBox(PropertyGridItemActionRequest request) {
if (request == null)
return false;
var control = request.Element as TextBox;
if ((control == null) || (!control.IsEnabled) || (control.IsReadOnly))
return false;
var bindingExpression = control.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null) {
bindingExpression.UpdateSource();
return true;
}
return false;
}
This behavior can be disabled by setting KeyboardNavigation.AcceptsReturn
to false
on the property grid.
Canceling Property Value Changes
When the property grid receives a key down event for the Escape
key, it will attempt to find an appropriate action to perform. The action would generally cancel any pending changes. After the action is executed, focus is automatically moved back to the parent PropertyGridItem if focus was within a property editor.
This behavior is control by the static PropertyGrid.CancelPropertyValueEditHandlers dictionary. This dictionary maps a Type
to a PropertyGridItemActionHandler delegate. Using the type of the focused control, the property grid will search for an associated delegate to execute. The search will start with the concrete type, then search up the base class types until a delegate is found or until no more base types are available.
Once a delegate is found, it will be executed. The delegate is responsible for cancelling any changes. The dictionary is initialized with a two entries to support TextBox
and ComboBox
.
This sample code shows the default handler for TextBox
, which can be used as a basis for how to handle custom controls:
private bool CancelForTextBox(PropertyGridItemActionRequest request) {
if (request == null)
return false;
var control = request.Element as TextBox;
if ((control == null) || (!control.IsEnabled) || (control.IsReadOnly))
return false;
var bindingExpression = control.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null) {
bindingExpression.UpdateTarget();
return true;
}
return false;
}
Property Dialogs (ExtendedStringPropertyEditor)
The ExtendedStringPropertyEditor class activates a value DataTemplate
for editing a String
value (or Object
value via ValueAsString
) with a TextBox
and ...
(ellipses) button. A common use of this UI is to allow the end user to show a dialog or popup that allows extended input by clicking the ellipses button.
The button's Command
property is bound to the ExtendedStringPropertyEditor.ButtonCommand property, which must be set in your code to an ICommand
that shows appropriate UI such as a dialog in response. The related IPropertyModel is passed as a parameter to the command.
This sample code uses the ExtendedStringPropertyEditor for a property named Path
:
<grids:PropertyGrid x:Name="propGrid" DataObject="{Binding}">
<grids:PropertyGrid.PropertyEditors>
<grids:ExtendedStringPropertyEditor x:Name="dialogEditor" PropertyName="Path" />
</grids:PropertyGrid.PropertyEditors>
</grids:PropertyGrid>
This sample code wires up the command to the property editor:
private void InitializeDialogEditorButtonCommand() {
dialogEditor.ButtonCommand = new DelegateCommand<object>(p => {
var propertyModel = p as IPropertyModel;
if (propertyModel != null) {
// NOTE: Show a dialog or popup here and update propertyModel.Value after
}
});
}
TextBox and Button Enabled States
The TextBox
and ellipses Button
will both be disabled by default when IPropertyModel.IsReadOnly is true
. This can be a problem in various scenarios though.
A common scenario is where a collection property only has a getter, and thus the property model thinks it's read-only. In this scenario, the TextBox
and ellipses Button
will be disabled. There is a ExtendedStringPropertyEditor.Behavior property that can be set to a ExtendedStringPropertyEditorBehavior value to better control read-only/enabled states of controls in the template. Set the Behavior property to ButtonEnabled
to ensure the button is enabled for collection properties.
This sample code uses the ExtendedStringPropertyEditor for a collection property named Items
that only has a getter, allowing the end user to show a dialog for editing the collection:
<grids:PropertyGrid x:Name="propGrid" DataObject="{Binding}">
<grids:PropertyGrid.PropertyEditors>
<grids:ExtendedStringPropertyEditor x:Name="collectionEditor" PropertyName="Items" Behavior="ButtonEnabled" />
</grids:PropertyGrid.PropertyEditors>
</grids:PropertyGrid>
In other scenarios where you have a get/set property and want the ellipses Button
to remain enabled but don't want the TextBox
editable, set the Behavior property to TextReadOnly
.
Note that when IPropertyModel.IsHostReadOnly is true
(meaning the PropertyGrid.IsReadOnly property is set), both the TextBox
and ellipses Button
will both be disabled, even if a behavior described above says otherwise.