Who am I? What am I doing here at CodeProject? Well, I�ll answer myself all these questions and others.
I am a ComboView
control. You might think, well what�s that? Well, I am a ComboBox
with the capability of displaying columnar items in a ListView
. Well, you might ask, there are lot of other similar controls available, then what�s so special about me. Nothing, the only difference being I am going to narrate to you how I had been brought to life, and on this journey to my creation, the lessons learned by my creator.
In the Beginning
It was all a few days back when a crazy developer thought of creating me. It was not out of love for me but out of greed. The greed to learn something, the greed to understand the intricacies of control development etc. etc. So, you see a man is very greedy. He does everything for his needs.
Well, let's see what all things were on his mind when he was planning to create me.
- He needs to learn control creation in .NET (specifically in C#).
- He needs to understand how to create a composite control.
- He needs to understand the way
ListView
works.
- He needs to understand the details of how to provide custom databinding.
- He needs to understand how to work with custom events.
- Etc�
Well, I need to admit one thing. I am not one of the best controls, not even closest to them. But I am happy because to certain degree, it was through me the creator of me understood all the above objectives (or I assume he understood).
Let's dissect myself, and on the journey, you and me can learn a few things.
Dissecting myself
First things first. In order for me to come into existence, I needed to have a parent. The developer decided that the UserControl
should be my parent.
So, I was inherited from System.Windows.Forms.Usercontrol
. That would imply that I would be having all the attributes and behavior of my parent (UserControl
).
public class MyCombo : System.Windows.Forms.UserControl
{
Since I am a ComboView
, in order for me to come into existence, I would require the support of a few other controls, specifically, a Button
, a ListView
and a TextBox
.
private System.Windows.Forms.ListView lvObject;
private System.Windows.Forms.TextBox txtObject;
private System.Windows.Forms.Button btnDropDown;
OK, I would explain why the above three controls are required:
- The
ListView
would be used to display the data in multi-columnar form.
- The
TextBox
would be used to display the selected value.
- The
Button
would serve the purpose of drilling me up and down.
Then the developer needed certain variables to control my behavior. So, he declared it in its own region.
#region MyCombo Variables
private object _dataSource;
private string _dataMember;
private string _displayMember;
private string _valueMember;
protected ArrayList _columnHeader = new ArrayList();
private object _selectedValue;
private object _selectedText;
const int MIN_WIDTH = 140;
const int MIN_HEIGHT = 22;
#endregion
Let me tell you what some of these variables mean to me, rest are obvious. (Later, we will add respective set
/get
property for the above variables.)
_dataSource
I need to be able to populate myself with data from different sources like custom class, a database, an XML file etc. Use this property to assign the data source.
_dataMember
A _dataSource
can contain multiple tables. Specify which table I should pick up the data from through this property.
_displayMember
Since I would present myself in multi-column format, specify which column to display in the TextBox
through this property.
_valueMember
This would ideally be the unique ID for each of the data elements within a single row of data. You would require this value for querying, saving etc.
_selectedValue
This returns the value from _valueMember
variable.
_selectedText
This returns the value from _displayMember
variable.
_columnHeader
This is an ArrayList
. This will hold the columns' title with me.
Now comes the events part. Events are needed in order for the user of the control to know when something happens to me. Right now, the developer has provided only one custom event.
#region MyCombo Events
public delegate void
SelectedItemChangedEventHandler(object sender, EventArgs e);
public event SelectedItemChangedEventHandler
OnSelectedItemChangedHandler;
#endregion
The OnSelectedItemChangedHandler
will be raised whenever the user selects any of the list items from the ListView
.
As I promised, here are my corresponding get
/set
accessors. The user of me would see this properties rather than the variables we have seen above.
public string DisplayMember
{
get
{
return _displayMember;
}
set
{
_displayMember = value;
}
}
public string ValueMember
{
get
{
return _valueMember;
}
set
{
_valueMember = value;
}
}
public string DataMember
{
get
{
return _dataMember;
}
set
{
_dataMember = value;
}
}
public object DataSource
{
get
{
return _dataSource;
}
set
{
_dataSource = value;
OnDataBind();
}
}
public object SelectedText
{
get
{
return _selectedText;
}
set
{
_selectedText=value;
}
}
public object SelectedValue
{
get
{
return _selectedValue;
}
set
{
_selectedValue = value;
}
}
In the DataSource
property, you see a method OnDataBind()
. This would be explained as we go further.
Now, there should be a mechanism through which the user of me would assign columns to me. That�s taken care by the following function. The function takes two parameters, the name of the column and the width of the column. This function simply adds the column name to the ArrayList
we just saw above.
public void Columns(string columnName, int colWidth)
{
_columnHeader.Add(columnName);
OnHeaderBind(columnName,colWidth);
}
Now, lets see the OnHeaderBind()
function. This function adds the columnName
to the actual ListView
object and also sets its width.
protected void OnHeaderBind(object v, int colWidth)
{
lvObject.Columns.Add(v.ToString(), colWidth, HorizontalAlignment.Left);
}
There should also be a provision for manually adding data to me rather than using a data source and data member. This is achieved by the following function. This function takes an ID, and an array of items to be added to the ListView
. So, the user of me can add an item to me like:
<instance of me>.Add (�101�,�Rajesh�,�23)
public void Add(string id, params string [] items)
{
ListViewItem lvi = new ListViewItem(id);
foreach(string s in items)
{
lvi.SubItems.Add(s);
}
lvObject.Items.Add(lvi);
}
Now, as promised earlier, let's come to the OnDataBind()
method. This method is called whenever you assign a data source to me. This method would just fill the ListView
the information from the data source.
protected void OnDataBind()
{
if (_dataSource == null)
return;
IList iList = InnerDataSource();
Type type = iList.GetType();
string s;
for (int i = 0; i < iList.Count ; i++)
{
s= GetField(RuntimeHelpers.GetObjectValue(iList[i]),_valueMember);
ListViewItem lvi = new ListViewItem(s);
for (int j = 1; j < _columnHeader.Count; j++)
{
lvi.SubItems.Add(GetField(RuntimeHelpers.GetObjectValue(iList[i]),
_columnHeader[j].ToString()));
}
lvObject.Items.Add(lvi);
}
}
What does RuntimeHelpers.GetObjectValue(obj)
do? It returns a boxed copy of obj
if it is a value class; otherwise obj
itself is returned.
The InnerDataSource()
function returns the IList
object associated with the data source. Later, we could enumerate the IList
object for each of the list values it contains.
private IList InnerDataSource()
{
IList iList;
if (_dataSource is DataSet)
{
if (_dataMember.Length > 0)
{
iList = ((IListSource)
((DataSet)_dataSource).Tables[_dataMember]).GetList();
}
else
{
iList = ((IListSource)((DataSet)_dataSource).Tables[0]).GetList();
}
}
else if (_dataSource is IListSource)
{
iList = ((IListSource)_dataSource).GetList();
}
else if (_dataSource is ICollection)
{
object[] objs = new object[((ICollection)_dataSource).Count];
((ICollection)_dataSource).CopyTo(objs,0);
iList = objs;
}
else
{
iList = (IList)_dataSource;
}
return iList;
}
IListSource
: this interface provides functionality to an object to return a list that can be bound to a data source.
ICollection
: this interface defines size, enumerators and synchronization methods for all collections.
Compile me, add a test project and add reference to me, and I am ready to face the world.
That�s it to me for now. There are lots of things that needs to be done to me. Probably, that would come later on.
I hope you enjoyed reading me. Any suggestions, criticism, ideas should be directed to the creator of me, so that he could improve me or learn a bit more.
For complete source code, check the sample project uploaded with this article.
Bye for now,
ComboView
Credits
InnerDataSource
and related functions -> These functions has been taken from The Code Project/ or somewhere from the Internet. If any one knows the original author, please sent a comment to me, I would like to give credit to him.
Revision History
- 12-28-2003: Original article.