Skip to main content

Microsoft Silverlight

Jesse LibertyWritten by:
Jesse Liberty
Microsoft

DataBinding & DataTemplates Using Expression Blend

0 0

Summary

In Tutorial #2, Data Binding, we explored how to bind relatively simple data to relatively simple controls. This tutorial will dive a bit deeper, looking specifically at two topics that are of particular importance in DataBinding:

  • Binding complex objects to List controls
  • Using Expression Blend vs. Creating bindings in Xaml by hand

While we did have a list of chapter names that we bound to a list box in the previous chapter that was not quite complex enough to raise the question of DataTemplates; in this tutorial we'll look at what happens when you bind more complicated objects, that have numerous properties, to a list control.

The Finished Product

To get an idea of where we are going, and what pieces will be important, let's take a look at some aspects of the finished product.

Figure 7-1. ListBox with Data From Books (Click to view full-size image)

A quick look at Figure 7-1 shows that we're listing each book in a collection, formatting the name in red and Comic Sans MS, 16 point, while we're formatting the rest of the line, which includes the Book's ISBN and cover price in Verdana, black, 14 point. How we are doing that is not yet obvious. Nor is it obvious where the information (book title, ISBN or price) is coming from.

It gets more interesting when you click on a particular book in the list as shown in Figure 7-2

Figure 7-2. Book Details (Click to view full-size image)

When the user clicks on a book it is highlighted, and the list box literally shrinks down to make room for the details about the book. What is now visible is 6 rows of information about the book, including a list of authors bound to a smaller list box, a few rows of strings and integers, and a rating displayed both in a slider and a TextBlock.

Leveraging Expression Blend with Visual Studio

This tutorial will show how to create bindings both to lists and to individual controls using Blend; giving you more leverage, more quickly, and, like any good development environment, allowing you to focus on what you are trying to build rather than on the specific syntax of building it.

This tutorial will not recapitulate the concepts covered in Tutorial #2, but we will introduce a few new UI concepts that Blend makes easy to implement.

Creating the Project

To get started, create a new project, in Blend, called Silverlight Data as shown in Figure 7-3

Figure 7-3. Creating a new project in Blend (Click to view full-size image)

Once the project is created, set the outermost user control to 680 x 550 and create three rows, as shown in Figure 7-4

Figure 7-4. Creating three rows in Blend (Click to view full-size image)

If you are not comfortable with how to create these rows in Blend, you'll want to pause here and read Tutorial #5 on Expression Blend for Developers

Note that the top row is of fixed height, and most of the space is devoted to the middle row. If you care to make the measurements exact, feel free to open the Xaml window and set them by hand, though this is not necessary at all:

<Grid x:Name="LayoutRoot" Background="White" >
    <Grid.RowDefinitions>
          <RowDefinition Height="90"/>
          <RowDefinition Height="200"/>
          <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

Add the title to the top row, by dragging on a TextBlock, and setting its properties as shown in Figure 7-5

Figure 7-5. Setting the properties for the Title (Click to view full-size image)

Drag a ListBox control into the 2nd Row

Drag a ListBox from the Assets into the Second Row and set its Alignments to stretch and its margins to 25,50,25,50 as shown in Figure 7-6

Figure 7-6. Adding and Positioning the ListBox Control (Click to view full-size image)

Populating the ListBox with hard-coded strings

One way to separate the issue of managing the look and feel of ListBoxItems from that of binding ListBoxItems is to start by just hard-coding a few values using the ListBoxItem Collection Editor. This only takes a few minutes; here's how you do it:

Begin by clicking on the ListBox in the Interaction Panel and then click on Item (Collection) in the Common Properties tab as shown in Figure 7-7

Figure 7-7. Adding to the Items Collection

This opens a rather intimidating Collection Editor. Don't panic. Click on the button which disingenuously says "Add another item." Again, don't panic when a virtually empty dialog appears. Click the check box "Show System Assemblies."

You just went from nothing to far too much. You can reduce this clutter and find what you need by searching; enter ListBoxItem in the search box as shown in Figure 7-8

Figure 7-8. Selecting which class to add to the list box's collection

Clicking on ListBoxItem will open a special editor that will allow you to edit your ListBoxItem's content, giving you a tremendous degree of control over the appearance of the items you'll be adding by hand,

Figure 7-9. Property Editor for ListBoxItem (Click to view full-size image)

Fill in the properties for your first List Box Item, setting the text to the first line shown in Figure 7-9. Once you've added three or four items, click OK and you'll return to the page, but the list box will no longer be empty. Run the program and you'll see your hard-coded data displayed in the list box as shown in Figure 7-10

Figure 7-10. Hard Coded ListBoxItems

Design by Crick And Watson[1]

Our goal is to create an n-tier application, with the three principal layers being:

  • User Interface Layer
  • Business Layer
  • Persistence Layer

The User Interface (UI) Layer is the part the user sees and interacts with, and we'll build that in Expression Blend using Silverlight controls.

The Business Layer manages the logic of your application; we'll build that in Visual Studio using code in VB and C#.

For this application the Business Layer will be encapsulated in three relatively simple classes:

  1. Library represents a named collection of books retrieved from a larger collection. The larger collection might be a database, a web service or some other persistence mechanism.

  2. Book represents some level of abstraction of a Book, though it may represent one or more printings or editions. It contains such vital properties as the author, the title, the author, the publisher, the author, the publication date and the author.

  3. Author, representing what we need to know about the author of a Book for this application. Author is so vital to a book we've made it a class of its own.

Our goals in this tutorial will include:

  1. Data binding: bind the members of a list box to the Books collection in a Library

  2. Data Template: tell the list box how to display (some of ) the properties of each book in the list

Creating the Data Classes

Begin by deleting the ListBox (and its contents) from the LayoutRoot. (Really, it's okay, I'll make you a much nicer ListBox with real data; I promise).

Blend isn't the right place to create business layer classes, so click on File-Save all and then right click on the application and choose Edit in Visual Studio as shown in Figure 7-11.

Figure 7-11. Moving from Blend to Visual Studio to Create Classes

When Visual Studio opens, create the three classes discussed above. The complete code is provided here, and taken apart immediately afterward.

Author.vb
Public Class Author
    Private privateName As String
    Public Property Name() As String
        Get
            Return privateName
        End Get
        Set(ByVal value As String)
            privateName = value
        End Set
    End Property
 
End Class
 
 
Books.vb
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Collections.ObjectModel
 
Public Class Book
   Implements INotifyPropertyChanged
   Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
 
   Public Sub New()
   End Sub
 
   Public Sub New( _
       ByVal title As String, _
       ByVal authorList As ObservableCollection(Of Author), _
       ByVal coverPrice As Double, _
       ByVal isbn10 As String, _
       ByVal publisher As String, _
       ByVal edition As Integer, _
       ByVal printing As Integer, _
       ByVal pubYear As String, _
       ByVal rating As Double)
 
      Me.Title = title
 
      Me.Authors = New ObservableCollection(Of Author)()
      For Each auth As Author In authorList
         Me.Authors.Add(auth)
      Next auth
 
      Me.CoverPrice = coverPrice
      Me.ISBN10 = isbn10
      Me.Publisher = publisher
      Me.Edition = edition
      Me.Printing = printing
      Me.PubYear = pubYear
 
   End Sub
 
   Private privateTitle As String
   Public Property Title() As String
      Get
         Return privateTitle
      End Get
      Set(ByVal value As String)
         privateTitle = value
         NotifyPropertyChanged("Title")
      End Set
   End Property
   Private privateAuthors As ObservableCollection(Of Author)
   Public Property Authors() As ObservableCollection(Of Author)
      Get
         Return privateAuthors
      End Get
      Friend Set(ByVal value As ObservableCollection(Of Author))
         privateAuthors = value
         NotifyPropertyChanged("Authors")
      End Set
   End Property
   Private privateCoverPrice As Double
   Public Property CoverPrice() As Double
      Get
         Return privateCoverPrice
      End Get
      Set(ByVal value As Double)
         privateCoverPrice = value
         NotifyPropertyChanged("CoverPrice")
      End Set
   End Property
   Private privateISBN10 As String
   Public Property ISBN10() As String
      Get
         Return privateISBN10
      End Get
      Set(ByVal value As String)
         privateISBN10 = value
         NotifyPropertyChanged("ISBN10")
      End Set
   End Property
   Private privatePublisher As String
   Public Property Publisher() As String
      Get
         Return privatePublisher
      End Get
      Set(ByVal value As String)
         privatePublisher = value
      End Set
   End Property
   Private privateEdition As Integer
   Public Property Edition() As Integer
      Get
         Return privateEdition
      End Get
      Set(ByVal value As Integer)
         privateEdition = value
         NotifyPropertyChanged("Edition")
      End Set
   End Property
   Private privatePrinting As Integer
   Public Property Printing() As Integer
      Get
         Return privatePrinting
      End Get
      Set(ByVal value As Integer)
         privatePrinting = value
         NotifyPropertyChanged("Printing")
      End Set
   End Property
   Private privatePubYear As String
   Public Property PubYear() As String
      Get
         Return privatePubYear
      End Get
      Set(ByVal value As String)
         privatePubYear = value
         NotifyPropertyChanged("PubYear")
      End Set
   End Property
 
   Private privateRating As Double
   Public Property Rating() As Double
      Get
         Return privateRating
      End Get
      Set(ByVal value As Double)
         privateRating = value
         NotifyPropertyChanged("Rating")
      End Set
   End Property

  
   Private Sub NotifyPropertyChanged(ByVal propName As String)
      RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
   End Sub
End Class
 
 
Library.vb
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports System.Collections.Generic
Imports System.Windows.Browser
 
Public Class Library
   Implements INotifyPropertyChanged
   Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
 
   Public Sub New()
      PrivateName = String.Empty
      PrivateBooks = New ObservableCollection(Of Book)()
      If HtmlPage.IsEnabled = False Then GenerateDummyData()
 
   End Sub
 
 
   Private PrivateName As String
   Public Property Name() As String
      Get
         Return PrivateName
      End Get
      Set(ByVal value As String)
         PrivateName = value
         RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Name"))
      End Set
   End Property
 
   Private PrivateBooks As ObservableCollection(Of Book)
   Public Property Books() As ObservableCollection(Of Book)
      Get
         If PrivateBooks.Count < 1 Then
            GetBooks()
         End If
 
         Return PrivateBooks
      End Get
      Set(ByVal value As ObservableCollection(Of Book))
         PrivateBooks = value
         RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Books"))
      End Set
   End Property
 
   Public Sub GenerateDummyData()
 
      Books = New ObservableCollection(Of Book)
 
      Dim Authors As ObservableCollection(Of Author) = New ObservableCollection(Of Author)()
      Dim Author1 As Author = New Author
      Dim Author2 As Author = New Author
      Author1.Name = "Jesse Liberty"
      Author2.Name = "Mark Twain"
      Authors.Add(Author1)
      Authors.Add(Author2)
 
      Dim newBook As New Book("Programming Silverlight", Authors, 49.99, "0123456789", "O'Reilly", 1, 1, "2000", 5.0)
      Books.Add(newBook)
 
      Author2.Name = "Cormac McCarthy"
      newBook = New Book("Programming Without Shoes", Authors, 149.99, "0123456789", "O'Reilly Media", 1, 1, "1955", 4.5)
      Books.Add(newBook)
 
      Author2.Name = "Ian McEwan"
      newBook = New Book("Programming Without A Spec", Authors, 149.99, "0123456789", "O'Reilly Media", 1, 1, "1955", 4.3)
      Books.Add(newBook)
 
      Author2.Name = "Stephen King"
      Books.Add(New Book("Programming Without A Conscience", Authors, 149.99, "0123456789", "O'Reilly Media", 1, 1, "1955", 4.1))
 
   End Sub
 
 
 
   Private Sub GetBooks()
      Name = "Liberty Books"
 
      Dim Authors As ObservableCollection(Of Author) = _
              New ObservableCollection(Of Author)()
      Dim Author1 As Author = New Author
      Dim Author2 As Author = New Author
      Dim Author3 As Author = New Author
 
      Author1.Name = "Jesse Liberty"
      Author2.Name = "Tim Heurer"
      Authors.Add(Author1)
      Authors.Add(Author2)
 
      Dim newBook As New Book( _
          "Programming Silverlight", _
          Authors, _
          49.99, _
          "TBD", _
          "O'Reilly Media", _
          1, _
          1, _
          "2009", _
          5.0)
      PrivateBooks.Add(newBook)
 
      Authors.Remove(Author2)
      Author2 = New Author()
      Author2.Name = "Alex Horovitz"
      Authors.Add(Author2)
 
      newBook = New Book("Programming .NET 3.5", Authors, 49.99, "0-596-51039-X", "O'Reilly Media", 1, 2, "2008", 4.7)
      PrivateBooks.Add(newBook)
 
      Authors.Remove(Author2)
      Author2 = New Author()
      Author2.Name = "Dan Hurwitz"
      Author3.Name = "Brian Macdonald"
      Authors.Add(Author2)
      Authors.Add(Author3)
 
      newBook = New Book("Learning ASP.NET 3.5", Authors, 44.99, "0-596-51845-5", "O'Reilly Media", 2, 1, "2008", 4.8)
      PrivateBooks.Add(newBook)
 
      Authors.Remove(Author2)
      Authors.Remove(Author3)
      Author2 = New Author()
      Author2.Name = "Donald Xie"
      Authors.Add(Author2)
      PrivateBooks.Add(New Book("Programming C# 3.0", Authors, 44.99, "0-596-51845-5", "O'Reilly Media", 5, 2, "2008", 4.3))
 
 
      Authors.Remove(Author2)
      Author2 = New Author()
      Author2.Name = "Dan Hurwitz"
      Authors.Add(Author2)
      PrivateBooks.Add(New Book("Programming .NET Windows Apps", Authors, 49.95, "0596003218", "O'Reilly Media", 1, 1, "2003", 3.7))
 
 
      Authors.Remove(Author2)
      Author2 = New Author()
      Author2.Name = "Brad Jones"
      Authors.Add(Author2)
      PrivateBooks.Add(New Book("Teach Yourself C++ in 1 Hour", Authors, 44.99, "0672327112", "Sams", 6, 1, "2008", 2.9))
 
      Authors.Remove(Author2)
      Author2 = New Author()
      Author2.Name = "David Horvath"
      Authors.Add(Author2)
      PrivateBooks.Add(New Book("Teach Yourself C++ in 24 Hours", Authors, 34.99, "0672326817", "Sams", 4, 1, "2004", 2.4))
 
 
      Authors.Remove(Author2)
      PrivateBooks.Add(New Book("Programming VB.NET", Authors, 39.95, "0596004389 ", "O'Reilly Media", 2, 1, "2003", 3.2))
      PrivateBooks.Add(New Book("Visual C# 2005 Dev Notebook", Authors, 29.95, "059600799X", "O'Reilly Media", 1, 1, "2005", 3.7))
      PrivateBooks.Add(New Book("Clouds To Code", Authors, 41.95, "1861000952", "Wrox", 1, 1, "1997", 4.1))
   End Sub
 
 
End Class

Save the project in Visual Studio and before you return to Blend let's unpack these classes to make sure they are fully understood.

ObservableCollection and INotifyPropertyChanged

You may have noticed the following declarations,

Public Class Library
        Private PrivateBooks As ObservableCollection(Of Book)
 
Public Class Book
    Private privateAuthors As ObservableCollection(Of Author)

An observable collection is much like a list, except that it implements the INotifyPropertyChanged event, firing that event each time the collection is modified in any way). This is done without any effort on your part, and is especially useful because all of the UIControls have built into them the code to register for that event. If you want your controls to be updated when the underlying collection changes you do not have to write any code, it all happens automagically! Right convenient, that.

The Library class consists of a constructor (which just sets the name to empty and allocates memory for an empty observable collection of Book objects.

It does one more terribly convenient thing, it check to see if we are in design mode, and if so it generates Dummy data to fill the list box so we can see what things look like. This is entirely gratuitous but very handy,

If HtmlPage.IsEnabled = False Then GenerateDummyData()

The Library constructor is followed by two properties, Name and Books, where the latter is a very clever observableCollection of Book objects. Clever because when you try to retrieve the collection it checks to make sure it has Book objects to return and if not it calls a private helper method GetBooks

Public Property Books() As ObservableCollection(Of Book)
   Get
      If PrivateBooks.Count < 1 Then
         GetBooks()
      End If
 
      Return PrivateBooks
   End Get

GetBooks is free to go off to a web service and execute a query, or to read books from an XML file or do just about anything it chooses. What it chooses is to arbitrarily fill the PrivateBooks collection with a selection of Book objects.

This is convenient because it allows us to focus on data binding rather than Book getting.

Binding Your Business Object To The Listbox

Save all that, and return to the project in Blend where you will be told that your project has changed (well, yes, it has, actually) as shown in Figure 7-12. Say yes, you do want to reload it.

(Well, you have to click the Yes button, actually saying "yes" out loud won't do a bit of good on most computers, though you never know.)

Figure 7-12. Reloading Blend (Click to view full-size image)

It turns out that binding the Books collection to the ListBox is embarrassingly simple. Here's how you do it.

Find the +Clr Object button on the Data tab (typically below the Project/Properties/Resources panels. Click on it as shown in Figure 7-13

Figure 7-13. Clicking on CLR Object in the Data Tab

This opens the "Add CLR Object Data Source window" which will display all the potential data source objects in your project. From here you can elect Library as your data source, as shown in Figure 7-14

Figure 7-14. Binding the Library Class as the Data Source

Once Library is chosen, that result is reflected back in the Data window, and turning the arrow next to Library reveals the properties, including its Property Books as shown in Figure 7-14 Drag that Books object onto the Listbox and let go!

Figure 7-15. Books object in Data Window

When you let go, you are offered the opportunity to bind the Books collection to the list box as shown in Figure 7-16

Figure 7-16. Binding Books Collection to ListBox

As soon as you choose to bind the Books collection the ListBox you'll be prompted as to which field should be bound. With a list box the source list is bound to the ItemsSource, which is the default field displayed, as shown in Figure 7-17

Figure 7-17. Create Data Binding

The instant you accept ItemsSource the grid is populated with one row for each book in the collection, though not quite as you might hope. Run the application, let's see that bound data but don't be too upset; we haven't told the list box quite enough yet for it to show you what you want.

Figure 7-18. Showing The Books - Almost (Click to view full-size image)

The problem the list box is having in Figure 7-17 is that you are asking it to bind to a very complex object (a Book) which has a lot of properties. It has no idea what you want it to display, and so in desperation it just shows you the type of the object.

All it needs is for you to provide it a template for what each line should look like, and then it will happily zip though the collection applying your item template to each item in the collection.

You can write that item template by hand or in Blend.

Writing a ListItemTemplate By Hand

It turns out to be pretty easy to write a simple list item template by hand, so let's do that first.

Open the project in Visual studio and find the List box in Page.xaml. You'll find that it has a self-closing tag. Un-self-close it!

<ListBox x:Name="BookListBox"
    Margin="50,25,50,25"
    Grid.Row="1" 
    HorizontalAlignment="Stretch"
    VerticalAlignment="Stretch"
    ItemsSource="{Binding Mode=OneWay, Path=Books,
    Source={StaticResource LibraryDS}}">
   
    </ListBox>

Between the opening tag and the closing tag we'll place the ListBox.ItemTemplate and within that a DataTemplate.

<ListBox x:Name="BookListBox"
    Margin="50,25,50,25"
    Grid.Row="1" 
    HorizontalAlignment="Stretch"
    VerticalAlignment="Stretch"
    ItemsSource="{Binding Mode=OneWay, Path=Books,
    Source={StaticResource LibraryDS}}">
        <ListBox.ItemTemplate>
            <DataTemplate>
           
            </DataTemplate>                 
        </ListBox.ItemTemplate>
    </ListBox>

Now it is just a matter of filling the DataTemplate with whatever controls you want to use to bind to the properties in each book. Typically you begin with some sort of layout control. My choice here is to use a stack panel and 5 text blocks to keep things simple. I'll use the first, third and fifth to bind to properties of each book, and the remaining two as "prompts" for the data...

<ListBox.ItemTemplate>
    <DataTemplate>
        <StackPanel x:Name="DisplayListData"
        Orientation="Horizontal"
        VerticalAlignment="Bottom"
        Margin="5" >
            <TextBlock x:Name="Title"
            Text="{Binding Title}"
            Margin="5,0,0,0"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left"
            FontFamily="Comic Sans MS"
            FontSize="18" />
           
            <TextBlock x:Name="ISBNPrompt"
            Text="(ISBN:  " 
            Margin="10,0,0,0"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left"
            FontFamily="Verdana"
            FontSize="14" />
           
            <TextBlock x:Name="ISBN"
            Text="{Binding ISBN10}" 
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left"
            FontFamily="Verdana"
            FontSize="14" />
           
            <TextBlock x:Name="CoverPrompt"
            Text=") Cover Price: $" 
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left"
            FontFamily="Verdana"
            FontSize="14" />
           
            <TextBlock x:Name="Cover"
            Text="{Binding CoverPrice}" 
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left"
            FontFamily="Verdana"
            FontSize="14" />
        </StackPanel>
    </DataTemplate>              
</ListBox.ItemTemplate>

Within the DataTemplate is a very straightforward StackPanel and within that are five nearly identical TextBlocks. The first has its font size set to 16 and to Comic Sans MS, all the rest are set to Verdana 14. The first, third and fifth are bound to the Title, ISBN10 and CoverPrice properties of the book, respectively. When run, the list box now looks like Figure 7-19

Figure 7-19. Running with the ListItemTemplate (Click to view full-size image)

Creating the List Item Template in Blend

Before we proceed, let's rip out the list item template and recreate it in Blend. We just pull out the entire template and make the list box self-closing again. We then rebuild the application and then open it in Blend.

In Blend we click on the ListBox and then select Object → EditOtherTemplates → EditItemTemplate → Create Empty as shown in Figure 7-20

Figure 7-20. Creating a ListItemTemplate in Blend (Click to view full-size image)

As soon as you make this choice, you are presented with the DataTmeplate Resource dialog box, shown in Figure 7-21 where you can decide whether you want your data template to be an application-wide resource or scoped to the current document (typically you'll choose the default of putting the list item template in the document)

Figure 7-21. Create Data Template Dialog

Note, during beta there is a known bug, and if you open the lower drop down and choose list box rather than the default “user Control” your code will not compile properly

Clicking OK creates an empty list item template which we can then populate like any other UI element (in fact, Blend starts us off with a Grid control. We'll just add the stack panel to that as shown in Figure 7-22. Once the stack panel is in place, we can then drag on the text blocks, setting the binding just as we would with any other controls.

Figure 7-22. Creating the DataStackPanel for the ListItem (Click to view full-size image)

When creating the list box item template it will be important to bind the text blocks to the Books collection as the data source, and to the properties of the Book class as the binding fields, as you can see in Figure 7-23

Figure 7-23. Binding to Book Properties (Click to view full-size image)

Note that you create BookDS just like you created LibraryDS, by pressing on the +CLR object button.

Implementing LibraryList_SelectionChanged

When the user clicks on a book, the LibraryList_SelectionChanged event handler will be called and will take the following actions:

  1. Resize the list box to make it smaller and to set it to span only one row
  2. Set the details grid to be visible
  3. Set a Book object (selectedBook) to the book the user chose
  4. Set the dataContext for all the controls to be that selected book by setting the DataContext of the DetailsGrid
  5. Raise the PropertyChanged event from within the Book so that the UI is updated.

Creating the Second Grid

Return to Blend and hide the library list by clicking on the eyeball next to it in the Objects and Timeline as shown in Figure 7-24

Figure 7-24. Hiding the LibraryList

Draw a Grid, DetailsGrid, into the third row of the existing grid, and use the stretch and margin (=0) properties to have it fill the entire row.

Divide the DetailsGrid into six rows and two columns, and add the controls as shown in Figure 7-25

Figure 7-25. DetailsGrid divided into six rows and two columns (Click to view full-size image)

Binding the controls in the DetailsGrid grid.

There are eight controls in the six rows on the right side of the DetailsGrid, though in Figure 7-24 six of them are not visible (they are TextBlocks with no text). The easiest way to describe their position, attributes and binding is to show you the Xaml, though you will not create them in Xaml, but rather by dragging TextBlocks onto the Artboard and setting their properties.

Please assume:

VerticalAlignment=”Bottom”
HorizontalAlignment=”Right”
FontFamaily=”Verdana”
FontSize=”18”
FontWeight="Medium"

Unless the Xaml states otherwise.

<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" >
 
    <TextBlock x:Name="NumAuthorsPrompt" 
    Text="{Binding NumAuthors, Mode=OneWay}"
    Margin="0,0,15,15"/>
              
    <TextBlock x:Name="AuthorsPrompt"  Text="Authors"
    Margin="0,0,15,15"/>
              
</StackPanel>
                  
<ListBox x:Name="AuthorsListBox"
  Height="Auto" Width="Auto"
  HorizontalAlignment="Stretch"
  VerticalAlignment="Stretch"
  Grid.Column="1" Grid.Row="0" Margin="10,0,20,0"
  ItemsSource="{Binding Authors, Mode=OneWay}">
     <ListBox.ItemTemplate>
         <DataTemplate>
             <StackPanel Orientation="Horizontal" Margin="5">
                 <TextBlock
                    FontSize="14"
                    Foreground="Black"
                    Text="{Binding Name}"/>
            </StackPanel>
         </DataTemplate>
     </ListBox.ItemTemplate>
</ListBox>
 
                  
<TextBlock x:Name="PublisherPrompt" Grid.Row="1" Grid.Column="0"
Text="Publisher" Margin="0,0,15,15"/>
                  
<TextBlock x:Name="Publisher" Grid.Row="1" Grid.Column="1"
HorizontalAlignment="Left" Height="Auto" Width="Auto" Margin="10,0,15,15"
Text="{Binding Publisher}" />
   
<TextBlock x:Name="EditionPrompt" Grid.Row="2" Grid.Column="0"
Text="Edition" Margin="0,0,15,15"/>
 
<TextBlock x:Name="Edition" Grid.Row="2" Grid.Column="1"
HorizontalAlignment="Left" Height="Auto" Width="Auto" Margin="10,0,15,15"
Text="{Binding Edition}" />
  
<TextBlock x:Name="PrintingPrompt" Grid.Row="3" Grid.Column="0"
Text="Printing" Margin="0,0,15,15"/>
 
<TextBlock x:Name="Printing" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
Height="Auto" Width="Auto" Margin="10,0,15,15"  
Text="{Binding Printing}" />
 
<TextBlock x:Name="YearPrompt" Grid.Row="4" Grid.Column="0"
Text="Publication Year" Margin="0,0,15,15"/>
 
<TextBlock x:Name="Year" Grid.Row="4" Grid.Column="1"
HorizontalAlignment="Left" Height="Auto" Width="Auto"
Margin="10,0,15,15"  Text="{Binding PubYear}" />  
 
<TextBlock x:Name="RatingPrompt" Grid.Row="5" Grid.Column="0"
Text="Rating" Margin="0,0,15,15"/>  
 
<StackPanel Grid.Row="5" Margin="0,0,0,15" Grid.Column="1"
Orientation="Horizontal" > 
 
<Slider x:Name="RatingSlider" Width="150"
HorizontalAlignment="Left" Margin="5,0,5,0"
LargeChange="1.0" SmallChange="0.1"
Minimum="0" Maximum="5.0" ValueChanged="RatingSlider_ValueChanged"
Value="{Binding Rating, Mode=TwoWay }" /> 
 
<TextBlock x:Name="SliderValueDisplay" Margin="5,0,0,0"
HorizontalAlignment="Left" />
 
</StackPanel>
</Grid>

Hiding the DetailsGrid

You want this grid to be invisible when the outer-grid (LayoutControl) is first displayed, so change the Visibility property (line 65) from Visibility="Visible" to Visibility="Collapsed" and, for convenience, unhide the Library List in the Objects and Timeline tab.

Creating the Event Handlers

You need an event handler to switch from one view to the other, and you need an event handler for changes to the slider (note the inline event "ValueChanged"

Resize the List Box, Span Just One Row

Setting the size is a matter of setting the margins properly. Margins, it turns out, are instances of the class Thickness.

As for setting the ListBox to span only a single row, here we must use the extended property of the Grid, and to do that we use the SetValue method, passing in the Grid's property and the value we wish to set it to.

Private Sub MyListbox_SelectionChanged(ByVal sender As System.Object, _
         ByVal e As System.Windows.Controls.SelectionChangedEventArgs)
 
    LibraryList.Margin = New Thickness(50,5,20,5)
    LibraryList.SetValue(Grid.RowSpanProperty, 1)

If you wish to see if this is working before going further, find LayoutRoot (the top Grid) and add ShowGridLines="True" - then run the program and click on a book. You should see the list control shrink above the lower dashed line, as shown in Figure 7-26

Figure 7-26. ListBox resized into Row 1 (Click to view full-size image)

Returning to the event handler, the Details Grid has a visibility property; Intellisense will help you set it properly to the enumerated value Visibility.Visible.

Set a Book object (selectedBook) to the book the user chose

The selected book will come into your method through the SelectionChangedEventArgs as the first member of the AddedItems collection. That will be an object whose underlying type is Book, and you can cast it accordingly.

Dim selectedBook As Book = TryCast(e.AddedItems(0), Book)

Set the dataContext for all the controls to be that selected book by setting the DataContext of the DetailsGrid

All of the controls inside a container inherit that container's DataContext unless they override it. Thus there is no reason to write,

Printing.Text = selectedBook.Printing.ToString()
PublicationYear.Text = selectedBook.PubYear.ToString()

Etc. Instead you can just bind the text of these objects as we have done, and now set the DataContext of their container,

DetailsGrid.DataContext = selectedBook

Run the program and watch the magic, shown in Figure 7-27

Figure 7-27. Run the program, see the details (Click to view full-size image)

How many authors?

Two quick features to point out. In the first row the prompt was created in a stack panel

<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Bottom">
              
<TextBlock x:Name="NumAuthorsPrompt"  Text="{Binding NumAuthors, Mode=OneWay}"
VerticalAlignment="Bottom" HorizontalAlignment="Right" FontFamily="Verdana"
FontSize="18" FontWeight="Medium" Margin="0,0,15,15"/>
              
<TextBlock x:Name="AuthorsPrompt"  Text="Authors"
VerticalAlignment="Bottom" HorizontalAlignment="Right" FontFamily="Verdana"
FontSize="18" FontWeight="Medium" Margin="0,0,15,15"/>
 
</StackPanel>

The first TextBlock is bound to the mysterious property NumAuthors. We know that all of these bindings are to the Book object; does the Book actually have a property NumAuthors that tells how many authors the book has?

A quick check of Book shows that indeed this computation has been encapsulated in the Book class (where it belongs):

Public ReadOnly Property NumAuthors() As String
     Get
         Return privateAuthors.Count.ToString()
     End Get
 End Property

Finally, the slider control is initialized with the rating from the book (note the binding) but allows the user to set any value between 0 and 5 and that value is written back to the business object. This is accomplished by making the binding two way,

<Slider x:Name="RatingSlider" Width="150" VerticalAlignment="Bottom"
    HorizontalAlignment="Left"  Margin="5,0,5,0" LargeChange="1.0" SmallChange="0.1" Minimum="0" Maximum="5.0"
    ValueChanged="RatingSlider_ValueChanged" Value="{Binding Rating, Mode=TwoWay}" />

Because the slider has no intrinsic display of its value, we've added a textBlock that will display the Slider's value

<StackPanel Grid.Row="5"  Margin="0,0,0,15"   Grid.Column="1" Orientation="Horizontal" >
    
 <Slider x:Name="RatingSlider" Width="150" VerticalAlignment="Bottom"
    HorizontalAlignment="Left"  Margin="5,0,5,0" LargeChange="1.0" SmallChange="0.1" Minimum="0" Maximum="5.0"
    ValueChanged="RatingSlider_ValueChanged" Value="{Binding Rating, Mode=TwoWay }" />
   
<TextBlock x:Name="SliderValueDisplay" Margin="5,0,0,0"
VerticalAlignment="Bottom"
HorizontalAlignment="Left" FontFamily="Verdana" FontSize="18" />
   
</StackPanel>

Note that the TextBlock is not bound to the Slider's value. Instead it is updated as a result of the implementation of the Slider's ValueChanged event, ensuring that it is continually updated when the user moves the slider, a much more satisfying user experience,

Private Sub RatingSlider_ValueChanged(ByVal sender As System.Object, ByVal e As
       System.Windows.RoutedPropertyChangedEventArgs(Of System.Double))
    Dim s As Slider = TryCast(sender, Slider)
    SliderValueDisplay.Text = s.Value.ToString("N")
 End Sub

Briefly, we must cast the sender (which is of type object) to type Slider so that we can access the Slider's value property, which we then render as a string by calling ToString, passing in the standard formatting string "N" which forces the string to render with 2 decimal places so that we don't end up with a book rating of 4.7383928

[1] This is an incredibly obscure pun. In 1957 Francis Crick and James D. Watson suggested the first accurate model of the Deoxyribonucleic acid (DNA) structure. DNA (Distributed interNet Application Architecture) was a precursor to .NET, and the image shown above is the lambda repressor transcription factor bound to a DNA target, which especially makes me chuckle.

Leave a Comment Comments (0) RSS Feed

  • 1

You must be logged in to leave a comment. Click here to log in.

Microsoft Communities