Click here to Skip to main content
15,867,756 members
Articles / Programming Languages / C#
Article

Searchable MultiColumn ComboBox with Linked TextBox

Rate me:
Please Sign up or sign in to vote.
4.79/5 (51 votes)
31 Jan 2008CPOL10 min read 325.6K   17.1K   174   111
A multi-column combox that can link to a dataset or an array of objects. Supports the hiding of columns and the reordering of visible ones. The combobox can display a field (like a code) while the linked text box can show the corresponding description. Supports auto-completion and RightToLeft langua

Screenshot - CodeProject01.jpg

Version 1.3 - Searchable with fixed width / hidden columns. Supports RightToLeft languages.

Introduction

Some of the users that I write programs for have to fill out forms that consist of selecting many codes from combo boxes. These codes are fixed. The average user can not enter new codes but must select from a predetermined list of values. The people who do the data entry are very familiar with the codes and are keyboard centric. They don't want to use a mouse each time they enter a value. The people who view the data but don't do a lot of data entry are not as familiar with the codes but want to see the more verbose text descriptions that each code is associated with.

The solution that I was trying to create was a multicolumn ComboBox that was linked to a read-only TextBox. When a user selected an item from the ComboBox, it would display both the code and the description in a multicolumn dropdown list. But when the ComboBox closed, I wanted to display the code in the ComboBox, and the description in the TextBox that was linked to it. The code in the ComboBox would be bound to the database, and the TextBox would be unbound and for informational purposes only. This way, both sets of users would be able to see the information that was most important to them.

I also wanted to force the user to only select items that were in the ComboBox list. The user could not enter any text that was not valid. So this control can operate in one of two modes:

  1. AutoComplete = true. When AutoComplete is set to true, the control behaves like a ComboBoxStyle.DropDown. The user can type in a value, and it will auto complete based on the associated list of values. If the user types a letter that will cause an invalid code to be entered, that keystroke is suppressed.
  2. AutoComplete = false. When AutoComplete is set to false, the control behaves like a ComboBoxStyle.DropDownList (even though it actually has a ComboBoxStyle.DropDown visual style). In this mode, the user can hit a letter or number and the control will cycle through all of the list values that start with that letter or number. But the closed ComboBox never displays the dividing lines between the columns like a ListBox. It only shows the code value, no matter how wide the ComboBox is.

Background

This article and the control I created would not have been possible if not for the work of Nishant Sivakumar and the MultiColumn ComboBox control that he wrote. It was his work that helped me overcome all the tough obstacles that were stopping me from creating what I needed. Once I found his code, I was able to create a solution in about a day. If you like this control and find it useful, thank him first (and most). Instead of duplicating all his documentation here, I would suggest you read about the control on his page if you want a better understanding of how my control works.

I would also like to acknowledge Laurent Muller since I used his forum suggestion as a basis for my auto-complete functionality.

Using the code

To use this code, all you have to do is select the MultiColumnComboBox from the toolbox along with a TextBox and put them on a form.

Then, you can set the following properties:

  • AutoComplete: True makes it behave like an auto completing dropdown. False makes it behave like a read-only dropdownlist.
  • AutoDropdown: When you press a key, it will automatically drop down the list so you can see the choices. In AutoComplete = true mode, it will automatically close when you've typed in a valid selection.
  • BackColorEven: The back color of the even items in the list. The default is white.
  • BackColorOdd: The back color of the odd items in the list. The default is white. I always use a forecolor of black so I did not code an Odd/Even forecolor property.
  • LinkedColumnIndex: What is the index of the column that you want the TextBox to display? The majority of my forms have the ComboBox display column 0 and the TextBox display column 1.
  • LinkedTextBox: Select a TextBox from the dropdown list of controls on the form. When a TextBox is linked to the ComboBox, it is automatically set to ReadOnly = true and TabStop = false at design time.

Hitting the Escape key clears the ComboBox and its associated TextBox.

Version 1.1 changes

Once I started using the control and became a little more familiar with the code Nishant had written, I realized there were a few changes that I wanted to make in order to have it behave the way I needed it to. So I tweaked it a little:

  1. The Delete key clears the control and the linked TextBox in the same way the Escape key did in the original version.
  2. The Backspace key behaves like a left arrow key. It doesn't remove letters from the code since I wanted to enforce only valid selections. It just moves the caret back a space and repositions the list accordingly.
  3. The original version iterated through every item that was going to be in the dropdown list and dynamically set the column widths. As another member pointed out in the feedback, this was a problem when linking to objects with many public properties as they all showed up in the list. I decided to make the column width a user specified item so that columns could be hidden by setting the value to zero.
  4. I added an example to the demo that shows one way to programmatically reference a hidden column of a selected combobox item, in case someone wanted to hide a column but use something like an ID or key value.

Adding these features added two more properties:

  • ColumnWidthDefault: If a column width is not explicitly declared, this will be the width of the column.
  • ColumnWidths: A delimited string of column widths. To implement this feature, I copied a Microsoft Access idiom of using a semi-colon delimited string to specify the column widths. A blank column is set to the default.

Here are a few examples:

ColumnWidthDefault = 75
ColumnWidths =

Result: Every column will be displayed with a default width of 75.

Bound to an object with six columns:

ColumnWidthDefault = 75
ColumnWidths = 100;;200

Result: Column0 = 100, Column1 = 75 (default), Column2 = 200; the remaining three columns all default to 75.

Bound to an object with three columns:

ColumnWidthDefault = 75
ColumnWidths = 0;50;150

Result: Column0 is hidden with a width of zero, Column1 = 50, Column2 = 150.

Bound to an object with six columns:

ColumnWidthDefault = 0
ColumnWidths = 50;100

Result: Column0 = 50, Column1 = 100, all the rest are hidden because they are the default value of zero.

Version 1.2 changes

Binding the control to an array of objects that has a large number of properties was causing problems. While a dataset will display its columns in the order they are defined, an object with a lot of public properties would display the properties in the dropdown list in a random order. This made it impossible to use column indexes to set column widths since there was no guarantee that a column would always be tied to a particular index.

To solve this problem, I added a new property so that the programmer could define which columns would be displayed by name and which order they would appear.

  • ColumnName: A delimited string of column names. The order of the names determines the order of the columns. A blank string shows all the columns. An example would look like this:
  • ProductCode;ProductCategory;ProductDescription

A few of the dropdown lists in my program have a lot of codes (several hundred). The majority of the time, the users select a few very common choices that are easily memorized. But when a new user is learning the system or an experienced user needs to locate a rarely used item, some kind of search utility can be helpful.

For this reason, I added a OpenSearchForm event to the class. When a user hits the <F3> key (the standard Windows search key), this event gets fired. The programmer can then create any kind of search form he likes and tie it to this event. I added an example search form to the demo where the user can enter a search word, hit the Enter key, and the string will be located in the search grid. Double-clicking an item in the grid sets the value in the ComboBox and closes the form. To test this, the user should go to a ComboBox that lists all the states, hit <F3>, type in the word "North", and hit the Enter key several times to see it loop through states it finds.

To open the search form in my demo program, I added a single routine:

C#
private void multiColumnComboBox_OpenSearchForm(object sender, EventArgs e)
{
    FormSearch newForm = new FormSearch((MultiColumnComboBox)sender);
    newForm.ShowDialog();      
}

I then link the OpenSearchForm event of each ComboBox on the form to this single event. The search form that is opened then sets the DataSource of the search form's DataGridView to the DataSource of the passed ComboBox. I use the ColumnNameCollection and the ColumnWidthCollection of the passed ComboBox to determine which columns to display in the grid.

Screenshot - CodeProject02.jpg

This search routine worked fine for all of my examples except one. If the data source of a ComboBox was an array of objects that exposed a lot of properties, the dropdown list would look fine if I used the ColumnNames property to only display the properties I wanted to see:

Screenshot - CodeProject03.jpg

But if I hit the <F3> key to open the search form, the list would lose its ordering:

Screenshot - CodeProject04.jpg

Instead of showing Property0, Property1, Property2, it would show the columns in the random order of the underlying array. Even when I hardcoded the positions, it would still ignore these settings in this one instance. The searching and the double-clicking worked fine, just the columns weren't in the order I wanted them to be. Small objects and DataSets worked like they should.

The only way I was able to solve this was to have the large class implement an interface that only exposed the properties that should appear in the ComboBox:

C#
//An interface that only shows properties 0, 1 & 2
public interface ICombo012
{
    string Property0
    {
        get;
    }

    string Property1
    {
        get;
    }

    string Property2
    {
        get;
    }
}

// A class that has 10 properties but can display
// properties 0, 1 & 2 or properties 1 & 9 
// through two different interfaces
public class ComplexObject : ICombo012, ICombo19
{
    //Class with 10 properties defined here
}

Binding the ComboBox to the smaller interfaces allowed me to use the larger objects without problems. I do not know if this is the best way to solve this problem, but since this is a learning process for me, I'm sure someone will point out a more elegant solution if one is available. My goal is to post each set of improvements even if it has a few problems, in the hopes that I'll eventually be able to refine this into a control that will be useful and reliable.

Version 1.3 changes

I added support for RightToLeft languages. If the language is LeftToRight, I draw the strings in the OnDrawItem event, starting with column zero and ending with the last column, using the default settings. If the language is RightToLeft, I draw the columns in reverse order from the highest index down to zero. I also use a StringFormat object to align the strings in a RightToLeft style.

This update also exposed a small "flaw" in the earlier versions. When setting the DropDownWidth in the OnDropDown event, I didn't calculate the width of the vertical scrollbar if it was present. In a LeftToRight language, this would truncate trailing characters of the last column, and could be "fixed" by making the last column wider.

But with RightToLeft languages, the presence of the vertical scroll bar would obscure significant characters of the first (rightmost) column. The only way to fix this was to make sure the width of the vertical scrollbar was also added whenever the Items.Count was greater than the MaxDropDownItems.

Version 1.3.1 changes

I received an email from Onno Sloof in the Netherlands suggesting that adding the following line:

C#
protected override void OnSelectedValueChanged(EventArgs e)
{    
    base.OnSelectedValueChanged(e);

    .

    .

    .

}

to the OnSelectedValueChanged event would help with databinding. As per his suggestion, I added this line to version 1.3.1.

History

  • January 31, 2008 - Version 1.3.1 published.
  • December 26, 2007 - Version 1.3 published.
  • August 24, 2007 - Version 1.2 published.
  • August 22, 2007 - Version 1.1 published.
  • August 14, 2007 - Article first published.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I wrote my first program on a Tandy computer using a 1963 black & white Zenith TV for a monitor.

I wrote my second program in Fortran using a card punch machine.

I've been hooked ever since...


Comments and Discussions

 
GeneralRe: Great! One question about DLLs, though... Pin
Darryl Caillouet20-Aug-08 18:53
Darryl Caillouet20-Aug-08 18:53 
QuestionDLL Error Pin
Nasir Razzaq4-Aug-08 3:59
Nasir Razzaq4-Aug-08 3:59 
AnswerRe: DLL Error Pin
Darryl Caillouet20-Aug-08 18:58
Darryl Caillouet20-Aug-08 18:58 
GeneralNot Sure Pin
thesax8-Jul-08 5:26
thesax8-Jul-08 5:26 
GeneralRe: Not Sure Pin
Darryl Caillouet8-Jul-08 5:53
Darryl Caillouet8-Jul-08 5:53 
GeneralRe: Not Sure Pin
thesax9-Jul-08 22:15
thesax9-Jul-08 22:15 
GeneralTreating a Return key as a Tab... Pin
Darryl Caillouet10-Jul-08 1:51
Darryl Caillouet10-Jul-08 1:51 
GeneralRe: Treating a Return key as a Tab... Pin
thesax10-Jul-08 3:16
thesax10-Jul-08 3:16 
GeneralRe: Treating a Return key as a Tab... Pin
Darryl Caillouet10-Jul-08 3:30
Darryl Caillouet10-Jul-08 3:30 
GeneralRe: Treating a Return key as a Tab... Pin
thesax10-Jul-08 13:18
thesax10-Jul-08 13:18 
Generalneed dll Pin
Mirko P.15-Jun-08 13:07
Mirko P.15-Jun-08 13:07 
GeneralHow to create a .dll file Pin
Darryl Caillouet17-Jun-08 4:15
Darryl Caillouet17-Jun-08 4:15 
GeneralRe: need dll Pin
Mirko P.17-Jun-08 9:15
Mirko P.17-Jun-08 9:15 
QuestionDateTime format Pin
Member 36959043-Jun-08 16:18
Member 36959043-Jun-08 16:18 
AnswerRe: DateTime format Pin
Darryl Caillouet3-Jun-08 16:58
Darryl Caillouet3-Jun-08 16:58 
GeneralRe: DateTime format Pin
Darryl Caillouet4-Jun-08 7:32
Darryl Caillouet4-Jun-08 7:32 
GeneralRe: DateTime format Pin
Member 36959044-Jun-08 9:58
Member 36959044-Jun-08 9:58 
QuestionDataSource Property Problem Pin
Member 369590431-May-08 19:11
Member 369590431-May-08 19:11 
AnswerRe: DataSource Property Problem Pin
Darryl Caillouet3-Jun-08 2:34
Darryl Caillouet3-Jun-08 2:34 
GeneralRe: DataSource Property Problem Pin
Member 36959043-Jun-08 7:21
Member 36959043-Jun-08 7:21 
GeneralRe: DataSource Property Problem Pin
Darryl Caillouet3-Jun-08 7:28
Darryl Caillouet3-Jun-08 7:28 
Generalthank Pin
denistoto18-May-08 0:04
denistoto18-May-08 0:04 
GeneralChinese input method is covered. It's Very serious . Hope it to be solutioned. Pin
xiezhenhua16-May-08 21:20
xiezhenhua16-May-08 21:20 
JokeThank you Pin
Terry Reardon5-May-08 7:09
Terry Reardon5-May-08 7:09 
GeneralRe: Thank you Pin
Darryl Caillouet5-May-08 8:50
Darryl Caillouet5-May-08 8:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.