Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Data binding a ListView

0.00/5 (No votes)
9 Apr 2005 2  
An overview on inheriting a ListView and implementing design-time data binding capability.

Sample Image - ListView_DataBinding.jpg

Introduction

The ListView control provided by Microsoft does not allow design-time data binding. This article gives a basic overview to get you started in implementing this functionality. It will also show you how to hide the base class� properties and to sort the currency manager when the list view is sorted.

Background

I am in the process of architecting a system under strict time constraints; hence I wanted to use Databinding for rapid development. However, I discovered that the ListView control cannot be bound to a DataSource at design-time. And just like most of you developers out there, after GOOGLING around I decided to post an article on this topic. What I am explaining in this article is quite basic, but it is meant to get you started. There are many modifications and changes that can be made to the source as it is not perfect. Hence, I will update the article as I add useful functionality to the control.

Disclaimer

Please note that the Visual Studio Generated Code is not listed below. You will find the code in its entirety in the source-code above. In addition, the code is not perfect, but it works. It is in bits-n-pieces to just get the point across. The code implemented for this article uses the Customer table from the NorthWind database in SQL Server 2000.

Getting Started

Before you run the code, make sure to change the ConnectionString property of the sqlConnection1 object to point to your database. You can modify the string below and replace the ConnectionString property in the designer.

sqlConnection1.ConnectionString = 
"workstation id=YOUR_WORKSTATION_ID;packet size=4096;" +
"integrated security=SSPI;data source=YOUR_SQL_SERVER;" + 
"persist security info=True;initial catalog=Northwind�

Once you replace the above string, you should be able to run the code snippet.

First and foremost, the required namespaces are listed below and also the fact that we inherit from the ListView control.

using System;
using System.ComponentModel;
using System.Data;
using System.Windows.Forms;

public class MyListView : System.Windows.Forms.ListView

Class Variables

We define two class variables. A CurrencyManager cm to track the current selected record and a DataView dv to sort the columns on the CurrencyManager.

CurrencyManager <CODE>cm = null;
DataView dv = null;

Constructor

The constructor is straight-forward. We add two event-handlers. One for the selected-index-changed event and the other for the column-header-click event.

public MyListView()
{
    // This call is required by the Windows.Forms Form Designer.

    InitializeComponent();

    base.SelectedIndexChanged +=new EventHandler(
                       MyListView_SelectedIndexChanged);
    base.ColumnClick +=new ColumnClickEventHandler(
                       MyListView_ColumnClick);
}

Properties

We add the DataSource property as an Object type. Then, we set the Bindable attribute to true which forces the property grid to display this member under the DataBindings collection. The TypeConverter attribute is set to �System.Windows.Forms.Design.DataSourceConverter, System.Design�. Now, the DataSource property shows all the available data sources at design-time in the property-grid. Finally, the Category attribute categorizes the property under the Data Category.

private Object source;

true)>
[TypeConverter("System.Windows.Forms.Design.DataSourceConverter, 
                                           System.Design")]
[Category("Data")]
public Object DataSource
{
    get
    {
        return source;
    }
    set
    {
        source = value;
    }
}

Next we add the DataMember property as a String type to store the Name of the field to be displayed in the ListView. Once again we set the Bindable attribute to true and Category to Data. Next, the Editor attribute is set to �System.Windows.Forms.Design.DataMemberFieldEditor, System.Design�, which shows us the columns associated with the DataSource. The RefreshProperties attribute is set to re-query all the properties and the refresh the view. Finally, on setting the DataMember property, we call the bind() method which takes care of populating the ListView.

private String data;
true)>
[Category("Data")]
[Editor("System.Windows.Forms.Design.DataMemberFieldEditor, 
  System.Design", "System.Drawing.Design.UITypeEditor, 
  System.Drawing")]
[RefreshProperties(RefreshProperties.All)]
public String DataMember
{
    get
    {
        return data;
    }
    set
    {
        data = value;
        bind();
    }
}

Then, we hide the base class' Sorting property from the property grid by re-defining the Sorting property with the exact signature and setting its browsable attribute to false. This hides the Sorting property at design-time, but it is still accessible through the code. Please note: this property does not need to be hidden. You can write additional code at startup to sort the list based on the design-time selection.

false)>
public new SortOrder Sorting
{
    get
    {
        return base.Sorting;
    }
    set
    {
        base.Sorting = value;
    }
}

Methods

Finally, this is where the rubber meets the road. The method bind() pulls out a DataRowCollection from the DataSource and populates the ListView.

private void bind()
{
    //Clear the existing list

    Items.Clear();                                            
    //This implementation assumes the DataSource is a DataSet

    if(source is DataSet)                                        
    {
        DataSet ds = source as DataSet;
        DataTable dt = ds.Tables[0];

        if(dt!=null)
        {
             
            //get the Binding Context for the Form

            cm = (CurrencyManager)BindingContext[ds, 
                              ds.Tables[0].TableName];           
           
            //add an event handler for the Currency Manager       

            cm.CurrentChanged +=new EventHandler(cm_CurrentChanged);                
           
            //Get the Currency Manager Collection as a DataView

            dv = (DataView)cm.List;                                

            //Create the column header based on the DataMember

            Columns.Add(DataMember, ClientRectangle.Width - 17, 
                                      HorizontalAlignment.Left); 
             //Add each Row as a ListViewItem        

            foreach(DataRow dr in dt.Rows)                           
            {
                ListViewItem lvi = new ListViewItem(
                                        dr[DataMember].ToString());
                lvi.Tag = dr;
                Items.Add(lvi);
            }
            //Set the Sorting property as Ascending

            Sorting = SortOrder.Ascending;  
                                      
            //Sort the DataView on the DataMember          

            dv.Sort = this.Columns[0].Text + " ASC";                        
        }
    }
    else
    {
        //If no source is defined, Currency Manager is null  

        cm = null;                                        
    }
}

Events

There are three basic events that I have written for this control.

  1. Selected-Index-Changed event handler is in charge of updating the Position property on the Currency Manager, which forces other bound controls to update to the newly selected record.
    private void MyListView_SelectedIndexChanged(object sender, EventArgs e)
    {
        if(this.SelectedIndices.Count>0)
        {               
            if(cm!=null)
            {
                 cm.Position = base.SelectedIndices[0];
            }
        }
    }
  2. A Column-Click event handler to sort the ListView and the DataView in sync with the Currency Manager�s list.
    private void MyListView_ColumnClick(object sender, ColumnClickEventArgs e)
    {
        if(Sorting==SortOrder.None || Sorting == SortOrder.Descending)
        {
            Sorting = SortOrder.Ascending;
            dv.Sort = this.Columns[0].Text + " ASC";
        }
        else if(Sorting==SortOrder.Ascending)
        {
            Sorting = SortOrder.Descending;
            dv.Sort = this.Columns[0].Text + " DESC";
        }
    }
  3. Currency Manager�s position change event handler selects the appropriate item in the ListView.
    private void cm_CurrentChanged(object sender, EventArgs e)
    {
        this.Items[cm.Position].Selected = true;
    }

Voila! We have now successfully implemented Data-Binding to a ListView control. The binding explained above is quite limited in its features, but you can further expand on the control to enhance the basic features.

Points of Interest

While writing this control, I discovered that a generic class can be written for binding custom controls. This would allow implementing binding on new controls really fast without much coding. I will submit an article about it when I get a chance.

History

  • April 2, 2005

    Submitted the original article

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here