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()
{
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()
{
Items.Clear();
if(source is DataSet)
{
DataSet ds = source as DataSet;
DataTable dt = ds.Tables[0];
if(dt!=null)
{
cm = (CurrencyManager)BindingContext[ds,
ds.Tables[0].TableName];
cm.CurrentChanged +=new EventHandler(cm_CurrentChanged);
dv = (DataView)cm.List;
Columns.Add(DataMember, ClientRectangle.Width - 17,
HorizontalAlignment.Left);
foreach(DataRow dr in dt.Rows)
{
ListViewItem lvi = new ListViewItem(
dr[DataMember].ToString());
lvi.Tag = dr;
Items.Add(lvi);
}
Sorting = SortOrder.Ascending;
dv.Sort = this.Columns[0].Text + " ASC";
}
}
else
{
cm = null;
}
}
Events
There are three basic events that I have written for this control.
- 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];
}
}
}
- 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";
}
}
- 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