Chapter 12. Data controls: DataGrid and DataForm

In chapter 11, we covered binding. I believe binding to be one of the most important topics for Silverlight developers. One reason behind that is because the DataGrid and DataForm, as well as data annotations for display, all require binding in order to be useful.

Silverlight 2 included the DataGrid, and Silverlight 3 added the DataForm to the mix of data-centric controls. The DataGrid provides tabular Excel-like data view and editing. The DataForm is like a DataGrid rotated 90 degrees. Where the DataGrid is all about rows and columns for multiple visible entries, the DataForm is about fields and labels for a single visible entry.

Once we cover the DataGrid and DataForm, we'll see how to annotate properties with simple attributes to control display within the DataGrid and DataForm. We'll cover the related validation attributes in chapter 13.

When used together, the DataForm, DataGrid, and data annotations can form the heart of the user interface for any forms-over-data or business application and can save you a ton of implementation time. Of the three, the DataGrid is the most often used, so we'll start there.

12.1 The DataGrid

The DataGrid is a list-style control that belongs to the System.Windows.Controls namespace. This control provides capabilities for displaying a collection of entities in a tabular format. In addition, it enables users to add, edit, delete, select, and sort items from a binding data source. This data source is bound to a DataGrid through the ItemsSource property just like an ItemsControl, so the data binding features you've seen so far are applicable within the realm of the DataGrid. Before you can bind data to a DataGrid, you must first reference the correct assembly.

The DataGrid control is defined in its own assembly called System.Windows.Controls. Data.dll. This assembly can be found within the Silverlight SDK, which is available at www.silverlight.net. Note that the DataGrid control's assembly isn't part of the default Silverlight runtime installation; it's an extended control, so you must reference the System.Windows.Controls.Data assembly within your Silverlight application. The process of referencing an assembly like this was discussed in section 2.1.2. Part of this process involves choosing a prefix in order to use the control at design time.

Throughout this section, we'll use a prefix called data. Referencing the DataGrid control's assembly will package it up with your application, ensuring that your users can enjoy the power of the DataGrid.

Throughout this section, you'll experience the power of the DataGrid. You'll first see how easy it is to use the DataGrid to display data. From there, you'll learn how to leverage the built-in features to enable a user to edit the data within a DataGrid. Finally, you'll see how to empower your users to sort the data in a DataGrid.

12.1.1 Displaying your data

The DataGrid was designed to make displaying data easy. The easiest way to display data from an ItemsSource is to use the AutoGenerateColumns property. This Boolean property defaults to true, causing the content within the ItemsSource to be rendered in tabular format. This ability is demonstrated in listing 12.1.

Example 12.1. The DataGrid—assume the ItemsSource property is set in code

The DataGrid—assume the ItemsSource property is set in code
The DataGrid—assume the ItemsSource property is set in code

Voilà! This example relies on the System.Windows.Controls.Data assembly to deliver the DataGrid. This Control instance relies on its default behavior to automatically create columns based on the data that it's bound to. This approach is the fastest way to show the data bound to a DataGrid, and it also has some details that are worth examining.

Let's look at what makes a bound column tick. From there, you'll learn how to customize the columns, rows, and headers of a DataGrid.

EXPLORING BOUND COLUMNS

When the AutoGenerateColumns property is set to true, the columns in a DataGrid are automatically ordered and rendered based on the type of the underlying data. Regardless of the type of data, the column type will always derive from the abstract base class DataGridBoundColumn, which serves as the base for the two types shown in table 12.1.

This table shows the kinds of columns that can be automatically generated within a DataGrid. If you want to manually create a column, you can also use these types. But, when you're manually defining your columns, you must set the Binding property, which represents the Binding associated with a column (the property name and the type name are, in fact, the same). Because of this, you can use the Binding syntax explained in chapter 11. This Binding declaration may be necessary because by default, when you use a DataGridBoundColumn, a TwoWay binding is used.

The DataGridBoundColumn is one of the main types of DataGrid columns. The other main type is a DataGridTemplateColumn, which uses a DataTemplate to determine how to render the binding source. Note that every type of column that can be added to a DataGrid derives from the DataGridColumn class, which is used to represent the column of a DataGrid. Objects of this type can be manually added to a DataGrid at design time and managed at runtime.

Table 12.1. The types of columns that can be automatically generated within a DataGrid

Type

Description

DataGridTextColumn

This type of column is used to display textual data. Most of the data from a binding source will be rendered in a DataGridTextColumn through calling the binding property's ToString method. This column type won't show the default Silverlight TextBox, but the rendered content can be edited as if it were in a visible TextBox.

DataGridCheckBoxColumn

This column type generates a CheckBox within a cell. When the AutoGenerateColumns property is true, any bool will be rendered using this column type.

MANUALLY WORKING WITH COLUMNS

The DataGrid can use any column that derives from DataGridColumn. These columns can be added to a DataGrid at design time through the Columns property. This approach is demonstrated in listing 12.2.

Example 12.2. Manually adding columns to a DataGrid

Manually adding columns to a DataGrid

This example shows how to add columns manually to a DataGrid at design time. These columns are added to the Columns property. The items of this read-only collection will be displayed in the order they appear in XAML, but you can change this through the DisplayIndex property.

The DisplayIndex property represents the position of a DataGridColumn in a DataGrid. This zero-based integer can be set at design time to override the default ordering approach. Alternatively, the DisplayIndex property can be set at runtime. This property makes it possible to create a truly dynamic DataGrid, but the dynamic features don't stop there. They also continue at the row level.

CUSTOMIZING THE ROWS

A row within a DataGrid will most likely represent a summarized view of an item. In these situations, it's not unusual to redirect the user to another page to get the details associated with the item, but the DataGrid provides the ability to display these details within the row itself. This approach can reduce the strain of waiting for another page to load for the user. To make this happen, you define the RowDetailsTemplate.

The RowDetailsTemplate is a DataTemplate that can be used to show the finer points of a specific row. This information may be shown if the RowDetailsVisibilityMode property is set accordingly. You'll learn more about that in a moment. For now, just assume that a row will show its details when a user selects it. When this occurs, the DataGrid will reveal the details using a smooth sliding animation. The details can take up as much or as little space as needed. To demonstrate how this works, imagine adding a string property called Keys to the Emoticon class defined earlier in this chapter. This property represents the keyboard shortcut to use for an emoticon. The DataTemplate for revealing this information is shown in listing 12.3.

Example 12.3. Using the RowDetailsTemplate to show the per-item keyboard shortcut

Using the RowDetailsTemplate to show the per-item keyboard shortcut
Using the RowDetailsTemplate to show the per-item keyboard shortcut

This shows how to use the RowDetailsTemplate property. This property uses a DataTemplate to display additional details about a row in a way dependent upon the value of the RowDetailsVisibilityMode property.

The RowDetailsVisibilityMode property determines when the details associated with a row are shown. By default, this property is set to Collapsed, but you can change this value to any option available within the DataGridRowDetailsVisibilityMode enumeration. This enumeration provides three options. All are shown in relation to the DataGrid with the emoticons (table 12.2).

This table shows the options available within the DataGridRowDetailsVisibilityMode enumeration. These options, coupled with the RowDetailsTemplate property, give you the ability to customize the experience with item-level details. The DataGrid extends the same type of power to the column headers.

Table 12.2. The options available within the DataGridRowDetailsVisibilityMode enumeration

Option

Example

Description

Collapsed

The options available within the DataGridRowDetailsVisibilityMode enumeration

When this option is used, the content in the RowDetailsTemplate won't be shown.

Visible

The options available within the DataGridRowDetailsVisibilityMode enumeration

This option forces the content in the RowDetailsTemplate to be shown for every row. The content will be shown regardless of user interaction.

VisibleWhenSelected

The options available within the DataGridRowDetailsVisibilityMode enumeration

This option will show the content in the RowDetailsTemplate for each selected row.

CUSTOMIZING THE HEADERS

The DataGrid gives you the ability to customize every part of it, including the headers. The headers of a DataGrid are split across two separate categories: row and column. By default, your DataGrid will show both, but you can control this by changing the HeadersVisibility property. This property uses one of the options available in the DataGridHeadersVisibility enumeration, which are shown in table 12.3.

Table 12.3. The options available through the DataGridHeadersVisibility enumeration

Option

Example

Description

All

The options available through the DataGridHeadersVisibility enumeration

This option displays both row and column headers. This is the default value.

Column

The options available through the DataGridHeadersVisibility enumeration

This option displays only the column headers.

None

The options available through the DataGridHeadersVisibility enumeration

This option displays neither the row nor column headers.

Row

The options available through the DataGridHeadersVisibility enumeration

This option displays only the row header.

The DataGridHeadersVisibility enumeration is used to set whether a header type is visible. You can also customize what the header looks like and how it behaves through the DataGridColumn class's Header property. This property simply represents the column header content, so it uses the same content-related information you've already learned about.

As you've seen, the DataGrid empowers you to fully customize how your data is presented. These customizations can be applied at the header, row, and column levels. Note that you don't have to make any of these adjustments. If you're looking for a quick way to show your data in a tabular format, you can rely on the fact that the AutoGenerateColumns property defaults to true. Either way, once your data is loaded, you can enable your users to edit the data directly within the grid.

12.1.2 Editing grid data

In addition to presenting data, the DataGrid has the ability to edit data. Users will be able to edit the contents of a DataGrid as long as the IsReadOnly property is set to false. By default it is, so your users have the flexibility to interact with their data in a familiar interface. As they do so, you can watch for the beginning of the editing process through two events. These events are triggered by the DataGrid and are called BeginningEdit and PreparingCellForEdit.

The BeginningEdit event gives you the opportunity to make last-minute adjustments just before users do their thing. In some situations, you may want to prevent a user from editing a cell due to previous inputs. For these occasions, the BeginningCellEdit event exposes a bool Cancel property within its DataGridBeginningEditEventArgs parameter. By setting this property to true, the event will stop running. If the event does complete in its entirety, the PreparingCellForEdit event will also be fired.

The PreparingCellForEdit is fired when the content of a DataGridTemplateColumn enters the editing mode. This event exists to give you the opportunity to override any changes that may have been made in the BeginningEdit event. Once this event and/or the BeginningEdit event have completed without cancellation, users will be given the reins. After they're done editing the data in the DataGrid, they may decide they want to re-sort the data.

12.1.3 Sorting items

The DataGrid has built-in support for sorting collections that implement the IList interface. This interface is a part of the System.Collections namespace and is heavily used throughout the Silverlight .NET framework so you can readily sort almost any collection of objects. If you don't like the way that the DataGrid sorts your collection, you're free to customize the sorting by binding to a collection that implements the ICollectionView interface. Either way, the DataGrid can be used to sort these collections via the SortMemberPath property.

The SortMemberPath property is a string available on the DataGridColumn class, so this property can be used by any of the options shown in table 12.3. Regardless of which option you use, the user will be empowered to sort the column in either ascending or descending order, as demonstrated in listing 12.4.

Example 12.4. Built-in DataGrid sorting

Built-in DataGrid sorting

This snippet shows two DataGridColumn instances enabling the user to sort the underlying ItemsSource. The first DataGridColumn enables the user to sort the Emoticon objects by their Name property. The other DataGridColumn lets the user sort by the Keys property. If the user were to select a column header, it would first be sorted in ascending order. Then, if the column header were to be selected again, it would be sorted in descending order. The SortMemberPath property is what makes this feature of the DataGrid possible.

As you've just seen, the DataGrid has an incredibly rich development model. This model is important because it can help you assist your users in their data entry tasks, which may include editing data or simply viewing it. Either way, the DataGrid provides the ability to efficiently deliver items from a data source in a tabular format.

The DataGrid is great for tabular data, but what do you do when you want similar functionality in a form-based layout model? New in Silverlight 3 and originally considered part of WCF RIA Services (covered in chapter 17), the DataForm is the DataGrid's form-based counterpart.

12.2 The DataForm

Silverlight 3 introduced a control that does for forms what the DataGrid does for lists: the DataForm. The DataForm can be thought of as a single-row DataGrid turned on its side. It shares similar capabilities in that it can be read-only or editable and can infer column names and edit controls based on the types bound to it. Like the DataGrid, it also provides full customization of the representation of each of the bound fields.

Like the DataGrid, the DataForm can work with multiple rows of data. The presentation differs in that you'll see only one row at a time, as is typical in a details form. In fact, the DataGrid and DataForm are sometimes used together to show a master-detail relationship where the DataGrid contains all the records and the DataForm is used to show an editable form for the DataGrid row.

The DataForm was originally developed for Silverlight by the same team that brought us WCF RIA Services. In order to support continued iteration on the control, it was moved from the SDK, where the now-mature DataGrid resides, and placed in the Silverlight Toolkit in the System.Windows.Controls.Data.DataForm.Toolkit assembly. The Silverlight Toolkit can be thought of as an agile extension of the Silverlight SDK, with the bonus that you have access to the source code for the controls and tests it contains.

Though not all applications will use the DataForm, it's much richer and more customizable than it initially appears. Virtually any forms-over-data application can use this control to show a UI that can be as simple as a list of fields or as complex as a customized layout with specific field styles, sizes, and positioning. How it looks is up to you.

In this section, we'll take a tour through the capabilities of the DataForm, starting with a simple binding to a single object and then to a collection of objects. Next, we'll work with the command buttons for canceling updates and submitting data. Once we have the functional mechanics down, we'll customize the display of fields using properties and then using richer data templates for the edit, add, and display modes. The section will wrap up with a discussion of IEditableObject and how that interface can make working with the DataForm even easier.

12.2.1 Displaying your data

The easiest thing to do with the DataForm is to bind it to an object and watch it generate all the fields you need. We'll step away from the Emoticon class for the remaining examples here to show the breadth of controls the DataForm understands. Let's create a new simple Person class, as shown in listing 12.5. (The Required attribute, used in this class, will be covered in chapter 13, which focuses on validation.)

Example 12.5. The Person class

public enum MaritalStatus
{
    Unknown,
    Married,
    Single,
    Divorced
}

public class Person
{
    [Required]
    public string LastName { get; set; }
    [Required]
    public string FirstName { get; set; }
    public bool IsRegistered { get; set; }
    public MaritalStatus MaritalStatus { get; set; }
    public DateTime DateOfBirth { get; set; }
    [Required]
    public string EmailAddress { get; set; }
    [Required]
    public int NumberOfChildren { get; set; }
}

We'll then bind to it in XAML using a static resource, much as we have in other examples. Instead of individual controls, we'll use the entire object as our data source for the new DataForm control, as shown in listing 12.6.

Example 12.6. Binding the DataForm to a single Person object

Binding the DataForm to a single Person object

The property that's used to generate the form is the CurrentItem property, in this sample case bound to a single object sitting in the Resources section of this control. The resulting DataForm, as seen in figure 12.1, is impressive in the breadth of controls it has auto-generated for you. Not only did we not have to write any code specific to the DataForm, but we also didn't have to place any edit controls in the markup.

Note that the DataForm displayed a CheckBox for the bool property, a ComboBox for the enum (populated with all the possible values defined in the enumeration, of course), and a DatePicker for the DateTime property. Not bad for a default form, and certainly workable for a simple utility application.

DataForm showing generated edit controls bound to a single object without a backing collection. Note that there's no toolbar or set of navigation buttons. Not all fields are shown, so your form will look slightly different.

Figure 12.1. DataForm showing generated edit controls bound to a single object without a backing collection. Note that there's no toolbar or set of navigation buttons. Not all fields are shown, so your form will look slightly different.

So far we've been binding one discrete object to the DataForm. To harness the true power of the DataForm, you'll want to bind it to a list of objects much as you would a DataGrid.

12.2.2 Binding to lists of data

If you want to support the ability to add new records, you'll need to provide a place to put them. For this example, we'll create a simple class that holds some dummy data. Of course, you could wire this up to a service to load a collection of Person objects, should you desire.

Listing 12.7 shows a class named PeopleRepository that will hold our Person objects. Note that this doesn't follow the formal Repository pattern; it's closer to a View-Model (covered in chapter 16).

Example 12.7. The PeopleRepository class

The PeopleRepository class

Once you have a suitable repository for the data (whether it's a view-model or something else), one of the easiest things to do is to supply an ObservableCollection<T> to the ItemsSource property, as shown in listing 12.8.

Example 12.8. Binding the DataForm to the PeopleRepository class

Binding the DataForm to the PeopleRepository class
Binding the DataForm to the PeopleRepository class

In the example, I use the PeopleRepository class from listing 12.7 with a collection of Person objects exposed through a property named People. In XAML, I create a resource to hold a reference to that repository and set the DataContext of the DataForm to that StaticResource. (You could, of course, also create the repository and set the DataContext from code.) I then bound the ItemsSource to the collection of Person objects. The resulting DataForm looks like figure 12.2.

A DataForm bound to a collection of objects. Note the presence of the toolbar including the add/remove and navigation buttons, sometimes called the VCR control. (Display annotations, covered in 12.3.1, were used in this example.)

Figure 12.2. A DataForm bound to a collection of objects. Note the presence of the toolbar including the add/remove and navigation buttons, sometimes called the VCR control. (Display annotations, covered in 12.3.1, were used in this example.)

Note the new toolbar at the top of the DataForm. This provides navigation as well as Add (the plus sign) and Delete (the minus sign) capabilities. For each of the operations (Add, Delete, Validate, and so on) appropriate events are raised with the capability to cancel operations based on criteria you set in your code.

The toolbar can be customized both by templating and via the CommandButtonsVisibility property, whose possible values are shown in table 12.4.

Table 12.4. DataForm CommandButtonsVisibility values

Template property

Description

All

Show all buttons.

Add

Show the add new item button.

Cancel

Show the cancel edit button. If the underlying item implements IEditableObject, this calls the CancelEdit function.

Commit

Show the commit edit button. If the underlying item implements IEditableObject, this calls the EndEdit function.

Delete

Show the delete button

Edit

Show the edit button. This button is typically not necessary if AutoEdit is set to true.

Navigation

Show the VCR control navigation buttons.

None

Don't show any command buttons.

Figure 12.3 shows what the toolbar looks like with all of the command buttons visible. The OK and Cancel buttons at the bottom are the commit and cancel buttons. The pencil in the upper right (currently disabled) is the edit button. The other buttons are as described earlier.

The DataForm with all command buttons displayed. The OK and Cancel buttons are the commit and cancel buttons, respectively.

Figure 12.3. The DataForm with all command buttons displayed. The OK and Cancel buttons are the commit and cancel buttons, respectively.

To alter the appearance of the OK and Cancel buttons without retemplating the DataForm, you can use CommitButtonContent and CancelButtonContent to set the contents of the buttons and CommitButtonStyle and CancelButtonStyle to restyle the buttons.

As with just about everything else in Silverlight, you can completely style the way the DataForm looks. You may want to change not only the style but also the field label display and the data type controls.

12.2.3 Customizing display

The DataForm provides multiple levels of UI customization, ranging from how to display field labels and descriptions all the way to providing your own complete DataTemplate for each of the various modes of the control.

CUSTOMIZING FIELD LABELS

In addition to the ability to change the text of the field labels, the DataForm provides the ability to change the position of the labels relative to the edit control. This capability is exposed through the LabelPosition property, the values of which are shown in table 12.5.

Table 12.5. Possible values for LabelPosition and the resulting display

Value

Result

Left

Possible values for LabelPosition and the resulting display

Top

Possible values for LabelPosition and the resulting display

Auto

When there is a parent DataForm, the label position will be inherited. When there's no additional parent DataForm, this value is treated as Left.

The field label provides the primary way you should indicate the expected contents of a field. Should the user require additional information, it may be provided via the field description.

CUSTOMIZING FIELD DESCRIPTIONS

Field description elements are the small icons and related tooltips that typically appear to the right of any control bound to a property that has an associated display description. Later in this chapter, we'll discuss how to set the description text for individual fields on the form using the DisplayAttribute.

The DataForm provides the property DescriptionViewerPosition, which enables you to set the relative location of the description viewer icon. The possible values are described in table 12.6.

Table 12.6. Possible values for DescriptionViewerPosition and the resulting display

Value

Result

BesideContent

Possible values for DescriptionViewerPosition and the resulting display

BesideLabel

Possible values for DescriptionViewerPosition and the resulting display

BesideLabel (with LabelPosition set to Top)

Possible values for DescriptionViewerPosition and the resulting display

Auto

When there is a parent DataForm, the position will be inherited. When there's no additional parent DataForm, this value is treated as BesideContent.

With the label and description covered, we can turn our attention to the field controls and edit them using the three available template properties.

12.2.4 Customizing edit, add, and display templates

The no-code/no-markup out-of-the-box experience is good, but those types of solutions only get us so far before they break down. The DataForm gets us further than most controls but, if you couldn't completely customize the DataForm, we all know it would be a nonstarter for production-ready real-world applications. Luckily, the DataForm supports customization of the associated data templates for the three values of the Mode property: AddNew, Edit, and ReadOnly.

In chapter 11, we covered how to use data templates. The DataForm control provides three places where we can insert our own data templates: the EditTemplate, NewItemTemplate, and ReadOnlyTemplate, all described in table 12.7.

Table 12.7. DataForm templates corresponding to the DataForm mode

Template property

Description

EditTemplate

Corresponds to the Edit value of the Mode property. This template is used when the user or application code puts the form in edit mode or when AutoEdit is true.

NewItemTemplate

Corresponds to the AddNew value of the Mode property. This template is used when the user or application code adds a new item.

ReadOnlyTemplate

Corresponds to the ReadOnly value of the Mode property. This template is used when the current item is read only.

The mechanics of defining the data templates for the three different modes are the same, so we'll concentrate on just the EditTemplate in the following examples.

CREATING THE DATATEMPLATE

Most of us will create our data forms ourselves, with our own aesthetics accounted for. Supplying your own data templates enable you to do a few primary things:

  • You have complete control over the layout of the form and can, therefore, make it look as the designer intended.

  • You can customize the individual field edit control types.

  • You can change the binding characteristics to include your own value converters.

What you give up, of course, is the magic. Unlike the case when you started manually adding fields to the DataGrid, the DataForm provides a nice assortment of capabilities when adding fields. You'll still need to add a field in markup for each field you want on the form but, in reality, this is no more work than we would've had to do if we created the forms without the help of the DataGrid, with some significant savings in application plumbing code.

In a departure from its DataGrid cousin, the DataForm takes a more flexible approach to specifying the individual fields. Rather than have some built-in field types you must choose from or use a template for the remaining, you simply need to wrap edit controls within a DataField content control, as shown in listing 12.9.

Example 12.9. Wrapping the controls in an edit template

Wrapping the controls in an edit template
Wrapping the controls in an edit template

The resulting DataForm edit controls look like figure 12.4. Note that I hard-coded the CheckBox content property to IsRegistered and hid the associated DataField label so that I could demonstrate how to use the built-in CheckBox content property.

Customized EditTemplate showing the CheckBox label to the right of the CheckBox

Figure 12.4. Customized EditTemplate showing the CheckBox label to the right of the CheckBox

The DataField content control has a number of properties that mirror those on the DataForm itself. These are used for controlling where or if the description displays, where to put the field label, and so on. Those may all be set on a field-by-field basis in order to override the DataForm-level settings.

Finally, if you don't want the additional support provided by the DataField control, you can simply omit it and add the TextBlocks and TextBoxes (and other controls) directly to the template and bind them to the appropriate fields, without wrapping in DataField controls.

That's how you control the editing experience at a field and form level. Now let's look at how to control the overall editing and commit experience from a workflow standpoint.

12.2.5 Finer control over editing and committing data

The DataForm and similar controls provide several other settings and hooks that may be used to customize the overall editing workflow. These range from altering the object state based on whether it's about to go into the edit mode or not, how to commit changes, and finally how to manually check if the form includes only valid data.

IEDITABLEOBJECT

System.ComponentModel.IEditableObject is an interface that allows controls such as the DataForm to make method calls into an object when it's about to be edited. Specifically, the interface defines the three methods listed in table 12.8.

Those three functions allow you to control exactly what happens to the object's data when it's put in the edit mode, the edit mode is cancelled, and the edits are committed. Though the sky's the limit with what you might do in these functions, common approaches include versioning, single or multiple-level undo, storing a history of changes, and lazy-loading data required for the edit process.

Table 12.8. IEditableObject interface

Template property

Description

BeginEdit

Called when the object is put into the edit mode

This is where you may want to cache undo information. If the DataForm's AutoEdit property is set to true, this is called as soon as an edit field receives focus. Otherwise, it's called as a result of the user clicking the edit button on the toolbar.

CancelEdit

Called when the object was previously in the edit mode but now is to be put back into the read-only mode, reverting any changes

On the DataForm, this is called when the user clicks the Cancel button.

EndEdit

Called when the edit is complete and the changes should be committed

If the DataForm's AutoCommit property is set to true, this will happen when the user navigates off the item and the item is both dirty and valid.

The Silverlight DataForm control respects these methods and calls them at the appropriate times if your class implements the IEditableObject interface. Another place where the DataForm allows customization in the object workflow is in checking the item state.

CHECKING FOR DIRTY STATE AND VALIDITY

The DataForm also provides a way to check the object's changed state, often called its dirty state, from within the DataForm itself. This doesn't require any dirty tracking infrastructure in place within the entity being edited; the DataForm takes care of all of that. To check whether the current item has been changed, simply refer to the IsItemChanged property as follows:

if (dataForm.IsItemChanged) {...}

If the item has changed, you'll probably be interested in knowing if it's valid. Luckily, the DataForm provides a property for that as well: the IsItemValid property. This property returns true if the currently edited item, the item visible on the DataForm, has met all associated validation rules (covered in chapter 13). The syntax for the read-only IsItemValid property is as shown here:

if (dataForm.IsItemValid) {...}

The DataForm by itself is a nice way to handle data entry in your applications. Without sacrificing the capabilities it offers, you have a great deal of control over how the content is rendered and how navigation is handled. The DataForm will help do for forms what the DataGrid did for tabular data.

The DataGrid and DataForm are two powerful controls for displaying and editing data. If you're writing a line-of-business application or something that's otherwise very data heavy, I strongly recommend you consider using these controls. Both provide commonly understood UI metaphors in an easy-to-use package. You can do pretty much anything you'd need to do with them more efficiently than writing analogous controls from scratch, especially when you consider the annotations we'll cover in the next section.

Now that we've seen how to edit data in the DataGrid and DataForm, we'll want to impose some parameters around how the data is displayed. We can do this in code, but it's typically more efficient to use data annotations.

12.3 Annotating for display

The DataForm and DataGrid both offer the ability to set the properties of columns and labels, including things such as the display caption and tooltips. But, if you share the data between many instances of the controls, instances which may vary in their display properties in other ways, it can be both tedious and a maintenance burden to have to repeat this configuration in multiple places.

Autogeneration of columns and labels often leaves us with even uglier results. Sadly, many internal applications go into production with programmer-friendly but user-unfriendly display properties because it was too much effort to keep the UI updated and in sync with the data model.

The System.ComponentModel.DataAnnotations assembly and namespace found in the Silverlight SDK provide a number of attributes designed to make data validation and display hinting easier for controls such as the DataForm, DataGrid, and some third-party controls. The approach taken by these attributes is to mark up the properties in your entities using attributes in code rather than require code within the properties or external to your entities.

The two main attributes that we'll cover here are Display and Editable. In chapter 13, we'll take a look at the validation attributes and how they can further enhance the DataGrid and DataForm.

12.3.1 The Display attribute

Both the DataGrid and the DataForm provide the capability to automatically generate display and edit controls, and associated labels or column headers, at runtime. Though the controls themselves provide a number of ways to customize the field information, there are times when you'd be better served by a centralized definition of that metadata.

One way to centralize that metadata is to annotate the properties on the entities themselves. The assembly System.ComponentModel.DataAnnotations provides a number of attributes designed specifically for this purpose (see listing 12.10).

Example 12.10. The Person class with Display attributes attached

public class Person
{
...
  [Display(Name = "Registered",
    Description = "Check if this person has registered with us.")]
  public bool IsRegistered { get; set; }
  [Display(Name = "Marital Status",
    Description = "Optional marital status information.")]
  public MaritalStatus MaritalStatus { get; set; }
...
}

The result of including the Display attribute with the name and description can be seen in figure 12.5. Note that the MaritalStatus field has its correctly formatted display name shown in the label, and the Registered field shows the information icon with the associated tooltip containing the description property.

Portion of a DataForm showing the tooltips with the Description property of the Display attribute and the field captions pulled from the Name property of the same attribute.

Figure 12.5. Portion of a DataForm showing the tooltips with the Description property of the Display attribute and the field captions pulled from the Name property of the same attribute.

The DisplayAttribute enables us to control a number of different aspects of the onscreen representation of the control, above and beyond just the field label and the tooltip. It can control whether the field is automatically generated as a column in the DataGrid or field in the DataForm. It can also control the order the fields are displayed in or information on the string to use when localizing. Table 12.9 has the complete list of the different properties available.

Table 12.9. DisplayAttribute properties and their uses

Property

Description

AutoGenerateField

Set this value to false if you don't want controls like the DataForm to automatically generate a control for this property.

AutoGenerateFilter

Set to true if you want the filtering UI automatically displayed for this field. It is currently unused by the DataGrid and DataForm controls.

Description

A resource name or regular text that will be displayed by the rendering control. In the case of the DataForm, this shows up in a tooltip over the information icon.

GroupName

A resource name or regular text to display as the heading for a group of related fields. Currently unused by the DataGrid and DataForm controls.

Name

A resource name or regular text to display as the name of this field. This is typically used in field labels.

Order

Relative order for this field in display. By default, fields are displayed in the order they're defined in the class. This property allows you to override that behavior.

Prompt

Specifies a prompt, such as a watermark, to use when displaying this field. Currently unused by the DataGrid and DataForm controls.

ResourceType

If you intend to use localized resources, specify the type container for those resources here.

ShortName

A resource name or regular text to display as the name of this field. This is typically used in column headers.

In several instances in table 12.9, I wrote that a property is "typically used as" something or other. The DisplayAttribute simply contains data; it doesn't provide behavior or any enforcement of proper use. It's up to the consuming control—typically a DataGrid, DataForm or a third-party control—to decide how that data will be used.

In addition to the DisplayAttribute and its properties, one final important attribute-based setting you have as a developer is the ability to mark individual properties as editable or read only.

12.3.2 The Editable attribute

On occasion, you may want to designate certain properties as read only from a UI point of view but still allow them to be manipulated via code. One way to handle that is to provide an accessor (property get) with no corresponding property set and then provide an explicit mutator method. Unfortunately, that makes the programming interface more cumbersome.

Another approach is to provide a normal property getter and setter but mark the property as read only at the UI level. Like the Display attribute discussed in the previous section, you could certainly do this on a form-by-form basis. But you may want to instead centralize this information on the entity itself, as shown in listing 12.11.

Example 12.11. Controlling editability using the Editable attribute

Controlling editability using the Editable attribute

In this example, we've marked the Marital Status field as read only by applying an EditableAttribute with the editable flag set to false. The result will be an onscreen field that's disabled, as shown in figure 12.6.

Figure 12.6 shows the Marital Status field disabled. Note also that its information icon isn't displayed, even though we've included a display description. The display of the field control itself will depend upon the disabled state for the control in use. This is something that may be easily changed by editing the control template as shown in chapter 23.

The Marital Status field has been disabled because its underlying property is marked as read only.

Figure 12.6. The Marital Status field has been disabled because its underlying property is marked as read only.

There is a number of other attributes in the DataAnnotations namespace, including some specifically geared toward object-relational mapping (ORM). Take a look around in there and you may find other attributes that can help with specific challenges you're facing in your applications.

Annotations are a powerful way to provide metadata for your entities. When combined with annotation-aware controls like the DataForm and DataGrid, you can provide UI information such as field labels and help text, and control whether fields are editable on forms and in grids. WCF RIA Services, covered in chapter 17, provides other options for surfacing this metadata.

12.4 Summary

For business and forms-over-data applications, the DataGrid and DataForm often form the heart of the UI. Even nontraditional applications sometimes use heavily styled DataGrids due to their rich programming model. Though the controls are complex, they're equally powerful and worth the time it takes to master them.

The DataGrid is great for tabular data, whether it's flat grid-style or contains images or richer content. There are lots of options for the types of content it contains as well as how you style both the content and the columns and rows that contain it. When you need to display and edit data in a tabular form, look first to the DataGrid.

The DataForm is the 90-degrees-off equivalent of the DataGrid, with columns shown as fields rather than columns. Like the DataGrid, it can work on multiple rows of data but, unlike the DataGrid, it shows only one at a time. Though certainly useful on its own, the DataForm is often combined with the DataGrid for master-detail layouts.

The DataGrid and DataForm both understand the display annotations such as the Display and Editable attributes. These allow you to mark up your classes or buddy partial classes to control the rendering of the controls in the grid and the form.

Now that we've covered binding (chapter 11) and the DataGrid and DataForm, we'll move on to validation. When you combine the grid, form, binding, and validation, you'll be well on your way to having a set of tools that cover the majority of the data manipulation needs of a business application UI.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.97.53