Setting an object to edit

Assign the object to be edited to the data form’s Data property. Data is a dependency property and can be data bound. If the value of Data changes, and the fields are auto generated, the data form will update itself accordingly. This is true whether or not the new data is the same type as the old data.

Use the DataType property in the situation where Data is bound to a property of your view model and that property could return null. If DataType is provided and if the type has a parameterless constructor, the data form will create a new instance of the type, assign it as the current value of the Data property, and load the object for editing.

Controlling when changes are committed

The CommitChanges property determines whether changes made by the user are propagated to the underlying data object. If CommitChanges is true, then changes made by the user are immediately propagated to the underlying data. If false, then changes made by the user are not immediately committed to the data object. These are referred to as “pending changes.” When CommitChanges is changed from false to true, then all pending changes are committed to the underlying data object at once. Having complete control over when changes are committed to the underlying data facilitates scenarios such as in a dialog were it might be necessary to cancel the entire operation.

Explicit field creation

DataForm is an ItemsControl. The native container type is DataFormField. However, it is not recommended that you place DataFormField controls directly into the DataForm. Instead, use DataFormFieldDescriptor objects. This is true whether filling the Items collection in Xaml, or binding a collection to the data form’s ItemsSource property.

When defined explicitly, default field are ordering is the order in which they are defined. You can, however, still sort based on the field’s display name by setting the data form’s Sorting property to either Ascending or Descending.

Here is an example of explicit field creation in Xaml:

<dc:DataForm Data="{Binding SelectedItem}">
     <dc:DataFormFieldDescriptor PropertyName="Id" DisplayName="ID" />
     <dc:DataFormFieldDescriptor PropertyName="FirstName" DisplayName="First Name" />
     <dc:DataFormFieldDescriptor PropertyName="LastName" DisplayName="Last Name" />
     <dc:DataFormFieldDescriptor PropertyName="Gender" DisplayName="Sex" />
     <dc:DataFormFieldDescriptor PropertyName="MainAddress" DisplayName="Primary Address">
         <dc:DataFormFieldDescriptor.SubFields>
             <dc:DataFormFieldDescriptor PropertyName="Address1" DisplayName="Street" />
             <dc:DataFormFieldDescriptor PropertyName="City" DisplayName="City" />
             <dc:DataFormFieldDescriptor PropertyName="State" DisplayName="State" />
         </dc:DataFormFieldDescriptor.SubFields>
     </dc:DataFormFieldDescriptor> </dc:DataForm
>

The one property of a field descriptor that is required is PropertyName. Use DisplayName to specify a display string different from PropertyName. The remaining properties of a field descriptor are as follows:

  • DataTemplate – Allows you to define a DataTemplate which shows how to render the data value.
  • EditorSettings – Allows customization of the built-in editor.
  • FieldStyle – Allows you to define a custom Style which is applied to the DataFormField control.
  • IsExpanded – Applies only when the field has sub fields.
  • IsReadOnly – Set to true if the property itself is not read-only but you want it to be treated as if it is.
  • ShowReadOnlyFields – Applies to the sub fields only. Overrides value inherited from the parent data form.
  • SortString – Use to define a sort order which is different from that based on display name, which is the default.
  • StringFormat – Specify a format string used for displaying the value.

Auto generating fields

If no explicit fields are defined the DataForm will generate labeled fields with default editors for every writable property of the object being edited.

If you want fields to be generated for read-only properties, then set the data form’s ShowReadOnlyFields property to true.

If there are properties with sub properties that you want to be editable, set the data form’s ShowSubFields property to true. If ShowSubFields is false, fields will not be auto generated for any property which is not a primitive, a string or a list. The exception to this rule is if the property has a TypeConverter associated with it which can convert it to a string.

When a field is being auto generated, an object of type DataFormFieldDescriptor is created as an intermediate representative for the property. After this object has been created but before it has been placed into the collection the routed event AutoGeneratingField is raised. The event’s arguments have a property named FieldDescriptor which exposes this object. The args also have a property name FullDataPath which identifies the property in relation to the root data object. If the Handled property of the args is set to true, then the field will not be added to the collection.

Using the built-in editor

The DataFormFieldDescriptor object has a property named EditorSettings which takes an object of type EditorSettings. This object serves as a proxy to the field’s underlying editing control. If a field descriptor does not have an editor settings assigned at the time the field is being created, a default settings instance is assigned, based on the type of the property being edited (Properties of type DateTime, Boolean, Enum, Collections and Lists are assigned type specific editors. All others are assigned the default TextBox based editor.)

Here is an example of setting up a built-in editor for a number which should be between 0 and 120, incrementing by 1.

<dc:DataFormFieldDescriptor PropertyName="Age" DisplayName="Age">
    <dc:DataFormFieldDescriptor.EditorSettings>
        <dc:NumericEditorSettings CommitOnContentChanged="True" MinValue="0" MaxValue="120" Increment="1" EnforceMinMax="True" />
    </dc:DataFormFieldDescriptor.EditorSettings
>
</
dc:DataFormFieldDescriptor
>

Class EditorSettings defines properties available to all of the built-in editors (though not all editors use all properties.) Behaviors that can be modified with editor settings properties include:

  • The Editor’s Style is determined by the editor settings. The settings EditorStyle property is available for you to set the style directly. (The Style’s TargetType should be DevComponents.WPF.EditingContentControl.)
  • By default, changes made in the editor are not transferred to the DataFormField control which contains it until the editor looses focus. Use property CommitOnContentChanged to change this.
  • Editors which have a drop down may or may not support in-line editing through the text box. For the editors which support it, property IsInCellEditingEnabled is used to specify whether the text box based editing should be available.
  • If the editor uses a button (for example, to open a drop down) the property IsButtonHighlightedOnMouseOver controls whether the button is made visible or highlighted on mouse over.
  • Define what is displayed for a null value with property NullValueDisplayString.
  • Some properties are mapped directly to the embedded TextBox:
    • AcceptsReturn – Specifies whether the editor’s text box (if it has one) can accept the enter key.
    • AutoComplete – Enables auto complete in the text box. This is an enum with possible values of Off (no auto complete) ReadOnly (strict auto-complete) FreeText (auto complete while allowing values not in the list.)
    • AutoCompleteList – If auto complete is enabled, this is the list of possible values. Some editors, like the enum editor or the Boolean editor, will setup this list themselves.
    • Mask – A mask for controlling what characters can be typed where. (Based on System.ComponentModel.MaskedTextProvider. See MSDN for details.)
    • StringFormat – A format string which is applicable to the value being edited.
    • TextWrapping – A value of type System.Windows.TextWrapping.

Some type specific editors are built-in. These editors are essentially defined by classes which derive from the class EditorSettings. To use one of these editors for your field simply set the field descriptor’s editor settings to an instance of the appropriate class. In the example above, the editor settings is set to an instance of NumericEditorSettings, indicating that the numeric editor should be used. Here is the list of built-in editors and the settings classes which define them:

  • For editing bool values, use BooleanEditorSettings. This class exposes a Type property which can be either InLineCheckBox or DropDown. DisplayNameTrue and DisplayNameFalse can be used to define display values other than the default.
  • For editing numbers with a spinner, use NumericEditorSettings. The numeric editor works for numbers of all types. Properties at your disposal include MinValue, MaxValue, Precision and Increment. If in-line text box editing is enabled, you can use EnforceMinMax to automatically set up a validation rule which fires if the user enters a value outside the range.
  • For editing enum values, use EnumEditorSettings. This editor makes use of the DevComponents EnumEditor control. The Type property specifies whether the enum values are presented in-line or in a drop down. Specify custom display names with the DisplayNames list. (It is also possible to specify display name through the DescriptionAttribute set on the enum value definition.)
  • Use DateEditorSettings for DateTime values which represent a date.
  • Use TimeOfDayEditorSettings for DateTime values which represent a time of day.
  • For an editor where you specify a list of possible values which the user can select from, use SelectorEditorSettings. This is another editor with a Type property for specifying whether the values are presented in-line or in a drop down. The PossibleValues property specifies the list of possible values.
  • If you need the editor to open a dialog window, use the DialogWindowEditorSettings and specify the window’s Style with the DialogWindowStyle property.
  • If you want an editor which opens a dialog window that has a data form inside of it, use DataFormEditorSettings. Custom field descriptors can be specified with the FieldDescriptors property. There are also properties ShowSubFields and ShowReadOnlyFields, and a list for form level validation rules.
  • For collections of primitive values (i.e. values which don’t have sub properties, like numbers and strings) use PrimitiveCollectionEditorSettings. This editor does support in-line text box based editing of comma delimited values.
  • Use CollectionEditorSettings for editing collections of objects which have sub properties. This editor is based on the DevComponents CollectionEditor. It has a Type property for specifying whether the collection editor uses a PropertyGrid or a DataForm for editing collection member properties. The NewItemTypes can be used to provide a list of types that can be created.

Defining a custom editor

Defining a custom editor starts with defining a custom Style for the editing control (type EditingContentControl.) Often, this is all that is needed. However, if you need to you can derive your own editor settings class. The settings are available to the editor via the editor’s EditorSettings property, which makes the settings object available from within a Style setter or control template. Following is an example of a simple Style which defines a custom editor which allows the user to edit the value by means of a Slider.

<Style x:Key="AgeCustomEditorStyle" TargetType="dc:EditingContentControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="dc:EditingContentControl">
                <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
                    <Slider Maximum="100" SmallChange="1" LargeChange="5" AutoToolTipPrecision="1" 
                           AutoToolTipPlacement="TopLeft" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" 
                           TickPlacement="None" TickFrequency="1" VerticalAlignment="Center"
                           Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter
>
</
Style
>

Notice that the Value of the slider is bound to the Content of the editing control with a two way binding. All that remains to use this Style for the editor of a field is to set it as the value of the editor setting’s EditorStyle property. For example:

<dc:DataFormFieldDescriptor PropertyName="Age" DisplayName="Age">
    <dc:DataFormFieldDescriptor.EditorSettings>
        <dc:EditorSettings CommitOnContentChanged="True" EditorStyle="{StaticResource AgeCustomEditorStyle}" />
    </dc:DataFormFieldDescriptor.EditorSettings
>
</
dc:DataFormFieldDescriptor
>

A useful feature of the editing control is that it has built-in support for editing with a drop down. Just set a value for the DropDownTemplate property. For example, here is one way to create a custom editor for a Color property value:

<Style x:Key="CustomColorEditorStyle" TargetType="dc:EditingContentControl">
    <Setter Property="DropDownTemplate">
        <Setter.Value>
            <ControlTemplate>
                <Border Padding="10" Background="White">
                    <dc:ColorControl Color="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter
>
</
Style
>

Using attributes for customization

If you have ownership of the class definition for the object that is being edited, then some customization is possible using Attributes on the property definitions, especially when auto generating fields. The DisplayName attribute can be used to modify the display name. The Browsable attribute, if set to false, will prevent the property from being included automatically. And the EditorSettings type does in fact derive from Attribute, so it is possible to make customizations to the editor. Here is an example:

[NumericEditorSettings(MinValue = 0, MaxValue = 120, Increment = 1, Precision = 0, EnforceMinMax = true)]

public double Age

{

    get;

    set;

}

Validating user input

User input can be validated on a per field basis and on the data form as a whole. Validation is based on built-in WPF validation (see System.Windows.Controls.Validation on MSDN.) Validation rules can be assigned to individual fields as well as the entire data form. A validation rule is any object that inherits from System.Windows.Controls.ValidationRule.

To associate validation rules with a specific field, add the rules to the editor setting’s ValidationRules collection. The rule’s Validate method will be called any time the value of the field’s Data property changes. The following field descriptor includes an editor settings that has two validation rules:

<dc:DataFormFieldDescriptor PropertyName="FirstName" DisplayName="First Name">
    <dc:DataFormFieldDescriptor.EditorSettings>
        <dc:EditorSettings NullValueDisplayString="(enter a first name)">
            <dc:EditorSettings.ValidationRules>
                <dc:HasValueValidationRule ErrorDescription="A first name is required." />
                <local:CapitalizeFirstLetterRule />
            </dc:EditorSettings.ValidationRules>
        </dc:EditorSettings>
    </dc:DataFormFieldDescriptor.EditorSettings
>
</
dc:DataFormFieldDescriptor
>

Validation rules are associated with the data form as a whole via the property ValidationRules of DataForm. It is a dependency property of type IEnumerable<ValidationRule> and can be data bound, set directly in code, or set in Xaml. Here is an example in Xaml:

<dc:DataForm Padding="8,8,8,0" CommitChanges="True" 
               Data="{Binding SelectedCustomer}" ShowSubFields="True">
    <dc:DataForm.ValidationRules>
        <x:ArrayExtension Type="ValidationRule">
            <local:NoOneFromCaliforniaOver40Rule />
        </x:ArrayExtension>
    </dc:DataForm.ValidationRules>           
</dc:DataForm
>

Form level validation rules are applied when a field’s Data property changes and there are no field level errors. When the rule’s Validate method is called, the value of the method’s value parameter will be an object of type ProposedValueCollection. To determine the effective value of a field – that is, the value as it is displayed in the editor – call the GetEffectiveValue method passing the full path to the underlying property relative to the data form’s Data. Following is a C# example of a validation rule which returns an error if the value for “Age” is over 40 and the value for “MainAddress.State” is California:

public class NoOneFromCaliforniaOver40Rule : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        var proposedValues = (ProposedValueCollection)value;
        double age = proposedValues.GetEffectiveValue<double>("Age");
        if (age <= 40)
            return ValidationResult.ValidResult;
        string state = proposedValues.GetEffectiveValue<string>("MainAddress.State");
        if (!state.ToLower().StartsWith("ca"))
            return ValidationResult.ValidResult;
        return new ValidationResult(false, "No person from California over the age of 40 is allowed.");
    }
}

Related posts:

  1. How to use FileDialog to edit a field in a DataForm
  2. WPF AdvGrid Quick Start Guide
  3. PropertyGrid Quick Start Guide
  4. ProgressSteps Quick Start Guide, DotNetBar for WPF
  5. WPF AdvTree Quick Start Guide