Data Templates and ItemControls

Most business applications contain lists of data items. In earlier user interface technologies, these items were often displayed in a data grid, with each item in a separate row. Other controls used to show lists included list boxes and combo boxes.

These controls didn't have a lot of flexibility on what to show to the user. A Windows Forms ListBox, for example, was restricted to showing a single string for each item. HTML interfaces did a little better, but sophisticated HTML coding was needed for all but the simplest lists.

Displaying lists of data items to the user is an area in which XAML absolutely shines. The developer has exquisite control over what the user will see, and even has capabilities for vary what the user sees depending on various aspects of the data, the role of the user, or many other factors.

The controls in XAML that are designed to work with lists of data items all descend from a base class called ItemControl, and they are often referred to collectively as ItemControls. ItemControls can be bound to a list of data items, so that each item in the list is displayed in the control.

However, ItemControls need a way to declare the aspects of the data that will be shown to the user, and how that information will be laid out. That's the function of a data template.

This section will cover the basics of ItemControls and data templates. For simplicity, most of the content in this section will work with the ListBox control. However, the concepts shown apply to all ItemControls, though in some cases there are minor variations in details of implementation.

ListBox is the most commonly used ItemControl in many WPF and Silverlight applications. It is not quite as prominent in Windows 8, though it still works there. In Windows 8, it is more common to use the ListView control, which has some difference in how items are arranged and scrolled. However, as mentioned, the concepts are the same, and learning data templates and associated concepts in the context of a ListBox will produce expertise that can immediately be applied with ListView and other ItemControls.

Setting the Stage with Some Sample Data

If you're going to review lists of data items, you'll need a sample list to serve a tangible example. Here is how you will get a list of data items for our examples.

First you'll need a class for our data objects, such as a Customer data item. A simple version, without property change notification, looks like this (code file: Customer.vb):

Public Class Customer

    Public Property Name As String
    Public Property Balance As Integer
    Public Property Active As Boolean
    Public Property ContactName As String
    Public Property Phone As String
    Public Property Logo As String

End Class

To declare a collection of these Customer objects in XAML, it's helpful to have another simple class named Customers, which looks like this (code file: Customers.vb):

Public Class Customers
    Inherits List(Of Customer)

End Class

You also need some sample data objects based on these classes. For simplicity, those objects will be declared in XAML. A resource named CustomerList will contain five Customer data items, inside a Customers list. Here is the XAML for declaring that list as a resource, assuming a local namespace has been declared in our XAML page (code file: CustomerList.xaml):

<UserControl.Resources>
    <local:Customers x:Key="CustomerList">
        <local:Customer Name="BigBux, Inc." Balance="100"
                    Active="True" ContactName="Sherlock Holmes"
                    Phone="223-555-1234" 
                    Logo="http://billyhollis.com/Logos/BigBuxLogo.png" />
        <local:Customer Name="SmallPotatoes, Ltd." Balance="-300"
                    Active="False" ContactName="Oliver Twist"
                    Phone="212-555-1234" 
                    Logo="http://billyhollis.com/Logos/SmallPotatoesLogo.png"/>
        <local:Customer Name="Medium Enterprises, Inc." Balance="2000"
                    Active="True" ContactName="Elizabeth Bennet"
                    Phone="313-555-1234"
                    Logo=http://billyhollis.com/Logos/MediumEnterprisesLogo.png
                    />
        <local:Customer Name="Globe Theatre" Balance="-200"
                    Active="False" ContactName="Romeo Montague"
                    Phone="515-555-1234" 
                    Logo="http://billyhollis.com/Logos/GlobeTheatreLogo.png"/>
        <local:Customer Name="Global Traders" Balance="-300"
                    Active="True" ContactName="Silas Marner"
                    Phone="616-555-1234" 
                    Logo="http://billyhollis.com/Logos/GlobalTradersLogo.png"/>
    </local:Customers>
</UserControl.Resources>

The Logo property for these items contains a URL, which points to images on a web server. As such, if you are not connected to the Internet as you are trying out these concepts, you will not see the actual images.

All the examples from this point will assume that this list of data items is available in the page where the example XAML is placed.

The generic collection type List(of T) is fine for static, read-only lists. However, if the list will have items added or removed while the list is used in a ListBox, a better choice for the collection type is the ObservableCollection(of T).

ItemControls

You've almost certainly used controls such as list boxes, combo boxes, and the like in other user interface technologies. However, XAML takes a radically different approach to such controls, and thereby gives you a lot of power and flexibility you don't have in other technologies. While some of the basic functionality of those controls will feel familiar, the way the controls display items is radically changed.

You may think that just providing more display flexibility is a nice change, but not a radical one. Not so. The capabilities of XAML ItemsControls drive changes in best practices for user interface design. Hopefully some of the examples in this chapter will demonstrate that to you convincingly.

The next section begins by covering the ListBox in detail. It's a typical and commonly used ItemsControl. Once you've seen what it can do, you'll learn about the other ItemControls.

The XAML ListBox

Superficially, the XAML ListBox resembles the Listbox in older technologies. It contains a collection of items, and it displays something for each item. This main difference is that the XAML ListBox has dramatically more flexibility in how the item is displayed.

You can certainly use the traditional display of a simple string for each item, and you'll see how to do that first. But that's just the start. In essence, you can display an item in a ListBox using any of the layout capabilities of XAML.

The foundation for that capability is that a ListBox always holds a collection of ListBoxItem instances. ListBoxItem is a ContentControl, which means it can have content inside as complex as you like.

It's possible to merely construct ListBoxItem instances manually in XAML. Here is a short example (code file: RandomListBoxItems.xaml):

<ListBox>
    <ListBoxItem>
        I'm a list box item
    </ListBoxItem>
    <ListBoxItem>
        <StackPanel Orientation="Horizontal">
            <Ellipse Fill="Gray" Height="30" Width="60" />
            <TextBlock Text="Me, too" />
        </StackPanel>
    </ListBoxItem>
</ListBox>

However, this capability is rarely used. Instead, the ListBox is bound to a list of data items, and a ListBoxItem must be automatically created for each data item in the list.

The ListBox has an ItemsSource property to bind a list of items to the ListBox. That property can be set to anything that implements the .NET IEnumerable interface, and that includes arrays and most types of collections.

For example, suppose you have a UserControl, and the resources block for the Control contains our list of Customers, as shown in the XAML featured previously. You can bind the list to a ListBox in the UserControl by setting the ItemsSource property of the ListBox to the resource. Here's a XAML snippet that does that:

<ListBox ItemsSource="{StaticResource CustomerList}" . . . />

If you create such a ListBox and bind the list of customers to it using the previous XAML in WPF, the result will look something like Figure 12.10.

Figure 12.10 A ListBox showing a list of Customer objects, without a data template or DisplayMemberPath set. This example is rendered in WPF on Windows 7

12.10

Windows Forms developers have seen this problem before. Since the ListBox does not know how the render the item, each item is displayed by calling the ToString method of the item and showing the returned value. If the ToString method is not overridden in a class, it returns the name of the type for an object. Since the items in the bound list are Customer objects, you get the result in Figure 12.10.

The solution will also be familiar to Windows Forms developers. The ListBox has a property to specify where the item's string comes from. The property is named DisplayMemberPath, and you set it to a property or property path on your data object. If you set DisplayMemberPath in the Visual Studio Properties window to “Name,” then the ListBox will look more like Figure 12.11.

Figure 12.11 The same ListBox as in Figure 12.10, with DisplayMemberPath set to show the customer's name. This example is rendered in WPF on Windows 7

12.11

However, DisplayMemberPath is rarely used in XAML production applications nearly as often as it is in Windows Forms. There's nothing wrong with it; it's just that there are better options in XAML. Once you see these options, you'll understand why they're better.

Data Templates

Instead of just displaying a simple string for each item, you can supply a ListBox with a template that describes how you want the item laid out. Then the ListBox can use the template whenever it needs to display an item. That kind of template is called a data template.

You can think of a data template as a pattern for each item in the ListBox. It specifies the controls that will be shown, how they are laid out, and where each control gets its information through data binding. Then, when an item needs to be rendered, the pattern is used to create a XAML element to display the item.

A data template in XAML is an instance of the DataTemplate element, and contains a tree of elements, just as a Window or Page does. Usually, that tree of elements is defined in XAML. A DataTemplate instance normally has a root container, such as a Border, Grid, or StackPanel, to hold other controls. That panel can then contain XAML controls such as TextBlocks and Images. All of the features you've seen previously for laying out controls in panels are also available in a data template.

Using data binding, the controls in a data template can display any information that comes from a data object. The data object is the actual item in the ListBox; the data template is just the pattern used to transform the item into a visual representation.

The ListBox has an ItemTemplate property that is used to set the desired data template for the ListBox. The data type of the property is DataTemplate. You can create such a DataTemplate right in the XAML definition for a ListBox.

Here is an example of a ListBox with a simple data template for display of records in our list of customers (code file: ListBoxWithTemplate.xaml):

<ListBox ItemsSource="{StaticResource CustomerList}"  
         Name="CustomerListBox" HorizontalContentAlignment="Stretch">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border Margin="3" Padding="3" BorderBrush="Blue" 
                    BorderThickness="2" CornerRadius="4">
                <StackPanel>
                    <TextBlock Text="{Binding Name}" />
                    <TextBlock Text="{Binding ContactName}" />
                    <TextBlock Text="{Binding Phone}" />
                </StackPanel>
            </Border>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Notice that the DisplayMemberPath setting discussed earlier is not present. You cannot include both a data template and a DisplayMemberPath setting in the same ListBox at the same time.

The ItemTemplate property is set to a DataTemplate instance. The DataTemplate instance contains a Border as the root element, which will cause each item in the ListBox to have a border around it. Inside the Border is a StackPanel which contains three TextBlocks. Each TextBlock is data bound to a different property of a Customer object.

Figure 12.12 shows the output of such a program in Windows 8, and Figure 12.13 shows the output of the same XAML in WPF/Windows 7. The two ListBox results are quite similar, with the main differences being the lack of window chrome in Windows 8, and a different default font.

Figure 12.12 A ListBox with a simple data template for Customer objects, rendered in WPF on Windows 7

12.12

Figure 12.13 A ListBox with a simple data template for Customer objects, rendered in Windows 8 XAML

12.13

There is one other subtlety to note. The items are all the same width. That is done in WPF by setting the HorizontalContentAlignment property of the ListBox to Stretch. However, in Silverlight and Windows 8, a different technique is needed, involving a property of the ListBox called ItemContainerStyle. That syntax will be shown later in this chapter, after discussing Styles.

This is an interesting advance over list boxes in ASP.NET and Windows Forms, even with a very simple data template. However, a data template can have a layout as complex as you wish. You can set various properties, such as fonts and colors, for the various controls in the template. Or you could use a Grid instead of a StackPanel and gain more control over positioning of the controls used in the data template.

Let's look at a somewhat more interesting data template. The Customer object has a property for a logo. It's a string property that points to a file name. Loading an Image at the bottom of the StackPanel wouldn't look very nice, and there's all that lovely room on the right side of the item that is currently unused. Let's fix the data template to use a Grid as the root element, and set up the Grid to place the Image on the right (code file: CustomerDataTemplate.xaml):

<DataTemplate>
    <Border Margin="3" Padding="3" BorderBrush="Blue" 
            BorderThickness="2" CornerRadius="4">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="15" />
            </Grid.ColumnDefinitions>
            <StackPanel>
                <TextBlock Text="{Binding Name}" />
                <TextBlock Text="{Binding ContactName}" />
                <TextBlock Text="{Binding Phone}" />
            </StackPanel>
            <Image Height="50" Grid.Column="1"  Source="{Binding Logo}" />
        </Grid>
    </Border>
</DataTemplate>

Now the ListBox will look more like Figure 12.14 when rendered in Windows 8, and like Figure 12.15 when rendered in WPF/Windows 7.

Figure 12.14 A ListBox with a data template for Customer objects, including an Image control for the logo, rendered in Windows 8 XAML

12.14

Figure 12.15 A ListBox with a data template for Customer objects, including an Image control for the logo, rendered in WPF on Windows 7

12.15

Data Binding in Data Templates

In the previous section on Data Binding, you discussed several data binding capabilities, such as value conversion. The bindings in a data template are just regular bindings, and can take advantage of any of those capabilities.

For example, suppose you wanted some visual signal in an item to reflect the customer's balance. For example, the background of an item could be changed if the customer had a negative balance. In this case, the Background property of the Border element would be bound to the Balance property of the Customer, with a value converter that could peek at the Balance and return an appropriate Brush. That value converter would be very similar to the QuantityToBrushConverter example covered earlier.

Items in ListBox elements are also a good place to use RelativeSource bindings. Suppose, for example, you wished to have only the logo image appear if the customer was selected. The ListBoxItem class has an IsSelected property, which is Boolean. However, to use a parent ListBoxItem as a data source, the binding must be able to locate it.

That's one of the functions of a RelativeSource binding. In this case, you don't know the name of the ListBoxItem, but you do know where it is—namely, up the tree from the items in the data template. You'll see the syntax to apply a RelativeSource binding, which locates the ListBoxItem, in just a moment. First, though, there's one other concern you need to address.

The IsSelected property is a Boolean, but the Visibility property of the Image is of type Visibility. As you saw earlier, that property type can take values of Visible or Collapsed. (WPF has an additional value of Hidden.) To bind a Boolean property to a Visibility property, a value converter is needed.

In WPF, that value converter is built in, and is called, naturally enough, BooleanToVisibilityConverter. Silverlight and Windows 8 XAML lack that built-in converter, but it's quite easy to write one. It looks like this (code file: BooleanToVisibilityConverter.vb):

Public Class BooleanToVisibilityConverter
    Implements IValueConverter

    Public Function Convert(value As Object, …
        If CBool(value) Then
            Return Visibility.Visible
        Else
            Return Visibility.Collapsed
        End If
    End Function

    Public Function ConvertBack(value As Object, …
        If CType(value, Visibility) = Visibility.Visible Then
            Return True
        Else
            Return False
        End If
    End Function
End Class

Again, ellipses are used to deal with the differing method signatures in Windows 8 XAML vs. WPF and Silverlight.

You can create an instance of this converter as a resource, giving it a key of “B2VConverter”:

<local:BooleanToVisibilityConverter x:Key="B2VConverter" />

Then you can create the RelativeSource binding you need to make the logo visible only for a selected Customer, but changing the Image element in the earlier DataTemplate to look like this:

<Image Height="50" Grid.Column="1"  Source="{Binding Logo}" 
Visibility="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=ListBoxItem, AncestorLevel=1}, Path=IsSelected, 
Converter={StaticResource B2VConverter}}" />

The result rendered in WPF would look like Figure 12.16, with only the selected item showing the logo.

Figure 12.16 A ListBox with a data template for Customer objects, including an Image control for the logo. It is rendered in WPF on Windows 7

12.16

Switching between Data Templates

Designing user interfaces can be frustrating, because you usually have a wide range of users. What works best for one type of user might not work well for another type. One of XAML's strengths is to give more options for dealing with that challenge.

You have seen two different data templates for the Customer object, one that includes the logo and a simpler one that does not. Neither of these templates is particular well designed; they're intended simply to demonstrate the syntax of data templates. However, let's pretend that they're both worthy templates for different situations. Some users like the richer template with the logo, while others prefer the simpler template.

You can provide users a means to switch between the two templates. Each template is just an object instance of a DataTemplate class. The ListBox has an ItemTemplate property that points to the currently used data template. While our examples set the ItemTemplate property in XAML, there's nothing stopping us from setting it in code.

However, you need to be able to locate data templates to use them in code. If you know what resource dictionary the data templates are defined in, that's easy.

Let's run through an example that does all this.

First, you'll need to place both our data templates in the Resources property of the UserControl. The XAML for that is exactly the same as the earlier XAML for our two data templates, except that each template needs an x:Key. The template with the logo will get the key “TemplateWithLogo” and the other one will get “TemplateWithoutLogo.” Here are those resource declarations (code file: TwoDataTemplates.xaml):

<DataTemplate x:Key="TemplateWithLogo">
    <Border Margin="3" Padding="3" BorderBrush="Blue" 
    BorderThickness="2" CornerRadius="4">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="15" />
            </Grid.ColumnDefinitions>
            <StackPanel>
                <TextBlock Text="{Binding Name}" />
                <TextBlock Text="{Binding ContactName}" />
                <TextBlock Text="{Binding Phone}" />
            </StackPanel>
            <Image Height="50" Grid.Column="1"  Source="{Binding Logo}"/>
        </Grid>
    </Border>
</DataTemplate>

<DataTemplate x:Key="TemplateWithoutLogo">
    <Border Margin="3" Padding="3" BorderBrush="Blue" 
            BorderThickness="2" CornerRadius="4">
        <StackPanel>
            <TextBlock Text="{Binding Name}" />
            <TextBlock Text="{Binding ContactName}" />
            <TextBlock Text="{Binding Phone}" />
        </StackPanel>
    </Border>
</DataTemplate>

To arrange switching between the templates, you could use two Button elements, a CheckBox, a ComboBox, or various other possibilities for user interaction. To keep our example simple, let's assume it's a CheckBox. One of the templates will be used when the CheckBox is checked and the other when it is unchecked.

The declaration for the CheckBox would look something like this:

<CheckBox VerticalAlignment="Bottom" 
          IsChecked="True"
          Checked="CheckBox_Checked"
          Unchecked="CheckBox_Checked" 
          Content="Use template with logo" />

The event handler referenced in this XAML, plus a helper routine, would look something like this (code file: SwitchDataTemplates.vb):

Private Sub CheckBox_Checked(sender As System.Object, e As 
                            System.Windows.RoutedEventArgs)
    If CType(sender, CheckBox).IsChecked Then
        CustomerListBox.ItemTemplate = GetDataTemplate("TemplateWithLogo")
    Else
        CustomerListBox.ItemTemplate = GetDataTemplate("TemplateWithoutLogo")
    End If
End Sub

Private Function GetDataTemplate(sName As String) As DataTemplate
    If Me.Resources.Contains(sName) Then
        Return CType(Me.Resources.Item(sName), DataTemplate)
    Else
        Return Nothing
    End If
End Function

This code could stand some hardening, but it demonstrates the concept. As soon as the CheckBox is checked or unchecked, the appropriate template is located in the resources collection, and applied to the ItemTemplate property of CustomerListBox.

Changing Layout of ListBox Items with ItemsPanel

Befitting the philosophy of XAML, a ListBox doesn't really know how to stack up items. It only stacks items by default because it contains a variant of a StackPanel inside. The special StackPanel used by a ListBox is called a VirtualizingStackPanel, and has special rendering logic for long lists.

The default VirtualizingStackPanel can be replaced. For example, if you would like the items in your ListBox to stack horizontally instead of vertically, you can change the panel used for stacking to a StackPanel with Orientation set to Horizontal.

The panel used to arrange items is set with the ItemsPanel property of the ListBox. Here is a ListBox with the ItemsPanel property modified to use a horizontal StackPanel:

<ListBox.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel Orientation="Horizontal" />
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>

Any available panel can be used as a layout panel for a ListBox, though some of them require additional work to get items properly positioned. The code download for this chapter includes a ListBox in WPF that uses a Canvas as a layout panel.

Additional ItemControls

As mentioned at the start of this section, the ListBox was used to discuss concepts that apply to all ItemControls. Each XAML platform has other ItemControls, and all of them use DataTemplates for determining layout of their items.

Examples of other ItemControls on the various XAML platforms include ComboBox, TreeView, ListView, ContextMenu, and DataGrid. If you understand how data templates work, you should have no difficulty in applying that understanding to any of these controls.

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

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