Follow @PDSAInc March 2010 - Posts - Paul Sheriff's Blog for the Real World

Paul Sheriff's Blog for the Real World

This blog is to share my tips and tricks garnered over 25+ years in the IT industry

Paul's Favorites

March 2010 - Posts

WPF ListView as a DataGrid – Part 2

In my last blog post I showed you how to create GridViewColumn objects on the fly from the meta-data in a DataTable. By doing this you can create columns for a ListView at runtime instead of having to pre-define each ListView for each different DataTable. Well, many of us use collections of our classes and it would be nice to be able to do the same thing for our collection classes as well. This blog post will show you one approach for using collection classes as the source of the data for your ListView.
 

Figure 1: A List of Data using a ListView

Load Property Names
You could use reflection to gather the property names in your class, however there are two things wrong with this approach. First, reflection is too slow, and second you may not want to display all your properties from your class in the ListView.

Instead of reflection you could just create your own custom collection class of PropertyHeader objects. Each PropertyHeader object will contain a property name and a header text value at a minimum. You could add a width property if you wanted as well. All you need to do is to create a collection of property header objects where each object represents one column in your ListView. Below is a simple example:

PropertyHeaders coll = new PropertyHeaders();

coll.Add(new PropertyHeader("ProductId", "Product ID"));
coll.Add(new PropertyHeader("ProductName", "Product Name"));
coll.Add(new PropertyHeader("Price", "Price"));

Once you have this collection created, you could pass this collection to a method that would create the GridViewColumn objects based on the information in this collection. Below is the full code for the PropertyHeader class. Besides the PropertyName and Header properties, there is a constructor that will allow you to set both properties when the object is created.

C#
public class PropertyHeader
{
  public PropertyHeader()
  {
  }

  public PropertyHeader(string propertyName, string headerText)
  {
    PropertyName = propertyName;
    HeaderText = headerText;
  }

  public string PropertyName { get; set; }
  public string HeaderText { get; set; }
}

VB.NET
Public Class PropertyHeader
  Public Sub New()
  End Sub

  Public Sub New(ByVal propName As String, ByVal header As String)
    PropertyName = propName
    HeaderText = header
  End Sub

  Private mPropertyName As String
  Private mHeaderText As String

  Public Property PropertyName() As String
    Get
      Return mPropertyName
    End Get
    Set(ByVal value As String)
      mPropertyName = value
    End Set
  End Property

  Public Property HeaderText() As String
    Get
      Return mHeaderText
    End Get
    Set(ByVal value As String)
      mHeaderText = value
    End Set
  End Property
End Class

You can use a Generic List class to create a collection of PropertyHeader objects as shown in the following code.

C#
public class PropertyHeaders : List<PropertyHeader>
{
}

VB.NET
Public Class PropertyHeaders
  Inherits List(Of PropertyHeader)
End Class

Create Property Header Objects

You need to create a method somewhere that will create and return a collection of PropertyHeader objects that will represent the columns you wish to add to your ListView prior to binding your collection class to that ListView. Below is a sample method called GetProperties that builds a list of PropertyHeader objects with properties and headers for a Product object.

C#
public PropertyHeaders GetProperties()
{
  PropertyHeaders coll = new PropertyHeaders();

  coll.Add(new PropertyHeader("ProductId", "Product ID"));
  coll.Add(new PropertyHeader("ProductName", "Product Name"));
  coll.Add(new PropertyHeader("Price", "Price"));

  return coll;
}

VB.NET
Public Function GetProperties() As PropertyHeaders
  Dim coll As New PropertyHeaders()

  coll.Add(New PropertyHeader("ProductId", "Product ID"))
  coll.Add(New PropertyHeader("ProductName", "Product Name"))
  coll.Add(New PropertyHeader("Price", "Price"))

  Return coll
End Function

WPFListViewCommon Class

Now that you have a collection of PropertyHeader objects you need a method that will create a GridView and a collection of GridViewColumn objects based on this PropertyHeader collection. Below is a static/Shared method that you might put into a class called WPFListViewCommon.

C#
public static GridView CreateGridViewColumns(
  PropertyHeaders properties)
{
  GridView gv;
  GridViewColumn gvc;

  // Create the GridView
  gv = new GridView();
  gv.AllowsColumnReorder = true;

  // Create the GridView Columns
  foreach (PropertyHeader item in properties)
  {
    gvc = new GridViewColumn();
    gvc.DisplayMemberBinding = new Binding(item.PropertyName);
    gvc.Header = item.HeaderText;
    gvc.Width = Double.NaN;
    gv.Columns.Add(gvc);
  }

  return gv;
}

VB.NET
Public Shared Function CreateGridViewColumns( _
   ByVal properties As PropertyHeaders) As GridView
  Dim gv As GridView
  Dim gvc As GridViewColumn

  ' Create the GridView
  gv = New GridView()
  gv.AllowsColumnReorder = True

  ' Create the GridView Columns
  For Each item As PropertyHeader In properties
    gvc = New GridViewColumn()
    gvc.DisplayMemberBinding = New Binding(item.PropertyName)
    gvc.Header = item.HeaderText
    gvc.Width = [Double].NaN
    gv.Columns.Add(gvc)
  Next

  Return gv
End Function

Build the Product Screen

To build the window shown in Figure 1, you might write code like the following:

C#
private void CollectionSample()
{
  Product prod = new Product();

  // Setup the GridView Columns
  lstData.View = WPFListViewCommon.CreateGridViewColumns(
       prod.GetProperties());
  lstData.DataContext = prod.GetProducts();
}

VB.NET
Private Sub CollectionSample()
  Dim prod As New Product()

  ' Setup the GridView Columns
  lstData.View = WPFListViewCommon.CreateGridViewColumns( _
       prod.GetProperties())
  lstData.DataContext = prod.GetProducts()
End Sub

The Product class contains a method called GetProperties that returns a PropertyHeaders collection. You pass this collection to the WPFListViewCommon’s CreateGridViewColumns method and it will create a GridView for the ListView. When you then feed the DataContext property of the ListView the Product collection the appropriate columns have already been created and data bound.

Summary

In this blog you learned how to create a ListView that acts like a DataGrid using a collection class. While it does take a little code to do this, it is an alternative to creating each GridViewColumn in XAML. This gives you a lot of flexibility. You could even read in the property names and header text from an XML file for a truly configurable ListView.

NOTE: You can download the complete sample code (in both VB and C#) at my website. http://www.pdsa.com/downloads. Choose Tips & Tricks, then "WPF ListView as a DataGrid – Part 2" from the drop-down.

Good Luck with your Coding,
Paul Sheriff

** SPECIAL OFFER FOR MY BLOG READERS **
Visit http://www.pdsa.com/Event/Blog for a free eBook on "Fundamentals of N-Tier".

 

Posted: Mar 29 2010, 11:04 AM by psheriff | with 1 comment(s)
Filed under: ,
Using a WPF ListView as a DataGrid

Many people like to view data in a grid format of rows and columns. WPF did not come with a data grid control that automatically creates rows and columns for you based on the object you pass it. However, the WPF Toolkit can be downloaded from CodePlex.com that does contain a DataGrid control. This DataGrid gives you the ability to pass it a DataTable or a Collection class and it will automatically figure out the columns or properties and create all the columns for you and display the data.
The DataGrid control also supports editing and many other features that you might not always need. This means that the DataGrid does take a little more time to render the data. If you want to just display data (see Figure 1) in a grid format, then a ListView works quite well for this task. Of course, you will need to create the columns for the ListView, but with just a little generic code, you can create the columns on the fly just like the WPF Toolkit’s DataGrid.

ListView as a DataGrid - Figure 1

Figure 1: A List of Data using a ListView


A Simple ListView Control
The XAML below is what you would use to create the ListView shown in Figure 1. However, the problem with using XAML is you have to pre-define the columns. You cannot re-use this ListView except for “Product” data.


<ListView x:Name="lstData"
          ItemsSource="{Binding}">
  <ListView.View>
    <GridView>
      <GridViewColumn Header="Product ID"
                      Width="Auto"
               DisplayMemberBinding="{Binding Path=ProductId}" />
      <GridViewColumn Header="Product Name"
                      Width="Auto"
               DisplayMemberBinding="{Binding Path=ProductName}" />
      <GridViewColumn Header="Price"
                      Width="Auto"
               DisplayMemberBinding="{Binding Path=Price}" />
    </GridView>
  </ListView.View>
</ListView>

So, instead of creating the GridViewColumn’s in XAML, let’s learn to create them in code to create any amount of columns in a ListView.

Create GridViewColumn’s From Data Table
To display multiple columns in a ListView control you need to set its View property to a GridView collection object. You add GridViewColumn objects to the GridView collection and assign the GridView to the View property. Each GridViewColumn object needs to be bound to a column or property name of the object that the ListView will be bound to. An ADO.NET DataTable object contains a collection of columns, and these columns have a ColumnName property which you use to bind to the GridViewColumn objects.

Listing 1 shows a sample of reading and XML file into a DataSet object. After reading the data a GridView object is created. You can then loop through the DataTable columns collection and create a GridViewColumn object for each column in the DataTable. Notice the DisplayMemberBinding property is set to a new Binding to the ColumnName in the DataTable.

C#
private void FirstSample()
{
  // Read the data
  DataSet ds = new DataSet();
  ds.ReadXml(GetCurrentDirectory() + @"\Xml\Product.xml");
 
  // Create the GridView
  GridView gv = new GridView();

  // Create the GridView Columns
  foreach (DataColumn item in ds.Tables[0].Columns)
  {
    GridViewColumn gvc = new GridViewColumn();
    gvc.DisplayMemberBinding = new Binding(item.ColumnName);
    gvc.Header = item.ColumnName;
    gvc.Width = Double.NaN;
    gv.Columns.Add(gvc);
  }

  // Setup the GridView Columns
  lstData.View = gv;
  // Display the Data
  lstData.DataContext = ds.Tables[0];
}

VB.NET
Private Sub FirstSample()
  ' Read the data
  Dim ds As New DataSet()
  ds.ReadXml(GetCurrentDirectory() & "\Xml\Product.xml")

  ' Create the GridView
  Dim gv As New GridView()

  ' Create the GridView Columns
  For Each item As DataColumn In ds.Tables(0).Columns
    Dim gvc As New GridViewColumn()
    gvc.DisplayMemberBinding = New Binding(item.ColumnName)
    gvc.Header = item.ColumnName
    gvc.Width = [Double].NaN
    gv.Columns.Add(gvc)
  Next

  ' Setup the GridView Columns
  lstData.View = gv
  ' Display the Data
  lstData.DataContext = ds.Tables(0)
End Sub
Listing 1: Loop through the DataTable columns collection to create GridViewColumn objects

A Generic Method for Creating a GridView
Instead of having to write the code shown in Listing 1 for each ListView you wish to create, you can create a generic method that given any DataTable will return a GridView column collection. Listing 2 shows how you can simplify the code in Listing 1 by setting up a class called WPFListViewCommon and create a method called CreateGridViewColumns that returns your GridView.

C#
private void DataTableSample()
{
  // Read the data
  DataSet ds = new DataSet();
  ds.ReadXml(GetCurrentDirectory() + @"\Xml\Product.xml");

  // Setup the GridView Columns
  lstData.View =
     WPFListViewCommon.CreateGridViewColumns(ds.Tables[0]);
  lstData.DataContext = ds.Tables[0];
}

VB.NET
Private Sub DataTableSample()
  ' Read the data
  Dim ds As New DataSet()
  ds.ReadXml(GetCurrentDirectory() & "\Xml\Product.xml")

  ' Setup the GridView Columns
  lstData.View = _
      WPFListViewCommon.CreateGridViewColumns(ds.Tables(0))
  lstData.DataContext = ds.Tables(0)
End Sub
Listing 2: Call a generic method to create GridViewColumns.

The CreateGridViewColumns Method
The CreateGridViewColumns method will take a DataTable as a parameter and create a GridView object with a GridViewColumn object in its collection for each column in your DataTable.

C#
public static GridView CreateGridViewColumns(DataTable dt)
{
  // Create the GridView
  GridView gv = new GridView();
  gv.AllowsColumnReorder = true;

  // Create the GridView Columns
  foreach (DataColumn item in dt.Columns)
  {
    GridViewColumn gvc = new GridViewColumn();
    gvc.DisplayMemberBinding = new Binding(item.ColumnName);
    gvc.Header = item.ColumnName;
    gvc.Width = Double.NaN;
    gv.Columns.Add(gvc);
  }

  return gv;
}

VB.NET
Public Shared Function CreateGridViewColumns _
  (ByVal dt As DataTable) As GridView
  ' Create the GridView
  Dim gv As New GridView()
  gv.AllowsColumnReorder = True

  ' Create the GridView Columns
  For Each item As DataColumn In dt.Columns
    Dim gvc As New GridViewColumn()
    gvc.DisplayMemberBinding = New Binding(item.ColumnName)
    gvc.Header = item.ColumnName
    gvc.Width = [Double].NaN
    gv.Columns.Add(gvc)
  Next

  Return gv
End Function
Listing 3: The CreateGridViewColumns method takes a DataTable and creates GridViewColumn objects in a GridView.

By separating this method out into a class you can call this method anytime you want to create a ListView with a collection of columns from a DataTable.

Summary
In this blog you learned how to create a ListView that acts like a DataGrid. You are able to use a DataTable as both the source of the data, and for creating the columns for the ListView. In the next blog entry you will learn how to use the same technique, but for Collection classes.

NOTE: You can download the complete sample code (in both VB and C#) at my website. http://www.pdsa.com/downloads. Choose Tips & Tricks, then "WPF ListView as a DataGrid" from the drop-down.

Good Luck with your Coding,
Paul Sheriff

** SPECIAL OFFER FOR MY BLOG READERS **
Visit http://www.pdsa.com/Event/Blog for a free eBook on "Fundamentals of N-Tier".

Posted: Mar 08 2010, 11:56 AM by psheriff | with 7 comment(s)
Filed under: ,
More Posts