Introduction
This article allows you to create your own fully customized list controls for display in a combobox drop down style while at the same time staying with as much intrinsic/standard control functionality as is possible. The drop window behaviour supports multiple select operations without closing the drop window, and does not use a single API call. The base combobox implementation uses the ToolStripDropDown
component to replace the built in drop window functionality of the base combobox along with a ToolStripControlHost
component used internally to host a third control. The base implementation allows a developer to extend functionality by implementing a variety of different controls. Ideally, only use list controls such as the following.
ListBox
CheckedListBox
MonthCalendar
TreeView
DataGridView
Background
The code for this article is the result of a request that came across my desk. My brief was to "give us a checked combobox like the one used in Team System". After some serious search engine abuse, I came up with nothing usable in any stable format that did exactly what I wanted. All the samples used API calls - badly at that too. I have a rule, when you need to use APIs calls... don't! So, I did the next best thing, and re-invented the wheel, which also led to my very first article which I've wanted to do for sometime now. Hats off to the prolific article writers out there - where you find the time, I couldn't even begin to fathom. Anyway, here goes.
Using the Code
Using the code is pretty straightforward. Within a Windows Forms application, we have Form1
. The controls/base folders contain a base combobox implementation and a CheckedListComboBox
implementation built on top of the base combobox implementation. Simply extract the source code to a folder near you, open it and build the project in VS 2008. Select the Items
property to create a list and hit the Play button. For this version only, our control won't support data binding simply because the standard Windows CheckedListBox
control doesn't support data binding - I will address this functionality in my next article.
Base Implementation
Choice of Container
Firstly, we must hide the base drop window size to appear invisible, then prepare our internal containers that are going to provide the necessary functionality, specifically:
- 01 x standard
ToolStripDropDown
component, which is a marvelous little component that provides a host of functionality as ‘standard’. One particularly useful tasty morsel is that the behavior allows mouse click functionality to select listed items while at the same time maintaining an open drop list for subsequent select operations. Something that can only be properly achieved in C++. The sample code/articles I found all used API calls – which, besides being unstable, is an attempt to access the root C++ functionality anyway.
- 01 x standard
ToolStripControlHost
, almost a Swiss army knife when it comes to its flexibility in wrapping any other control, and ease of use with almost no code required.
namespace CheckedComboTest.Controls.Base
{
public abstract class ComboBoxEx : ComboBox
{
#region Declarations & Constructors
private ToolStripDropDown tsdd = new ToolStripDropDown();
private ToolStripControlHost tsch = null;
private int dropDownHeight;
new public event EventHandler DropDown;
new public event EventHandler DropDownClosed;
public ComboBoxEx()
: base()
{
base.DropDownHeight = 1;
}
#endregion
Instantiation
Always important in any component design is the manner and timing of any internal container instantiation. That’s why I like to use the base OnHandleCreated
method to build the internals because by the time the thread executes my code, I know the base control has been instantiated and my control instance has a handle attached to it.
#region Base Overrides/Overloads
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
BuildInternalHost();
}
#endregion
Internally, we configure our drop container and call the abstract OnCreateHost()
method, forcing any implementation up the hierarchy to provide a control for hosting at the same time as the creation of the host container is taking place.
tsch = new ToolStripControlHost(OnCreateHost());
All we need to do is provide our own drop height functionality, and we can proceed with a custom implementation of ComboBoxEx
.
Custom Implementation
Our custom implementation needs a class that inherits from our ComboBoxEx
base component, and for our example, let’s use a CheckedListBox
control.
public class CheckedListComboBox : Base.ComboBoxEx
{
#region Declarations & Constructors
private CheckedListBox checkedListBox = new CheckedListBox();
public CheckedListComboBox()
: base()
{
}
#endregion
Actually, all our implementation needs to do is return an instance of a list control back through the OnCreateHost()
function, like this…
#region Base Overrides/Overloads
protected override Control OnCreateHost()
{
return checkedListBox;
}
…and then we can paste from the toolbox onto the form and run the project. The resulting drop window is quite normal at first site, especially when compared with the standard ComboBox
, till we actually click to display the drop windows. Our custom drop window has a drop shadow effect along with matching border colors, and all this as provided standard by Microsoft.
Lastly, replace the standard Items
property with the one exposed by the CheckedListBox
control et voila:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor("System.Windows.Forms.Design.ListControlStringCollectionEditor,
System.Design, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
[Localizable(true)]
new public CheckedListBox.ObjectCollection Items
{
get
{
return checkedListBox.Items;
}
}
Points of Interest
Effectively, the only thing created was an intermediate layer of code allowing multiple components to be plugged into each other. We didn’t even have to ‘hack’ any drawing routine or use even a single API call. The available controls are simple building blocks, and while it is great fun to twist and 'hack' something to death, it is easy to overdo, especially when done without a clear idea of how to extend the framework.
In my next article, I will expand on my custom implementations to cover data binding on custom implementations.
History
First release!