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

An Autobiography of a Windows Forms custom control in its own words [ComboView] � Part I.

0.00/5 (No votes)
27 Dec 2003 1  
Who am I? What am I doing here at CodeProject? Well, I'll answer myself all these questions and others.

Sample Image - ComboView.jpg

Introduction

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;

    //Size

    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.

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