Creating the Summary List

In Chapter 5 we looked at how you can consume data from the server. Now it's time to display that data to the user. The most commonly used controls for presenting a summary list to the user are the DataGrid control and the ListBox control, so we will investigate how to configure both these controls, and compare and contrast the two so you can determine the most appropriate control to use for your purposes.

Using the DataGrid Control

Configuring a DataGrid control essentially involves setting up the columns to be displayed. If you happen to have presentation layer information, using the Display attribute discussed in Chapter 4, in the metadata of the entity being displayed, then simply assigning the collection to the ItemsSource property of the DataGrid and setting the AutoGenerateColumns property to True (its default value) is all that is necessary. However, assuming that you don't have this metadata available (as having presentation layer information in the middle tier isn't generally recommended), you will need to manually configure the columns to be displayed. Let's look at how to produce the output shown in Figure 6-2 using a DataGrid control.

images

Figure 6-2. A simple summary list displayed in a DataGrid control

To use the DataGrid control, you will need to add a reference to the System.Windows.Controls.Data.dll assembly to your project, and declare the sdk namespace prefix in your views, like so:

xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"

images Note If you want to use a DataGrid in your application, but the native DataGrid doesn't serve your needs, there are a number of third-party DataGrid controls available that you could use instead, from vendors such as Telerik, ComponentOne, Infragistics, ComponentArt, DevExpress, and Syncfusion.

Populating a DataGrid Control with Data

The DataGrid control has an ItemsSource property that you can bind to a collection of objects, or directly assign a collection of objects. For example, you can bind the DataGrid control's ItemsSource property to the Data property of a DomainDataSource control using ElementName binding. ElementName binding is covered in Chapter 11, but in summary, it binds the value of one control's property to the value of another control's property. In the following XAML, the ItemsSource property of a DataGrid control is being bound to the Data property of a DomainDataSource control named productSummaryDDS:

<sdk:DataGrid Name="productsDataGrid"
              ItemsSource="{Binding ElementName=productSummaryDDS, Path=Data}" />

Alternatively, you can simply assign a collection to the control's ItemsSource property in the code-behind, where collection is a collection of objects to be displayed, like so:

productsDataGrid.ItemsSource = collection;

Configuring DataGrid Columns

Each column will need to be defined on the Columns property of the DataGrid. The following three different column types are available for the DataGrid control in Silverlight:

  • DataGridTextColumn: This is the most commonly used column type in summary list scenarios. In read-only mode it shows the bound property value in a TextBlock, and in edit mode it shows it in a TextBox.
  • DataGridCheckBoxColumn: This column type is used for showing Boolean value. It displays the bound property value in a CheckBox in both read-only and edit modes. (It simply disables it when it's in read-only mode.)
  • DataGridTemplateColumn: This column type is for use when neither of the other two column types are appropriate. Essentially, you can configure this column to work the way you want by defining a cell template for the column (providing the CellTemplate property with a data template), and if you wish, an alternative cell-editing template (providing the CellEditingTemplate property with a data template). In other words, you can define two templates for a column—one for read-only scenarios, and one for editing scenarios. If only one of these templates is provided, it will be used in both scenarios. In these cell templates you can define the control(s) that you want displayed, and their layout if required, providing practically limitless possibilities for a cell's contents.

images Note Data templates are defined and briefly described in Chapter 2. We'll discuss data templates further shortly, when we look at the ListBox control.

The following example XAML demonstrates a simple DataGrid configuration with three columns, each demonstrating one of the preceding column types. Each column has a binding to a property on the entity, and assigns the text to be displayed in the column header. Note that both a cell template and an edit template are being defined for the quantityAvailableColumn column—using a TextBlock control to display the data for read-only scenarios, and a NumericUpDown control to enable the value to be altered in edit mode.

<sdk:DataGrid AutoGenerateColumns="False">
    <sdk:DataGrid.Columns>
        <sdk:DataGridTextColumn Binding="{Binding Name}" Header="Name" />

        <sdk:DataGridTemplateColumn Header="Qty Available">
            <sdk:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding QuantityAvailable}" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellTemplate>
            <sdk:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox
                        Text="{Binding QuantityAvailable, Mode=TwoWay}" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellEditingTemplate>
        </sdk:DataGridTemplateColumn>

        <sdk:DataGridCheckBoxColumn Binding="{Binding MakeFlag}"
                                    Header="Made In-House" />
    </sdk:DataGrid.Columns>
</sdk:DataGrid>

images Note When you bind a collection to the ItemsSource property of a DataGrid or ListBox control, the control will create a row/item for each item in the collection, and that row/item will have its DataContext property bound to the corresponding entity from the collection.

Creating Column Definitions the Easy Way

There is a trick that can save you time when manually configuring the columns of a DataGrid control. In Chapter 5 we looked at how you could drag an entity from the Data Sources window in Visual Studio onto a view, and a list or edit screen will be generated for you and wired up to a DomainDataSource control. Even if you have chosen not to use the DomainDataSource control to consume data in the view, you can still use this approach to generate a DataGrid control, with a column definition for each property on the entity created for you. You can then customize and reorganize/delete the columns that it defined for you to your heart's content, saving you from having to configure each of these manually. If you don't want to use the DomainDataSource control that was also created, simply delete it from the XAML, and delete the binding to it that was assigned to the ItemsSource property of the DataGrid control.

In addition, this approach can save you much time when you are using template columns to display/edit various properties on the entity, where the control to display/edit the value isn't available as a part of a standard DataGrid column—that is, DataGridTextColumn or DataGridCheckBoxColumn. For example, you might want a numeric property to be able to be edited using a NumericUpDown control instead of a standard TextBox control. Select the control that should be used to edit the data for a property in the Data Sources window from the drop-down menu that appears when the property is selected. If the control doesn't appear in the list, click the Customize menu item and select the control from the list of all available controls. Remember to also select the data type that the control should appear for! Selecting this control type for a property will automatically create a template column for that property in the DataGrid that is created, with a preconfigured cell template containing that control bound to the property.

images Note Often, you will want the last column in the DataGrid to simply fill the remaining space, especially in summary list–type scenarios. You can achieve this by simply setting the Width property of the column to *.

Displaying an Image in a Column

We'd also like to display an image of each product against the corresponding item in the DataGrid. This is another scenario in which a template column can be used, with a bound Image control in the cell template. However, properties that expose images on your entity will, in most cases, be represented by a simple byte array (byte[]). Unfortunately, the Image control cannot accept a byte array, and cannot be bound to it. The Image control can accept a BitmapImage, so we need to find a way to convert the byte array to a BitmapImage so it can be bound. There are a number of ways to achieve this, including the following:

  • Defining a partial class for your entity, and creating a property that converts and exposes the byte array as a BitmapImage, which you can bind to
  • Creating your own Image control that can accept an image as a byte array
  • Creating a value converter that can be used as a part of a binding to convert the bound value before it is applied to the property of the control

All of these are viable solutions, and have their own pros and cons. As a general rule, using a value converter is the better option. Value converters are a powerful feature of the binding mechanism in Silverlight, enabling the properties of two objects to be bound together, even if their types don't match. The value converter sits in the middle of the binding, translating the value of the source property and returning the result so that it can be applied to the destination property, and vice versa. You simply design a class that implements the IValueConverter interface, and insert the logic to translate one value to another—in this case, a byte array to a BitmapImage. Value converters are covered in full in Chapter 11, so we won't go into details here, but in summary, you can use a value converter to convert the byte array to a BitmapImage, which the Image control can understand, so that it can display a thumbnail image for the product.

images Note When it comes to exposing images from the AdventureWorks database, unfortunately we hit a small hurdle. The images are stored in the database as GIFs—an image format not actually supported by Silverlight. Therefore, we need to either convert them to an image format Silverlight supports on the server before sending them to the client (such as PNG or JPG), or find a way to do so on the client. Converting images for each item on the server when the client requests a summary list would put an excessive load on the server and reduce the scalability of your application. Therefore, it would be better to leave that task to the clients. Luckily, there is a CodePlex project called ImageTools for Silverlight (http://imagetools.codeplex.com) that provides a GIF decoder that you can use. This library is used in the sample project by calling it in our value converter, used when binding the image property to an Image control in XAML, to convert the byte array returned from the server to a BitmapImage. The GIF decoder from the library is used to decode the byte array from the GIF format, which is then reencoded using the PNG encoder from the library to a BitmapImage, which can then be bound to an Image control. This conversion process can be performed in the value converter, which will read in the byte array, convert the image to a PNG, and then assign it to a BitmapImage, which it will then return. Since this is a rather obscure implementation detail, we won't go into details of the conversion process here. However, you'll find code that does this in the code accompanying this book, which is available from the Source Code/Download area of the Apress web site (www.apress.com).

The following code demonstrates defining a template column containing an Image control whose Source property is bound to the ThumbnailPhoto property of the ProductSummary object. Note how the Converter property of the binding is assigned a custom value converter called GifConverter, which is defined as a resource higher up in the object hierarchy. This is the converter being used to handle the translation of a GIF image in a byte array to a PNG image in a BitmapImage object.

<sdk:DataGridTemplateColumn>
    <sdk:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Image Margin="2"
                   Source="{Binding ThumbnailPhoto,
                                    Converter={StaticResource GifConverter}}" />
        </DataTemplate>
    </sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>

Creating Calculated Columns

You can use the same value converter technique to implement a calculated column. Bind the property to your whole object so that the whole object is passed into the value converter, and then you can calculate a value to display in that column from the property values on the object; for example, total amount = quantity × price, where total amount is the calculated value, and the quantity and price values are retrieved from the object. However, since this is business logic rather than user interface logic, you are probably best off defining a partial class for that object and creating a property that performs the calculation there instead, which can then be bound to.

Editing Data in the DataGrid

The DataGrid control is primarily designed for editing data, and thus, grid cells are in edit mode by default. However, you can disable the editing behavior to use the control more like a list by setting the IsReadOnly property of the DataGrid to True. We'll look at the DataGrid control's editing behavior in Chapter 7.

images Note Being primarily designed for editing data, by default the DataGrid control uses a cell selection–type behavior, whereas in a summary list–type scenario, you really want the selection behavior to involve the full row. You'll find that when a cell is selected, the full row does have a selected appearance, but the cell has an additional (and quite prominent) selection square around it. There is no easy way to hide this, unfortunately, but you can use Silverlight's styling support to hide it yourself. This process involves applying a custom style to the CellStyle property of the DataGrid and retemplating the DataGridCell control to hide the rectangle (named “FocusVisual”) that it incorporates. Retemplating a control using a control template is covered in Chapter 9.

Additional Built-In Behaviors

When you use the DataGrid, you get a lot of additional built-in features and behaviors “for free,” which is one of the primary reasons that the DataGrid control has such a wide appeal to many developers. Some of these features are listed in the following subsections.

Sorting

Simply click the header of a column to sort the DataGrid by its values, and click it again to reverse the sorting. This feature is automatically turned on, but can be turned off by setting the CanUserSortColumns property of the DataGrid to False. It can also be toggled off/on selectively by setting the appropriate value to the CanUserSort property of a column.

Grouping

One of the most powerful features of Silverlight's DataGrid control is its ability to display grouped data. Most summary lists in business applications have the need to group data, and the DataGrid control has this ability built in. This feature alone makes Silverlight's DataGrid more powerful and functional than that available natively in any other Microsoft platform. Multiple levels of grouping can be displayed, meaning that you can group items by category and then by subcategory, for example.

images Note Grouping cannot actually be configured directly on the DataGrid control itself. Instead, you must bind it to a collection view and configure the grouping on it. This is discussed further in the section “Workshop: Grouping the Summary List,” later in this chapter.

Resizing of Columns

Users can increase or decrease the size of a column by clicking and dragging the right edge of the column header to the size they want. They can also double-click the right edge of the column header to make the column automatically size itself to a width at which all its content is visible. This feature is automatically turned on, but can be turned off by setting the CanUserResizeColumns property of the DataGrid to False. It can also be set on specific columns using their CanUserResize property.

Reordering of Columns

Users can rearrange the columns to their liking by dragging a column header to a new position. This feature is automatically turned on, but can be turned off by setting the CanUserReorderColumns property of the DataGrid to False. It can also be set on specific columns using their CanUserReorder property.

Displaying Additional Row Details

The DataGrid has a RowDetailsTemplate property that enables you to define a data template containing a layout of additional details you want to display about a row beneath the main row. For example, you might want to display only some of the details on the entity in the columns, and then display more details when the row is selected. You can even have quite a complex layout in which selecting a row displays another DataGrid below it showing a list of related items (e.g., the line items on an invoice). You can choose to have the row details displayed for each row automatically, display the details for a row only when it is selected, or only display them manually, via the RowDetailsVisibilityMode property on the DataGrid. We'll look at this feature further in the section “Displaying Details Using the DataGrid's Row Details,” later in the chapter.

Validation Support

When the DataGrid is in edit mode, any validation rules you've defined in your entity's metadata will be run over the entered values, and a validation rule failure message will appear so the user knows what the problem is so that they can fix the issue. We'll look at implementing validation rules in Chapter 7.

Images Workshop: Configuring a DataGrid Control for Displaying a Summary List

In this workshop, we'll create a DataGrid control that displays columns for the following three properties when bound to a ProductSummary entity (which we created and exposed from the server in Chapter 4): Name, QuantityAvailable, and MakeFlag. This will demonstrate each of the three types of DataGrid columns: DataGridTextColumn, DataGridTemplateColumn, and DataGridCheckBoxColumn. For now, we'll merely define it, and look at approaches to populating it with data later, in the section “Populating a Summary List with Data.” When it's populated with data, you will have a DataGrid control that looks the same as that shown in Figure 6-2.

  1. In a previous workshop, we created a view named ProductListView.xaml. Delete this view, and re-create it so that you start afresh.
  2. Add a reference to the System.Windows.Controls.Data.dll assembly to your project.
  3. Define the sdk namespace prefix in the root element of this XAML file, as follows:
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
  4. Now define the DataGrid control, like so:
    <sdk:DataGrid AutoGenerateColumns="False">
        <sdk:DataGrid.Columns>
            <sdk:DataGridTextColumn Binding="{Binding Name}" Header="Name" />

            <sdk:DataGridTemplateColumn Header="Qty Available">
                <sdk:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding QuantityAvailable}" />
                    </DataTemplate>
                </sdk:DataGridTemplateColumn.CellTemplate>
            </sdk:DataGridTemplateColumn>

            <sdk:DataGridCheckBoxColumn Binding="{Binding MakeFlag}"
                                        Header="Made In-House" />
        </sdk:DataGrid.Columns>
    </sdk:DataGrid>
  5. To populate the DataGrid control with data, you will need to bind its ItemsSource property to a source of data. We'll look at how you do this shortly.

Using the ListBox Control

One of the big advantages of using a ListBox control over a DataGrid is its flexibility. By default, a ListBox simply displays a line of text for each item in the list, as shown in Figure 6-3.

images

Figure 6-3. A ListBox control (with no customizations)

Despite this apparent simplicity, the ListBox control is actually quite a versatile control, with much more potential than you might initially assume. Let's take a look at the techniques you can use to customize the ListBox control to your needs.

Populating a ListBox Control with Data

Like the DataGrid control, the ListBox control has an ItemsSource property that you can bind to a collection of objects, or directly assign a collection of objects. You use this property in exactly the same manner as you do with the DataGrid control. For example, you can bind its ItemsSource property to the Data property of a DomainDataSource control, like so:

<ListBox Name="productsList"
         ItemsSource="{Binding ElementName=productSummaryDDS, Path=Data}" />

Or you can simply assign a collection to the control's ItemsSource property in the code-behind, where collection is a collection of objects to be displayed, like so:

productsList.ItemsSource = collection;

images Note Unlike the DataGrid control, the ListBox control has an Items property, exposing a collection that you can explicitly add/remove items to in the code-behind, assuming the control's ItemsSource property is not bound to a collection. However, this is rarely used in Silverlight, as binding a collection to the control's ItemsSource property is the preferred approach for populating a ListBox control. As you'll learn shortly, the ListBox control automatically listens for the CollectionChanged event on bound collections that implement the INotifyCollectionChanged interface (such as the ObservableCollection<T> type). As items are added to or removed from the bound collection, the CollectionChanged event will be raised, and the ListBox control will automatically update itself accordingly.

Templating List Items

A data template enables you to define a custom way in which data should be displayed in a control. The ListBox control allows you to define a data template that will be applied to each item, enabling you to completely customize the appearance of items in the list. To do this, simply define a data template and assign it to the ItemTemplate property of the ListBox. For example, take the following data template:

<DataTemplate>
    <Grid Height="50">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Image Margin="2"
               Source="{Binding ThumbnailPhoto,
                                Converter={StaticResource GifConverter}}"
               Grid.RowSpan="2" />
        <TextBlock Name="NameField" Text="{Binding Name}" Margin="2"
                   Grid.Row="0" Grid.Column="1" FontWeight="Bold" FontSize="12" />

        <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1">
            <TextBlock Text="Number:" Margin="2"  />
            <TextBlock Text="{Binding Number}" Margin="2"  />
            <TextBlock Text="| Available:" Margin="2"  />
            <TextBlock Text="{Binding QuantityAvailable}" Margin="2"  />
            <TextBlock Text="| Price:" Margin="2"  />
            <TextBlock Text="{Binding ListPrice, StringFormat=C}" Margin="2" />
        </StackPanel>
    </Grid>
</DataTemplate>

Applying this data template to the ItemTemplate property of a ListBox, like so:

<ListBox ItemsSource="{Binding ElementName=productSummaryDDS, Path=Data}">
    <ListBox.ItemTemplate>
        <!-- Put data template XAML here -->
    </ListBox.ItemTemplate>
</ListBox>

results in the output shown in Figure 6-4.

images

Figure 6-4. A ListBox control (with a custom item template)

As you can see, this enables you to provide a very customized view of the data, that is both attractive and highlights the important details of each item.

Implicit Data Templates

Implicit data templates are a new feature in Silverlight 5. Previously, you could define only a single data template that would be applied to all items in the list. However, the source collection may consist of different object types, and you might want to display the items in the list differently, with each item using a data template according to the type of object it is bound to. Say, for example, that you have an ErrorLog collection, of type ObservableCollection<LogMessage>. (We'll introduce you to the ObservableCollection<T> type shortly, but it's essentially a standard collection type with some additional behavior added.) For this example, the LogMessage class will simply have a single property: Message, of type string. You also have three classes, each inheriting from LogMessage: ErrorLogMessage, WarningLogMessage, and InformationLogMessage. The ErrorLog collection is populated with instances of each of these classes, each representing a message added to the log; the type representing the severity of the message. When you populate a ListBox control with this collection, it would be nice for each item to use a custom template according to the object type that it's bound to. You might want to highlight the error message items with a red background so that they stand out, and have each item display an icon representing the severity of the message being represented. This behavior is easy to implement using implicit data templates. Essentially, you can define a separate data template for each type of object that will appear in the ErrorLog collection.

The following XAML demonstrates the scenario just detailed. Here, we have a ListBox control, which has three data templates defined as resources. Each data template has its DataType property set (in bold) to the type of object that it will be applied to. Unlike standard data templates, implicit data templates are not explicitly assigned to the ListBox control's ItemTemplate property. Instead, when the ListBox control is populated, it will automatically search for a DataTemplate resource within its scope for each item in the list. It will search for a DataTemplate resource whose DataType property corresponds to the type of object the item is being bound to. If it finds one, then it will apply that data template to the item.

<ListBox ItemsSource="{Binding ErrorLog}">
    <ListBox.Resources>
        <DataTemplate DataType="models:ErrorLogMessage">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="35" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Image Source="/ImplicitDataTemplateSample;component/Images/Error.png"
                       Stretch="None" />
                <TextBlock Text="{Binding Message}" Grid.Column="1"
                           VerticalAlignment="Center" />
            </Grid>
        </DataTemplate>

        <DataTemplate DataType="models:WarningLogMessage">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="35" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Image Source="/ImplicitDataTemplateSample;component/Images/Warning.png"
                       Stretch="None" />
                <TextBlock Text="{Binding Message}" Grid.Column="1"
                           VerticalAlignment="Center" />
            </Grid>
        </DataTemplate>

        <DataTemplate DataType="models:InformationLogMessage">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="35" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Image Source="/ImplicitDataTemplateSample;component/Images/Information.png"
                       Stretch="None" />
                <TextBlock Text="{Binding Message}" Grid.Column="1"
                           VerticalAlignment="Center" />
            </Grid>
        </DataTemplate>
    </ListBox.Resources>
</ListBox>

images Note In the preceding example, we define the data templates as resources on the ListBox control. Note, however, that they can be defined as resources further up the hierarchy instead if you wish (such as at the view, or even application level), making them available to all the ListBox controls or to any other controls that support data templating.

A sample application putting this example into action can be found in the code accompanying this book, available from the Source Code/Download area of the Apress web site (www.apress.com).

Customizing the Layout of the Items

Items don't have to appear vertically in a list when using the ListBox. For example, you may choose to have items appear in a horizontal layout instead, or implement a thumbnail for each item in the list (as you find in Windows Explorer), or even display items in the form of contact cards. You can achieve these effects by assigning a new items panel template to the ItemsPanel property of the ListBox. This property enables you to customize how the items are laid out in the ListBox.

Let's take a look at how to implement the contact card–type layout. For this scenario, we want the items to stack horizontally until they reach the right edge of the ListBox, at which point they should wrap around to the next line. This is a scenario that the WrapPanel control from the Silverlight Toolkit is ideally suited for, as it was designed specifically to achieve this effect. First, you declare the toolkit namespace prefix in the root element in your XAML file, like so:

xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"

Then, simply assign this to the ItemsPanelTemplate property of the ListBox's ItemsPanel property. For example, apply this template to the ItemsPanel property of the ListBox:

<ItemsPanelTemplate>
    <toolkit:WrapPanel />
</ItemsPanelTemplate>

Then, apply this data template to the ItemTemplate property:

<DataTemplate>
    <Grid Width="270" Margin="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Rectangle Stroke="Black" RadiusX="3" RadiusY="3"
                   Grid.RowSpan="5" Grid.ColumnSpan="2">
            <Rectangle.Fill>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Color="White" Offset="0" />
                    <GradientStop Color="#6D6D6D" Offset="1" />
                </LinearGradientBrush>
            </Rectangle.Fill>
            <Rectangle.Effect>
                <DropShadowEffect ShadowDepth="2" />
            </Rectangle.Effect>
        </Rectangle>

        <Border BorderBrush="Black" Background="White" Margin="8" Height="70"
                VerticalAlignment="Top" CornerRadius="3" Grid.RowSpan="5">
            <Border.Effect>
                <DropShadowEffect ShadowDepth="2" />
            </Border.Effect>
            <Image Source="{Binding ThumbnailPhoto,
                                    Converter={StaticResource GifConverter}}"
                   VerticalAlignment="Center" HorizontalAlignment="Center" />
        </Border>

        <TextBlock Name="NameField" Text="{Binding Name}" Margin="2,8,2,2"
                   Grid.Row="0" Grid.Column="1" FontWeight="Bold" FontSize="12" />

        <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1">
            <TextBlock Text="Number:" Margin="2" />
            <TextBlock Text="{Binding Number}" Margin="2" />
        </StackPanel>

        <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1">
            <TextBlock Text="Available:" Margin="2" />
            <TextBlock Text="{Binding QuantityAvailable}" Margin="2" />
        </StackPanel>

        <StackPanel Orientation="Horizontal" Grid.Row="3" Grid.Column="1">
            <TextBlock Text="Price:" Margin="2" />
            <TextBlock Text="{Binding ListPrice, StringFormat=C}" Margin="2" />
        </StackPanel>
    </Grid>
</DataTemplate>

Finally, setting the ScrollViewer.HorizontalScrollBarVisibility property on the ListBox to Disabled, disabling its horizontal scroll bar so that the items will wrap, results in the output shown in Figure 6-5.

images

Figure 6-5. A ListBox control (with a custom item panel and item template)

Limitations of the ListBox Control

Despite all its flexibility, the ListBox control does have its limitations. One big issue comes when you want to group the items in the list. Unlike the DataGrid control, the ListBox unfortunately doesn't have any built-in grouping functionality. Data can be grouped within the ListBox; however, there is no support for displaying group headers. You could attempt to implement group headers yourself, but this would require quite a bit of code and could become a messy solution. If you require grouping in your summary list, you could potentially add some custom styling and behavior to the TreeView control, to make it look and behave like a list, as it is fundamentally designed to support hierarchical data, which grouped data could be considered to be. However, a fair amount of manual effort is still required to achieve the correct effect. Therefore, if you need to display grouped data beneath headers in your summary list, the DataGrid control might be your best option.

Images Workshop: Configuring a ListBox Control for Displaying a Summary List

In this workshop, we'll create a templated ListBox control that displays data for a ProductSummary entity, which we created and exposed from the server in Chapter 4. For now, we'll merely define it, and look at approaches to populating it with data later, in the section “Populating a Summary List with Data.” When it's populated with data, you will have a ListBox control that looks the same as that shown in Figure 6-4.

images Note As mentioned earlier, the images returned from the server are in the GIF format, which Silverlight does not support. We can convert them to a usable format using a library to help us, but for the purpose of this workshop, we'll simply define an unbound placeholder Image control. The sample code for this chapter does, however, have a full implementation.

  1. In a the “Creating a New View” workshop in Chapter 3, we created a view named ProductListView.xaml. Delete this view, and recreate it, so that you start afresh.
  2. In this view, define the ListBox control, like so:
    <ListBox Height="300" Width="480">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid Height="50">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100" />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>

                    <Image Margin="2" Grid.RowSpan="2" />
                    <TextBlock Name="NameField" Text="{Binding Name}" Margin="2"
                               Grid.Row="0" Grid.Column="1" FontWeight="Bold" FontSize="12" />

                    <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1">
                        <TextBlock Text="Number:" Margin="2"  />
                        <TextBlock Text="{Binding Number}" Margin="2"  />
                        <TextBlock Text="| Available:" Margin="2"  />
                        <TextBlock Text="{Binding QuantityAvailable}" Margin="2"  />
                        <TextBlock Text="| Price:" Margin="2"  />
                        <TextBlock Text="{Binding ListPrice, StringFormat=C}" Margin="2" />
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    Note how the majority of this XAML is for the ListBox control's item template. When the ListBox control is populated, this item template will result in the output shown in Figure 6-4, but without the Image control displaying the image.

  3. To populate the ListBox control with data, you will need to bind its ItemsSource property to a source of data. We'll look at how you do this shortly.

Choosing the Right Control

With these two controls in Silverlight suitable for displaying summary lists, you are left with the potentially difficult decision of which one you should use.

The DataGrid is a powerful and versatile control, revered by developers but detested by most user experience designers. Developers tend to like to use the DataGrid, as it provides a lot of functionality out of the box, with little work required to enable it. Hence, developers treat it like a “god” control that can do everything and make their lives easier. By simply binding a collection to the DataGrid control, you get display and editing behavior, sorting, header resizing, header reordering, multilevel grouping, data validation, and more—all with little or no work required on the part of the developer. However, because of this, it is sometimes overused and implemented in scenarios in which a ListBox would be a more appropriate choice, often at the expense of the application's user experience and user interface design.

images Note Ease of implementation should not provide the basis for your decision on which control you should use. Your decision should solely be based upon what will provide the best experience for the user, and often you will find that the easiest-to-use control for the developer is not always the easiest to use or understand for the user. Keep this in mind when making your choice.

The manner in which the DataGrid has its columns spread out over its width (and beyond) is not always the most efficient way to display the data, especially when the columns extend beyond the width of the DataGrid, where horizontal scrolling would be required. It's important to design your user interface based upon the needs of the user—not simply using what a control provides you.

As a general rule, the DataGrid control is best suited to data entry scenarios; but again, select with care for use in these scenarios too. Generally, you would use the DataGrid, as opposed to something like the DataForm control, for data entry purposes when the user needs to enter the details for a number of records associated with a parent record. For example, the DataGrid is very useful for enabling users to enter line items in an invoice, because there are typically only a few fields required for each invoice line item. However, for general data entry purposes, laying out controls in a form might be more appropriate. (This topic is discussed further in Chapter 7.)

While the ListBox control is row driven by design, the DataGrid is primarily cell driven, which becomes an issue when using it to display a summary list. The purpose of a summary list is to locate and drill down on the data, and as a general rule, the user will use it to drill down upon the data at the row level (not the cell level). Therefore, cell selection behavior in this scenario can be somewhat confusing to the user. If you do use the DataGrid for displaying summary lists, it is recommended that you change the default cell style to remove the selection rectangle from around the selected cell. This way, the cell won't appear selected, but instead the whole row will, as it already exhibits highlighting behavior.

As discussed earlier, the DataGrid does have a big advantage over the ListBox when you need to implement grouping in a summary list, as the ListBox simply doesn't have that capability. (Implementing grouping in a DataGrid will be discussed later in this chapter in the section “Workshop: Grouping the Summary List.”) One of the reasons why few developers tend to use the ListBox control is simply because they don't realize the possibilities opened by templating its items; or if they do, they think it's too much work. You might have to design a data template first to lay out any data to be displayed that involves more than one field.

Ultimately, the most appropriate control for displaying summary lists is the ListBox, even if it does require a little more work to lay out and to implement various behaviors that are built into the DataGrid. It's much more lightweight than the DataGrid, and it's very flexible when templated. The DataGrid does have its place, but should be used only when the user experience design calls for it.

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

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