Forms: General Guidelines and Best Practices

Applies To: System Center Service Manager 2010

You can extend features of Service Manager by adding or modifying the feature’s forms. This topic describes some best practice recommendations for creating and using Service Manager forms, using various tools and directly scripting form definitions.

This topic is primarily targeted at partners and customers who are experienced in building their own custom forms by using Windows Presentation Foundation (WPF) and Microsoft Visual Studio Team System or Microsoft Expression Blend.

The general guidelines for authoring a new form are as follows. For further information about some of these guidelines, see the topics below:

  • Use standard controls.

  • Follow general form design guidelines.

  • Avoid code-behind.

  • Include exception handling.

  • Consider forms customization and upgrades

  • Name all customizable controls.

  • Bind the form to data sources.

  • Use Service Manager forms infrastructure validation rules, value convertors, and error templates.

  • Use forms infrastructure commands and events.

Use Standard Controls

Controls used on a form can be:

  • Standard controls. This includes .NET library controls, such as combo box and list box.

  • Custom controls. This includes additional controls that are created by the form author or by a third party.

Tip

When you use standard controls wherever possible and avoid creating custom controls, you promote consistency the user experience of forms. If you must create a custom control, separate the visual appearance and behavior and the logical behavior by using control templates to define the appearance of the control. Preferably, there should be a separate control template for each Windows Theme.

Follow General Form Design Guidelines

When you design a form, use public design guidelines to ensure that the form is user friendly and adheres to common user-interaction paradigms.

For more information about general Windows design, see Windows User Experience Interaction Guidelines guide.

In addition:

  • Divide information across multiple tabs to make the form simpler and easier to read. Include the most commonly used information on the first tab and information of lesser importance on subsequent tabs.

  • Use layout panels to lay out controls on the form. This ensures that the form behaves correctly when it is resized and localized.

  • Avoid setting individual control visual properties and use styles instead. This allows you to change the appearance of all controls across a series of forms by modifying the style, and it promotes a consistent appearance across related forms.

Avoid Code-Behind

Limit the usage of code-behind in a form as much as possible. It is preferable that you embed the code for a form in the control itself because later, it is easier in case you need to change that code. Instead, use the declarative capabilities that are supported by the Service Manager forms infrastructure to define value conversions and validation rules in the form.

As a general guideline, you should limit the use of code-behind to the situations where it is not possible to provide the required functionality by using the declarative capabilities of XAML, with classes defined in the Windows Presentation Framework (WPF) and the forms infrastructure library. Even then, consider moving the functionality that is implemented in code-behind into a helper library and then reference it from the XAML.

Include Exception Handling

Ensure that the code in the form contains exception handling so that the form can be loaded both during the design phase in the Authoring Tool and in the Service Manager console at run time.

Consider Forms Customization and Upgrades

When designing a new form, you should consider future customizations and upgrades to that form. To ensure that it is possible to customize and to upgrade a form while preserving customizations, follow the guidelines and tips that are provided earlier in this section, and the guidelines below:

  • Consider future customizations and upgrades early while designing the form. Forms are likely to evolve in future versions and it is important to consider how users will be able to upgrade to new versions of your form while preserving their customizations to the original form. For example, you might provide an updated form after users have already invested heavily in customizing your original form. Users expect their customizations to survive the version upgrade.

  • Provide a unique name for each control on the form to allow customizations to be applied to controls. Form customizations are stored as a set of actions that are targeted at a specific control or a set of controls. The target control is referenced by name, which is why it is important to preserve control names across versions of the form. If a control does not have a name, the Form Customization Editor generates one, but the generated name is not preserved across different versions of the form.

  • Ensure that control names remain immutable across different versions of the form. This ensures that customizations for a given control in a previous version can be applied to the same control in a new version of the form.

  • If possible, avoid moving controls to a different location on the same tab when you upgrade a form. A common user customization is moving controls on the form to a different location. If you change the location of a control in a new version of the form, then there is a risk that the new control location could overlap with a control that the user has relocated.

  • If possible, avoid moving controls between tabs when you are designing an update to an existing form. Controls are identified both by name and by the tab they are located on. Moving a control from one tab to another in a new version of the form can break customizations that the user made to that control, because the customizations will fail to identify the target control.

  • When the update to a form includes new controls, consider adding the new controls to a new tab. That is the safest way to avoid interfering with any user customizations to the existing tabs and controls.

  • Be aware of how controls are bound. Read-only controls should use only one-way bindings.

Bind the Form to Data Sources

The main purpose of a form is to visualize a single object from the Service Manager database. This object is called a target instance, which is always specified via the DataContext property of a form (which is inherited from the FrameworkElement class).

Warning

Do not modify the form’s DataContext property. This property is used by the forms hosting environment to identify the form target instance.

In the Service Manager data model, a target instance is represented as a BindableDataItem object. This class aggregates the underlying SDK object, and exposes its properties through an indexer which takes a property name as a parameter.

The BindableDataItem class also implements ICustomTypeDescriptor, which allows for using BindableDataItem class as a data source for WPF binding. The following is an example of binding a target instance property to the Text property of a TextBox control:

<TextBox Name="textBoxDescription" Text="{Binding Path=Summary}"/> 

It is not necessary to specify the Source of the binding because the target instances are set as the DataContext of the form, which serves as the default Source for all controls on the form.

Controls on the form can be bound to data sources other than the target instance, and the forms infrastructure library contains a number of controls that perform the binding implicitly. For example, the instance picker control is bound to the data source, which provides the collection of instances to choose. It is also possible to define additional data sources declaratively using the ObjectDataProvider and XmlDataProvider classes.

The forms infrastructure considers the target instance as the only read/write data source on the form, so the implementation of the Submit command will only store the changes made to the target instance. Other data sources for the form are treated as read-only.

Use Service Manager Forms Infrastructure Validation Rules, Value Convertors, and Error Templates

It is recommended that you use forms infrastructure validation rules in forms to designate data input that is not valid. The Windows Presentation Framework binding infrastructure supports validation for control properties which are bound to a data source with either one-way or two-way bindings. The binding object has a ValidationRules collection which can contain any number of ValidationRule objects. Whenever data is pushed from the control to the data source, the ValidationRule objects will be called to validate the value.

The forms infrastructure library contains a number of validation rules that handles most common cases. The forms infrastructure takes advantage of the validation rules in order to determine whether the form contents can be submitted for storing. For example, a form’s Submit button can be disabled if there is a control that has a validation error on the form.

It is recommended that you use the custom error template provided with the forms infrastructure library. If a control has a validation error, it appears, by default, with a red border around it. The WPF allows for defining a custom error indicator through the Validation.ErrorTemplate property, which can be set on any control. The Service Manager forms infrastructure library contains a custom error template which displays an error icon instead of the WPF red border. In addition, if the mouse hovers over the error icon, the tooltip pops up with an error message. The error message should indicate the reason the data in the control failed validation.

The following example shows how to reference the error template in XAML:

<TextBox Text="{Binding SomeProperty}"
         scwpf:Validation.ValueRequired="True" 
         Validation.ErrorTemplate="{DynamicResource {ComponentResourceKey {x:Type scwpf:Validation}, InvalidDataErrorTemplate}}"/>

If built-in validation rules do not provide the required validation logic, it is recommended that you build custom validation rules to represent such logic. This will allow standard and custom validation logic to co-exist within the common validation handling mechanism.

If the validation rules mechanism is not adequate for a particular scenario, then you should instead handle FormEvents.PreviewSubmitEvent and run the validation from there.

The following code example provides an example of the pattern that can be used to run custom validation:

void MyForm_Loaded(object sender, RoutedEventArgs e)
{
    // hook to handle form events
    this.AddHandler(
        FormEvents.PreviewSubmitEvent,
        new EventHandler<PreviewFormCommandEventArgs>(this.OnPreviewSubmit));
}
private void OnPreviewSubmit(object sender, PreviewFormCommandEventArgs e)
{
    string errorMessage;
    bool result = this.DoVerify(out errorMessage);
    if (!result)
    {
        // cancel Submit operation
        e.Cancel = true;
        // display error message
        MessageBox.Show(errorMessage);
    }
}
internal bool DoVerify(out string errorMessage)
{
    // Do custom verification and return true to indicate that
    // validation check has passed; otherwise return false and
    // populate errorMessage argument
}

Use Form Infrastructure Commands and Events

The form infrastructure exposes a number of commands that can be run on a form. These commands include:

  • FormsCommand.Submit, which saves the target instance of the form.

  • FormsCommand.SubmitAndClose, which saves the target instance of the form and closes the form.

  • FormsCommand.Refresh, which repeats the query for the target instance of the form.

  • FormCommands.Cancel, which discards all changes and closes the form.

Each of these commands is bracketed by events, which are raised before and after the command runs:

Before the command, the following events are raised:

  • The FormEvents.PreviewSubmit event is raised before the FormCommand.Submit command, and the FormEvents.Submitted event is raised after the FormCommand.Submit command.

  • The FormEvents.PreviewRefresh event is raised before the FormCommands.Refresh command, and the FormCommand.Refreshed command is raised after the FormCommand.Submit command.

  • The FormEvents.PreviewCancel event is raised before the FormCommands.Cancel command, and the FormCommand.Canceled event is raised after the FormCommand.Cancel command.

The preview events pass along a PreviewFormCommandEventArgs object. This object contains a mutable Cancel property that will prevent the corresponding command from running when set to true.

The post-command events pass a FormCommandExecutedEventArgs object. This object contains a Result property which indicates whether running the command succeeded, was canceled, or caused an error. In case of an error, the Error property of the FormCommandExecutedEventArgs object will reference the exception that provides information about the error.

It is possible to enable, disable, and run form commands both programmatically and declaratively.

To enable form commands programmatically, establish a CommandBinding between the form and the related command.

In the following example, a command binding is established between the form and a Refresh command, and two handlers are defined for this command. The first handler returns whether or not the Refresh command can run, and the second handler actually contains the implementation of the Refresh command:

    public class MyForm : UserControl
    {
        public MyForm()
        {
            // do standard initialization
            // establish CommandBinding for Refresh command
            this.CommandBindings.Add(
                new CommandBinding(FormCommands.Refresh, this.ExecuteRefresh, this.CanExecuteRefresh));
        }
        private void CanExecuteRefresh(
              object sender,
              CanExecuteRoutedEventArgs e)
        {
            // put your logic that determines whether Refresh 
// can be executed here
            bool canExecute = true;
            BindableDataItem dataItem = this.DataContext as BindableDataItem;
            if (dataItem)
            {
                canExecute = dataItem["Status"] != "New";
            }
            e.CanExecute = canExecute;
        }
        private void ExecuteRefresh(
            object sender,
            ExecutedRoutedEventArgs e)
        {
            // here is placeholder for the code that has do be 
// executed upon running Refresh command
        }
    }

You can also define handlers for form commands declaratively. You can do this by employing a Rule object that uses a RoutedCommandTrigger. The following code example shows how to define handlers declaratively:

    <scwpf:BusinessLogic.Rules>
        <scwpf:RuleCollection>
            <scwpf:Rule>
                <scwpf:Rule.Triggers>
                    <scwpf:RoutedCommandTrigger 
RoutedCommand="{x:Static scwpf:FormCommands.Refresh}"/>
                </scwpf:Rule.Triggers>
                <scwpf:Rule.Conditions>
                    <scwpf:PropertyMatchCondition 
                        Binding="{Binding Status}" 
                        Value="New" 
                        Operation="NotEquals" />
                </scwpf:Rule.Conditions>
                <!-- Use RuleAction objects to define the logic that executed 
                upon running Refresh command; this can be left empty -->
            </scwpf:Rule>
        </scwpf:RuleCollection>
    </scwpf:BusinessLogic.Rules>

See Also

Concepts

Introduction to the Service Manager Authoring Guide

Other Resources

Windows Presentation Foundation (WPF) Web Site

Did you find this information helpful? Please send your suggestions and comments about System Center Service Manager documentation to scsmdocs@microsoft.com.