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.
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.
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"
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.
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;
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. 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>
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.
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.
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 *
.
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:
BitmapImage
, which you can bind toImage
control that can accept an image as a byte arrayAll 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.
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>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
ProductListView.xaml
. Delete this view, and re-create it so that you start afresh.System.Windows.Controls.Data.dll
assembly to your project.sdk
namespace prefix in the root element of this XAML file, as follows:
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
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>
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.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.
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.
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;
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.
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.
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 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>
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
).
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.
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.
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.
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.
ProductListView.xaml
. Delete this view, and recreate it, so that you start afresh.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.
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.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.
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.