Wrox Home  
Search
Professional C# 2005 with .NET 3.0
by Christian Nagel, Bill Evjen, Jay Glynn, Karli Watson, Morgan Skinner
June 2007, Paperback


Excerpt From Professional C# 2005 with .NET 3.0

Windows Presentation Foundation (WPF) Data Binding with C# 2005

by Christian Nagel

Windows Presentation Foundation (WPF) is one of the three major extensions of .NET Framework 3.0. WPF is a new library to create the UI for smart client applications. While the Windows Forms controls are based on native Windows controls that make use of Window handles that are based on screen pixels, WPF is based on DirectX. The application is no longer using Window handles, it is easy to resize the UI, and support for sound and video is included.

This article requires.NET Framework 3.0 and the .NET Framework 3.0 Extensions for Visual Studio 2005.

Windows Forms 2.0 data binding has a lot of improvements compared to 1.0. WPF data binding takes another huge step forward. This article gives you a good start in data binding with WPF and discusses these Simple Object Binding, Object Data Provider, and List Binding. In Chapter 31, "Windows Presentation Foundation," of the new book, Professional C# 2005 with .NET 3.0 (Wrox, 2007, ISBN: 978-0-470-12472-7), we also discuss Binding with XAML and Binding to XML.

Overview

With WPF data binding, the target can be any dependency property of a WPF element, and every property of a CLR object can be the source. Because a WPF element is implemented as a .NET class, every WPF element can also be the source. See Figure 1 for the connection between the source and the target. The Binding object defines the connection.


Figure 1

Binding supports several binding modes between the target and source. Binding can be one-way where the source information goes to the target, but if the user changes information in the user interface, the source does not get updated. For updates to the source two-way binding is required.

The following table shows the binding modes and their requirements.

Binding Mode Description
One-time Binding goes from the source to the target and occurs only once when the application is started or the data context changes. Here you get a snapshot of the data.
One-way Binding goes from the source to the target. This is useful for read-only data as it is not possible to change the data from the user interface. To get updates to the user interface the source must implement the interface INotifyPropertyChanged.
Two-way With a two-way binding the user can make changes to the data from the UI. Binding occurs in both directions - from the source to the target and from the target to the source.
One-way-to-source With one-way-to-source binding, if the target property changes, the source object gets updated.

Simple Object Binding

For binding to CLR objects, with the .NET classes you just have to define properties as shown with the class Book and the properties Title, Publisher, Isbn and Authors:

   public class Book
   {
      public Book(string title, string publisher, string isbn, 
            params string[] authors)
      {
         this.title = title;
         this.publisher = publisher;
         this.isbn = isbn;
         foreach (string author in authors)
         {
            this.authors.Add(author);
         }
      }
      public Book()
         : this("unknown", "unknown", "unknown")
      {
      }
      private string title;
      public string Title
      {
         get { return title; }
         set { title = value; }
      }
      private string publisher;
      public string Publisher
      {
         get { return publisher; }
         set { publisher = value; }
      }
      private string isbn;
      public string Isbn
      {
         get { return isbn; }
         set { isbn = value; }
      }
      
      public override string ToString()
      {
         return title;
      }
      private readonly List<string> authors = new List<string>();
      public string[] Authors
      {
         get { return authors.ToArray(); }
      }
   }

In the user interface, several labels and TextBox controls are defined to display book information. Using Binding markup extensions the TextBox controls are bound to the properties of the Book class. With the Binding markup extension nothing more than the Path property is defined to bind it to the property of the Book class. There's no need to define a source because the source is defined by assigning the DataContext as you can see in the code-behind that follows. The mode is defined by its default with the TextBox element, and this is two-way binding.

<Window x:Class="ObjectBindingSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Object Binding Sample" Height="300" Width="340"
    >
    <Grid Name="bookGrid" Margin="5,5,5,5" >
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="30*" />
        <ColumnDefinition Width="70*" />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="50" />
        <RowDefinition Height="50" />
        <RowDefinition Height="50" />
        <RowDefinition Height="50" />
        <RowDefinition Height="50" />
       </Grid.RowDefinitions>
      <Label Grid.Column="0" Grid.Row="0">Title:</Label>
      <TextBox Width="200" Height="30" Grid.Column="1" Grid.Row="0" 
            Text="{Binding Title}" />
      
      <Label Grid.Column="0" Grid.Row="1">Publisher:</Label>
      <TextBox Width="200" Height="30" Grid.Column="1" Grid.Row="1" 
            Text="{Binding Publisher}" />
      <Label Grid.Column="0" Grid.Row="2">ISBN:</Label>
      <TextBox Width="200" Height="30" Grid.Column="1" Grid.Row="2" 
            Text="{Binding Isbn}" />
      <Button Margin="5,5,5,5" Grid.Column="1" Grid.Row="4" Click="OnOpenBookDialog" 
            Name="bookButton">Open Dialog</Button>
        
    </Grid>
</Window>

With the code-behind a new Book object is created, and the book is assigned to the DataContext property of the Grid control. DataContext is a dependency property that is defined with the base class FrameworkElement. Assigning the DataContext with the Grid control every element in the Grid control has a default bind to the same data context.

   public partial class Window1 : System.Windows.Window
   {
      private Book book1 = new Book();
      public Window1()
      {
         InitializeComponent();
         book1.Title = "Professional C# 2005";
         book1.Publisher = "Wrox Press";
         book1.Isbn = "978-0764575341";
         bookGrid.DataContext = book1;
      }
   }

Starting the application you can see the bound data in Figure 2.


Figure 2

To demonstrate the two-way binding - with changes of the input to the WPF element the change is reflected inside the CLR object - the OnOpenBookDialog() method is implemented. This method is assigned to the Click event of the bookButton as you can see in the XAML code. With the implementation a message box pops up to show the current title and ISBN number of the book1 object. Figure 3 shows the output from the message box after a change to the input was done during runtime.

      void OnOpenBookDialog(object sender, RoutedEventArgs e)
      {
         string message = book1.Title;
         string caption = book1.Isbn;
         MessageBox.Show(message, caption);
      }

Figure 3

Object Data Provider

Instead of defining the object in code-behind, you can also define an object instance with XAML. To make this possible you have to reference the namespace with the namespace declarations in the XML root element. The XML attribute xmlns:src="clr-namespace:Wrox.ProCsharp.WPF" assigns the .NET namespace Wrox.ProCSharp.WPF to the XML namespace alias src.

One object of the Book class is now defined with the Book element inside the Window resources. By assigning values to the XML attributes Title and Publisher, you set the values of the properties from the class Book. x:Key="theBook" defines the identifier for the resource so you can reference the book object. With the TextBox element now the Source is defined with the Binding markup extension to reference the theBook resource.

As you can see, you can mix markup extensions. The StaticResource markup extension to reference the book resource is contained within the Binding markup extension.
<Window x:Class="ObjectBindingSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:src="clr-namespace:Wrox.ProCSharp.WPF" 
    Title="Object Binding Sample" Height="300" Width="340">
  <Window.Resources>
    <src:Book x:Key="theBook" Title="Professional C# with .NET 3.0" 
          Publisher="Wrox Press" />
  </Window.Resources>
<!-- ... -->
      <TextBox Width="200" Height="30" Grid.Column="1" Grid.Row="0" 
            Text="{Binding Source={StaticResource theBook}, Path=Title}" />
<!-- ... -->

If the .NET namespace to reference is in a different assembly, you have to add the assembly as well to the XML declaration:

xmlns:system="clr-namespace:System;assembly=mscorlib"

Instead of defining the object instance directly within XAML code, you can also define an object data provider that references a class to invoke a method. For use by the ObjectDataProvider it's best to create a factory class that returns the object to display as shown with the BookFactory class:

   public class BookFactory
   {
      private List<Book> books = new List<Book>();
      public BookFactory()
      {
         books.Add(new Book("Professional C# with .NET 3.0",
               "Wrox Press", "978-0-470-12472-7"));
      }
      public Book GetTheBook()
      {
         return books[0];
      }
   }

The ObjectDataProvider element can be defined with the resources section. The XML attribute ObjectType defines the name of the class, with MethodName you specify the name of the method that is invoked to get the book object:

  <Window.Resources>
    <ObjectDataProvider ObjectType="src:BookFactory" MethodName="GetTheBook" 
        x:Key="theBook">
    </ObjectDataProvider>
  </Window.Resources>

The properties you can specify with the ObjectDataProvider class are listed in the following table:

ObjectDataProvider Description
ObjectType The ObjectType property defines the type to create an instance of.
ConstructorParameters Using the ConstructorParameters collection you can add parameters to the class to create an instance.
MethodName The MethodName property defines the name of the method that is invoked by the object data provider.
MethodParameters With the MethodParameters property you can assign parameters to the method defined with the MethodName property.
ObjectInstance With the ObjectInstance property you can get and set the object that is used by the ObjectDataProvider class. For example, you can assign an existing object programmatically instead of defining the ObjectType so that an object is instantiated by the ObjectDataProvider.
Data With the Data property you can access the underlying object that is used for data binding. If the MethodName is defined, with the Data property you can access the object that is returned from the method defined.

List Binding

Instead of binding to simple objects usually binding to a list is more used. Binding to a list is very similar to binding to a simple object. You can assign the complete list to the DataContext from code-behind, or you can use an ObjectDataProvider that accesses an object factory that returns a list. With elements that support binding to a list (e.g. the ListBox), the complete list is bound. With elements that just support binding to one object (e.g. the TextBox), the current item is bound.

With the BookFactory class now a list of Book objects is returned:

   public class BookFactory
   {
      private List<Book> books = new List<Book>();
      public BookFactory()
      {
         books.Add(new Book("Professional C# with .NET 3.0",
               "Wrox Press", "978-0-470-12472-7", "Christian Nagel", "Bill Evjen",
               "Jay Glynn", "Karli Watson", "Morgan Skinner"));
         books.Add(new Book("Professional C# 2005",
               "Wrox Press", "978-0-7645-7534-1", "Christian Nagel", "Bill Evjen",
               "Jay Glynn", "Karli Watson", "Morgan Skinner", "Allen Jones"));
         books.Add(new Book("Beginning Visual C#",
               "Wrox Press", "978-0-7645-4382-1", "Karli Watson", "David Espinosa",
               "Zach Greenvoss", "Jacob Hammer Pedersen", "Christian Nagel", 
               "John D. Reid", "Matthew Reynolds", "Morgan Skinner", "Eric White"));
         books.Add(new Book("ASP.NET Professional Secrets",
               "Wiley", "978-0-7645-2628-2", "Bill Evjen", "Thiru Thangarathinam",
               "Bill Hatfield", "Doug Seven", "S. Srinivasa Sivakumar", 
               "Dave Wanta", "Jason T. Roff"));
         books.Add(new Book("Design and Analysis of Distributed Algorithms",
               "Wiley", "978-0-471-71997-7", "Nicolo Santoro"));
      }
      public List<Book> GetBooks()
      {
         return books;
     }
  }

In the WPF code-behind constructor of the class Window1 a BookFactory is instantiated and the method GetBooks() invoked to assign the Book array with the DataContext of the Window1 instance:

   public partial class Window1 : System.Windows.Window
   { 
      private BookFactory factory = new BookFactory();
      public Window1()
      {
         InitializeComponent();
         this.DataContext = factory.GetBooks();
      }
   }

In XAML you just need a control that supports lists such as the ListBox and bind the ItemsSource property as shown:

<Window x:Class="ListBindingSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="List Binding Sample" Height="300" Width="518"
    >
  <DockPanel>
    <Grid >
      <Grid.ColumnDefinitions>
        <ColumnDefinition />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
      </Grid.RowDefinitions>
      <ListBox HorizontalAlignment="Left" Margin="5,5,5,5" Grid.RowSpan="4" 
          Width="200" Grid.Row="0" Grid.Column="0" Name="booksList" 
          ItemsSource="{Binding}" />
    </Grid>
  </DockPanel>
</Window>

Because the Window has the Book array assigned to the DataContext, and the ListBox is placed within the Window, the ListBox shows all books with the default template, as shown in Figure 4.


Figure 4

There are many additional ways you can work with the Listbox layout and present additional information. chapter 31 "Windows Presentation Foundation" of the book Professional C# 2005 with .NET 3.0 also discusses defining templates that can be used for ListBox styling, binding to detail information about a selected item in the list, and adding list items dynamically.

This article is excerpted from Chapter 31, "Windows Presentation Foundation," in the book Professional C# 2005 with .NET 3.0 (Wrox, 2007, ISBN: 978-0-470-12472-7), by Christian Nagel, Bill Evjen, Jay Glynn, Karli Watson, and Morgan Skinner. Christian Nagel is software architect and developer, associate of thinktecture, who offers training and consulting on how to design and develop Microsoft .NET solutions. He looks back to more than 20 years of experience as a developer and software architect. Christian started his computing career with PDP 11 and VAX/VMS platforms, covering a variety of languages and platforms. Since the year 2000 - when .NET was just a technology preview - he has been working with various .NET technologies to build distributed business solutions. With his profound knowledge of Microsoft technologies, he has written numerous .NET books; is certified as Microsoft Certified Trainer (MCT) and Professional Developer (MCPD) for Windows-, Web- and Enterprise Applications; and he is Microsoft Regional Director and MVP for ASP.NET. Christian is a speaker at international conferences (TechEd, TechDays), and supports .NET user groups with INETA Europe (International .NET Association). You can contact Christian via his Web sites, http://www.christiannagel.com and http://www.thinktecture.com.