I needed a way to minimise the space required by a Filter control, yet maximise the filter capabilities provided to the user. One way to accomplish this was to replace a Grouped Box of
CheckBox controls with a
CheckBoxComboBox control. There are several
CheckBoxComboBox controls on the web, but all of the ones I found and tested had something missing.
Some controls simulated a check box by only painting it in the
Popup, which means the
Checkbox did not behave like a
Checkbox should and it caused the
Popup to close before the user could make more selections. Other controls did not specialise a normal
ComboBox, so you lost the existing functionality of a
ComboBox control and its
Items, for example, you could not bind the control to a custom DataSource anymore.
CheckBoxComboBox combines the standard .NET
ComboBox with real
CheckBoxes and accomplishes this task by creating a wrapper for the
ComboBox.Items (it does not use a
CheckBoxListBox either). Another point worth mentioning here, is that it also uses the neat
PopUp solution provided by Lukasz Swiatkowski which you can find here. This solved some custom
PopUp problems like focus removed from the owner form, resize capabilities, positioning, etc. Really worth looking at.
CheckBoxComboBox provides a
CheckBoxItems property which contains the
CheckBoxes shown in the list, as well as a link to the
ComboBox Item object it links to. In addition, it has a
CheckBoxCheckedChanged event which bubbles the
CheckedChanged events of the
CheckBox items back to the
Using the Code
Using the code is simple, you can populate the
ComboBox like you would do normally, by either populating the
Items manually or linking to a
However, make sure you think about the following, especially if you are going to be binding to the control. You might currently have a list of objects which you want to list in the
CheckBoxComboBox popup for the user to make a selection. That selection requires a bool property which when binded will be set back to the binded object. Does that object really care whether it is selected somewhere? What would happen if you not only wanted to select the object, but also wanted to add additional display information, such as how many of those objects are available? All this extra information can clutter your object unnecessarily. So, I included an additional class which you can use to solve this for you. It basically takes your list of objects, and adds a
Count property for you to manipulate without adding those properties to your existing object where it is actually not relevant. You can then easily extend on these properties or change their behavior without affecting your current class. (It's separate and clean.) The code below demonstrates its use with a custom
List<T> and a
DataTable, but the only requirement is an
#region POPULATE THE "MANUAL" COMBO BOX
The code sample above is straightforward, eight string objects are added to the
ComboBox as you would do normally.
#region POPULATED USING A CUSTOM "IList" DATASOURCE
_StatusList = new StatusList();
_StatusList.Add(new Status(1, "New"));
_StatusList.Add(new Status(2, "Loaded"));
_StatusList.Add(new Status(3, "Inserted"));
_StatusList.Add(new Status(4, "Updated"));
_StatusList.Add(new Status(5, "Deleted"));
new ListSelectionWrapper<status />(_StatusList);
cmbIListDataSource.ValueMember = "Selected";
cmbIListDataSource.DisplayMemberSingleItem = "Name";
cmbIListDataSource.DisplayMember = "NameConcatenated";
In this extract, a
List<Status> object is binded to the list. Note that it is not binded directly, I used the
ListSelectionWrapper<T> to handle the selection for me, because I did not want to modify my existing "re-usable"
#region POPULATED USING A DATATABLE
DataTable DT = new DataTable("TEST TABLE FOR DEMO PURPOSES");
new DataColumn("Id", typeof(int)),
new DataColumn("SomePropertyOrColumnName", typeof(string)),
new DataColumn("Description", typeof(string)),
DT.Rows.Add(1, "AAAA", "AAAAA");
DT.Rows.Add(2, "BBBB", "BBBBB");
DT.Rows.Add(3, "CCCC", "CCCCC");
DT.Rows.Add(3, "DDDD", "DDDDD");
cmbDataTableDataSource.DisplayMemberSingleItem = "Name";
cmbDataTableDataSource.DisplayMember = "NameConcatenated";
cmbDataTableDataSource.ValueMember = "Selected";
In the third example, I also use the
ListSelectionWrapper<T>, because my table does not have a Selection column. Note however, that I don't wrap the
DataTable, I wrap the
Rows instead, which is
IEnumerable. When initialising the wrapper, I specify
"SomePropertyOrColumnName", because the wrapper uses
ToString() on the objects, which normally on a
DataRow will result in
"System.Data.DataRow" instead of the real text you would want to display. You can use this Property specifier to indicate a
PropertyDescriptor or normal property on objects other than a
DataRow if you did not want them to use
ToString() either. This can be useful.
If you want to know which items are selected, you have two options:
- Use the
CheckBoxItems property on the
ComboBox which is a list of items wrapping each item in the
ComboBox.Items list. The
CheckBoxComboBoxItem class is a standard
CheckBox, and therefore the
bool value you are looking for is contained in
- Or if you stored a reference to the
ListSelectionWrapper<T>, you could use that to access the
Selected property of the binded list.
CheckBoxComboBox does not make sense or seem necessary to you, consider the following filter as a real-world example.
ComboBox shown here, replaces a Group Box shown below it.
There is no space for this control:
Points of Interest
I was originally a religious Delphi developer, so working with this control has taught me a lot about C# and .NET. The
ComboBox has always been, and still is a difficult control to customise, and doing so can prove to take up more hours of your time than you anticipated.
The following list of sources may help your attempts, and also contains links to other
CheckBox ComboBox controls:
- Simple Pop-up Control
- Custom ComboBoxes with Advanced Drop-down Features
- An OwnerDraw ComboBox with CheckBoxes in the Drop-Down
- Changed the pop-up frames and duration to zero to fix the black flicker. I couldn't figure a way to resolve it while keeping the fade effect.
- Solved a problem where the selected state of the
Checkbox has not yet been assigned back to the binded property before the
CheckBoxCheckedChanged event is raised. I now assign this value back myself if the
ComboBox uses a
- Added a class to wrap existing lists so I don't need to add unnecessary
Selected properties in a class where it is not needed. This
List wrapper does support
IBindingList sources, so if you implement that interface in your list, this wrapper will keep itself in sync with your list automatically. But, it is not a requirement at all.
- The Selection wrappers also help working with counts.
- Changed the
CheckBoxItems to synchronise when the property is accessed, so that
Checked values can be initialised before the popup is shown.
- Added a sub property on the
CheckBoxProperties which allows you to change the appearance of the
Checkboxes, e.g. Flat, Text Alignment, etc.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.