Thursday, January 03, 2008

Databinding a Generic List to a WPF ListView

Inevitably there will come a time when you will want to start using the WPF framework for your business apps, and that means that you'll need a grid.  The WPF grid of choice is the ListView control. 

The ListView control is extremely powerful, flexible and customizable.  It's also way complicated!  On the Microsoft campus, in the basement, past the closet where they hide the Steve Balmer bobble-heads and the unused copies of Microsoft BOB, from a darkened cubicle, a developer is laughing at us.

Anyway, there are not a lot of examples of how to programmatically bind a generic list to a ListView control.  So, here's mine.

First let's take a look at the class that we want to add to the collection:

namespace MyNameSpace
{
public class Item
{
public string Description { get; set; }
public string Name { get; set; }

public Item(string name, string description)
{
Name = name;
Description = description;
}
}
}


It's just a simple class with a couple of public accessors.  Now lets turn our attention to the xaml file:




   1:  <Window x:Class="DMG.AnyStream.ProfileEditor.Main"


   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


   4:      xmlns:tools="clr-namespace:MyNamespace;assembly=MyAssemblyName"


   5:      Title="Sample Application" Height="600" Width="800">


   6:      <Window.Resources>


   7:          <DataTemplate x:Key="NameHeader">


   8:              <StackPanel Orientation="Horizontal">


   9:                  <TextBlock Margin="10,0,0,0" Text="Name" VerticalAlignment="Center" />


  10:              </StackPanel>


  11:          </DataTemplate>


  12:          <DataTemplate x:Key="NameCell" DataType="Profile">


  13:              <StackPanel Orientation="Horizontal">


  14:                  <TextBlock>


  15:                      <TextBlock.Text>


  16:                          <Binding Path="Name" />


  17:                      </TextBlock.Text>


  18:                  </TextBlock>


  19:              </StackPanel>


  20:          </DataTemplate>


  21:          <DataTemplate x:Key="DescriptionHeader">


  22:              <StackPanel Orientation="Horizontal">


  23:                  <TextBlock Margin="10,0,0,0" Text="Description" VerticalAlignment="Center" />


  24:              </StackPanel>


  25:          </DataTemplate>


  26:          <DataTemplate x:Key="DescriptionCell" DataType="Profile">


  27:              <StackPanel Orientation="Horizontal">


  28:                  <TextBlock>


  29:                      <TextBlock.Text>


  30:                          <Binding Path="Description" />


  31:                      </TextBlock.Text>


  32:                  </TextBlock>


  33:              </StackPanel>


  34:          </DataTemplate>


  35:      </Window.Resources>


  36:      <Grid>


  37:          <ListView Margin="0,22,0,0" Name="lvItems" ItemsSource="{Binding}" 


  38:                    IsSynchronizedWithCurrentItem="True" 


  39:                    SelectionMode="Single" >


  40:              <ListView.View>


  41:                  <GridView>


  42:                      <GridViewColumn Header="Name" HeaderTemplate="{StaticResource NameHeader}" CellTemplate="{DynamicResource NameCell}" Width="200px"  />


  43:                      <GridViewColumn Header="WatchFolderPath" HeaderTemplate="{StaticResource DescriptionHeader}" CellTemplate="{DynamicResource DescriptionCell}" Width="400px" />


  44:                  </GridView>


  45:              </ListView.View>


  46:          </ListView>


  47:      </Grid>


  48:  </Window>


Take a look at line 4. If your class lives in an assembly outside of your WPF project, like mine, then you will need to add this line to declare it to the xaml.



The Windows resources section starts at line six. Here, I'm just defining some templates that will tell the ListView how to present my information.  Without these, the control will show the results from typeof() for each item.  Not good.  Inside the "NameCell" template, I'm just creating a TextBlock control.  There's nothing to stop you from inserting images, checkboxes, radio buttons, etc. Inside the TextBlock, I create a new binding object and give it the name of the public accessor that I want to bind to this column.



The actual ListView control starts down on line 37. Notice the GridView section starting at line 41.  Here is where we define our columns. All we have to do is point to the templates that we created in the Resources section.  You could define them here, but I think that this is cleaner.>



Finally, we need to bind a list to control.  I do that in the code behind for the form:



public partial class Main : Window
{
public Main()
{
List<Item> items = new List<Items>();
items.add(new Item("Item1", "Item1 Description"));
items.add(new Item("Item2", "Item2 Description"));
items.add(new Item("Item3", "Item3 Description"));

lvItems.ItemsSource = profiles;
}
}



It took me forever to figure this out, so I hope that it saves someone some headache and/or chest pains. I know what you are saying, "I only work on web apps."  I understand that this doesn't seem that important right now, but in a recent interview with Redmond Developer News, Scott Guthrie is quoted with:



"When you look at the next public preview of Silverlight that we're doing, all of those features that are core to WPF are built-in, and there's more than just the XAML in common. You can actually take your core UI code, working with controls and databinding and things like that, and use that seamlessly in the Windows app and in the Office app and in the RIA [rich Internet application] app."



How's that for cool?




4 comments:

Anonymous said...

Thank's a lot - you made my day. Pretty coooaal!

Anonymous said...

Where are you getting profiles?

Christian said...

Thank you very much for this post. The first one that really gave a working example out of the usual XML-context Microsoft uses for all examples.

Anonymous said...

Thanks for the good sample.
Problem: No runtime update function.
Here are some corrections:

List items = new List();
=>
List items = new List();

items.add => items.Add

lvItems.ItemsSource = profiles;
=>
lvItems.ItemsSource = items;