List Binding in WPF

This page is not so much a tutorial or sample as it is a record of how I learned how to do this one tiny corner of data binding. It was not easy -- it never is with Microsoft technology. List binding is a lot different in Windows Presentation Foundation (WPF), the technology formerly known as Avalon.

WPF is big ... really big. Not as big as space, but still really, really big. So to understand data binding, you have to know a lot of other stuff already. Here is a short, incomplete list of the things that are different about WPF

Anyway, there are a lot of other changes, but the foregoing are the ones that most directly impinge on this code.

The Goal of the Program

This demo shows you (and the later me) how to bind a list of objects to a ListBox control. First, we want a window with an empty list box and a bunch of command buttons, like this:

Screen shot of empty window

You will notice that the layout is fairly ugly, as befits a hand-coded design. I could have made it prettier, but at the expense of code that would have distracted your attention away from the meat of the demo. So what we get is the default behavior of many of the elements, which in the case of buttons is to squish themselves together much like commuters in Tokyo.

Here is the Xaml code that produces this window. It has nothing in it to do with data binding yet.

<Window x:Class="Bind02.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Bind02" Height="300" Width="300"
>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <ListBox/>
        <StackPanel Grid.Column="1">
            <Button Click="OnLoad">_Load</Button>
            <Button Click="OnSave">_Save</Button>
            <Button Click="OnAdd">_Add</Button>
            <Button Click="OnEdit">_Edit</Button>
            <Button Click="OnDelete">_Delete</Button>
            <Button Click="OnExit">E_xit</Button>
        </StackPanel>
    </Grid>
</Window>

The Grid control is a type of layout panel. This code defines two columns in the grid (and one row by default). The width of the first column is star, which means it takes up all the remaining space after the second column gets done. The width of the second column is Auto, which means it is sized to fit its contents.

The first column contains the list box. The second column contains a StackPanel, another type of layout panel. Nesting, nesting, nesting.... The stack panel contains a stack of buttons. Each button is sized to its contents, but when put into a stack panel, the widths of the buttons are made all equal to the widest one, which is the Delete button. The stack panel is then sized to its contents, which is as tall as its grid cell and as wide as its widest child. It's all very tedious and has nothing to do with data binding, but I thought you might like to know.

Also, notice that the button text contains underscore characters to specify the accelerator keys. This time it is not an arbitrary decision just to be different from all the Windows interfaces of the past, which used ampersands (&) for that purpose. In this case, since the names are embedded in XML, and XML requires that ampersands be escaped, it makes more sense to use underscores.

Now, let's look at the code-behind file for what we have so far:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
    }

    void OnLoad(object sender, RoutedEventArgs e)
    {
    }

    void OnSave(object sender, RoutedEventArgs e)
    {
    }

    void OnAdd(object sender, RoutedEventArgs e)
    {
    }

    void OnEdit(object sender, RoutedEventArgs e)
    {
    }

    void OnDelete(object sender, RoutedEventArgs e)
    {
    }

    void OnExit(object sender, RoutedEventArgs e)
    {
        Close();
    }
}

The call to InitializeComponent is required, but I'm not going there in this demo. Just take my word. The handlers take routed event arguments instead of plain event arguments. I'm not going their, either.

The Data We Want to Bind

Pretend we're writing a catalog of our records. As a tiny, small part of that program, we want to display a list of songs. They'll go in the list box, and we'll be able to do the standard CRUD on them. This demo will skip over all the mechanics of the user interface to get the information needed to do these things, and the buttons will implement the minimal hard-coded actions to demonstrate how it works. However, it will have some extraneous code to read and write the list of songs from and to the disk. I apologize for this distraction, but I wanted to be sure to demonstrate that the data were actually being changed, not just the user interface. So let's get this final distraction out of the way before we continue with the real data binding stuff.

Here is the class that represents a song. Of course, in real life, there would be more fields, but not in a demo:

class Song
{
    private string title;
    private TimeSpan length;

    public string Title
    {
        get { return title; }
        set { title = value; }
    }

    public TimeSpan Length
    {
        get { return length; }
        set { length = value; }
    }

    public static Song Load(string record)
    {
        string[] fields = record.Split('/');
        Song song = new Song();
        song.Title = fields[0];
        song.Length = TimeSpan.Parse(fields[1]);
        return song;
    }

    public string Save()
    {
        return String.Format("{0}/{1}", title, length);
    }
}

As you can see, we're also skimping egregiously on the error handling.

Here is the SongData.txt data file:

Hard Travelin'/0:2:31
Howdjadoo/0:1:39
Railroad Blues/0:3:13

Now we get to the list. This is just plain vanilla code that is based on Collection<T> to do the list work and adds simple loading and saving code.

class SongList : Collection<Song>
{
    public static SongList Load(string path)
    {
        SongList result = new SongList();

        using (StreamReader reader = File.OpenText(path))
        {
            string record;

            while ((record = reader.ReadLine()) != null)
            {
                Song song = Song.Load(record);
                result.Add(song);
            }
        }
        return result;
    }

    public void Save(string path)
    {
        using (StreamWriter writer = File.CreateText(path))
        {
            foreach (Song song in this)
            {
                string record = song.Save();
                writer.WriteLine(record);
            }
        }
    }
}

Now, presumably, if we were good boys and girls, we would have written and run our unit tests to ensure that the Song and SongList classes worked to our expectations. I was naughty and skipped that step. Now, how are we going to hook this list up to that there list box?

Finally We Start Binding

Let's start with the handler for the Load button. In the Xaml we have to give the list box a name so we can refer to it in the code-behind file and we have to set its binding. Here's what we get after the changes:

<Window x:Class="Bind02.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Bind02" Height="300" Width="300"
>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <ListBox Name="listBox" ItemsSource="{Binding}"/>
        <StackPanel Grid.Column="1">
            <Button Click="OnLoad">_Load</Button>
            <Button Click="OnSave">_Save</Button>
            <Button Click="OnAdd">_Add</Button>
            <Button Click="OnEdit">_Edit</Button>
            <Button Click="OnDelete">_Delete</Button>
            <Button Click="OnExit">E_xit</Button>
        </StackPanel>
    </Grid>
</Window>

Next, we have to fill in the handler in the code-behind file. Before coding the handler for OnLoad, we'll hard code a couple paths in this class like this:

const string inputPath = @"SongData.txt";
const string outputPath = @"SavedData.txt";

And now here is the code you've all been waiting for:

void OnLoad(object sender, RoutedEventArgs e)
{
    SongList songs = SongList.Load(inputPath);
    listBox.DataContext = songs;
}

So we're setting the DataContext property of the list box. Back in the Xaml file we set the ItemsSource property of the list box to "{Binding}". Those two properties are what hook up the list to the control. It's like the string "{Binding}" is interpreted to mean the same as DataContext. Let's see what this code gives us.

Screen shot of window with class names

Oops! I forgot that the list box, unless told otherwise, displays whatever the ToString method of the objects return. And unless you override ToString in your class, it just returns the name of the class. Not too informative. So let's add an override in the Song class:

public override string ToString()
{
    return String.Format("{0} - {1}", title, length);
}

Now it looks a little better:

Screen shot of window with class names

Now we can fill in the empty handlers for the Save, Add, Edit, and Delete buttons.

void OnSave(object sender, RoutedEventArgs e)
{
    SongList songs = (SongList)listBox.DataContext;
    if (songs == null)
        return;
    songs.Save(outputPath);
}

void OnAdd(object sender, RoutedEventArgs e)
{
    SongList songs = (SongList)listBox.DataContext;
    if (songs == null)
        return;

    Song song = new Song();
    song.Title = "Talking Columbia";
    song.Length = new TimeSpan(0, 2, 28);

    songs.Add(song);
}

void OnEdit(object sender, RoutedEventArgs e)
{
    SongList songs = (SongList)listBox.DataContext;
    if (songs == null)
        return;
    if (songs.Count == 0)
        return;
    Song song = songs[0];
    song.Length += TimeSpan.FromMinutes(10);
}

void OnDelete(object sender, RoutedEventArgs e)
{
    SongList songs = (SongList)listBox.DataContext;
    if (songs == null)
        return;
    if (songs.Count < 2)
        return;
    songs.RemoveAt(1);
}

After recompiling with those changes, we run the application, and find that none of the new stuff seems to work.

But then we notice that there is a new file in our directory named SavedData.txt. When we open it up, we find that the saved data have all our changes. But the UI never got updated!

Of course! We need one of those special lists that are made especially for binding. In Windows Forms it was called BindingList<T>. But in WPF, it's called ObservableCollection<T>. So all we have to do is derive the SongList class from that:

class SongList : ObservableCollection<Song>

After recompiling with that change, we run the application again. This time the Add and Delete buttons update the UI, but the Edit button still doesn't seem to work. We check the SavedData.txt file, and it shows that the time has indeed been incremented by ten minutes, but it didn't show up in the list box.

So the list is getting updated, but the items on the list are not. We look in the dreaded MSDN documentation and find this in the topic Data Binding Overview about half way down:

Note that to fully support transferring data values from source objects to targets, each object in your collection that supports bindable properties must also implement the INotifyPropertyChanged interface.

So back to the Song class. First we add the interface to the list of implemented interfaces and add the event, which is the only member of this interface:

class Song : INotifyPropertyChanged
{
    // ...
    public event PropertyChangedEventHandler PropertyChanged;
    // ...
}

Then we add a helper function to raise the event:

protected void OnPropertyChanged(string info)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(info));
    }
}

And finally, we have to add a call to this function in each of the property setters:

public string Title
{
    get { return title; }
    set { title = value; OnPropertyChanged("Title"); }
}

public TimeSpan Length
{
    get { return length; }
    set { length = value; OnPropertyChanged("Length"); }
}

We recompile and run the application. No change.

Now what's going on? Haven't we done everything they told us to do? We go back to MSDN and read a bunch of stuff, but can't seem to find a clue. We're tired, so we call it a night.

Those Last Few Inches

Next day, refreshed and ready to go, we start thinking through what must be the sequence of events that take place when we hit the Edit button. The edit button handler calls the getter on the Length of the Song, increments the time span, and then calls the setter. The setter sets the value and calls OnPropertyChanged, which raises the event.

But what receives the event? This is all hidden in the bowels of the data binding code we are not privy to. And the documentation just says you need to implement the interface, and Binding will take care of the rest.

If Binding did take care of the rest, why is it not calling the ToString method of the Song again? If it did that, it would certainly get the correct string, right?

Wait a minute. We're getting the list and the items mixed up again. The list box is bound to the list, but nothing is bound to the items. How can we get something bound to the items?

There must be a sample that does this. We find a sample that uses code like this:

<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=FirstName}"/>

And that was inside a thing called a data template. So off we go to find more information about data templates. It seems you can't do any little thing without knowing everything about WPF. And WPF is big.

Well, let's just do like those old movies do and show a calendar with the pages flying off to suggest the passage of great quantities of time. Finally, we put the following additions on the Xaml file:

<Window x:Class="Bind02.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Bind02" Height="300" Width="300"
>
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="SongItemTemplate">
                <WrapPanel>
                    <TextBlock Text="{Binding Path=Title}"/>
                    <TextBlock>. (</TextBlock>
                    <TextBlock Text="{Binding Path=Length}"/>
                    <TextBlock>)</TextBlock>
                </WrapPanel>
            </DataTemplate>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <ListBox Name="listBox" ItemsSource="{Binding}"
            ItemTemplate="{StaticResource SongItemTemplate}"/>
        <StackPanel Grid.Column="1">
            <Button Click="OnLoad">_Load</Button>
            <Button Click="OnSave">_Save</Button>
            <Button Click="OnAdd">_Add</Button>
            <Button Click="OnEdit">_Edit</Button>
            <Button Click="OnDelete">_Delete</Button>
            <Button Click="OnExit">E_xit</Button>
        </StackPanel>
    </Grid>
</Window>

What's with the WrapPanel? Why not just put the TextBlock objects in there? It turns out the data template is a content control, which is WPF-speak for something that can have only one child. The only thing I could think of that could have more than one child and seemed to apply to this case was a WrapPanel. Nesting, nesting, nesting....

We recompile and run the application. It all works. It looks like this after loading, adding, editing, deleting, and saving:

Screen shot of final window

Download the source (4KB).

Updated on April 22, 2007