CheckedListBox Woes
Recently, I had a problem to solve involving displaying lists of checkable items. I found out, the hard way, the limitations of the CheckedListBox and why you may not want to try using it. My woeful tale ended with me using a third-party control to achieve what I was looking for.
The problem
Using a Windows Forms Application, I needed a scrollable list of checkbox items. The number of items and what they were are dynamic, and come from a DataTable. The checkboxes needed to be Indetermindate when they are loaded – that is, neither checked or unchecked but that mysterious third state (in Windows the checkbox is filled in, usually in grey or green). I then needed to know, on an event such as a button click, which of the checkboxes had been ticked, and which had been unticked (ignoring any left in the third state).
First Solution
Initially I used a CheckedListBox. This is in the standard Toolbox, and offers “a ListBox in which a check box is displayed to the left of each item.” Sounds perfect, especially when I found out that the check boxes supported the indeterminate state.
The first hurdle was this interesting find: Note   You cannot bind data to a CheckedListBox. Use a ComboBox or a ListBox for this instead. This took a moment to digest. This is a top-level, Microsoft-provided Toolbox control living in the System.Windows.Form namespace, described as a type of ListBox, but you can’t data bind to it? That seems like madness.
I should have walked away at this point. However, I’d already sunk an hour into this, so I write a For..Next to populate the CheckedListBox. That worked OK:
This gave me exactly what I was looking for. However, this is when I ran into my second problem: you can’t enumerate the CheckedListBox and get CheckBoxes back. If you go through the .Items property you get a String with the title of the CheckBox. Strings! Pretty, but not helpful.
CheckedListBox does have both a .CheckedIndices and a .CheckedItems property. If anyone can tell me what the difference between these is, I’d be very grateful. Initially I didn’t think this would be helpful – I thought it would only show items in the checked state. It turns out that apparently indeterminate items are also shown, along with their CheckState. So, I probably could have used this, although it would be a fairly nasty hack of finding out what had been specifically un-checked using a process of elimination.
Second Solution
At the point I decided to leave the CheckedListBox well alone, and use something else. I was also getting fed-up, so I didn’t spend very long on the following solutions. I used a ListView, set it’s CheckBoxes property to True and added Items, but then found that those CheckBoxes couldn’t be indeterminate. I then switched to a ListBox and added CheckBox controls to its Control collection. I didn’t spend very long on this, but I ran into a problem where only the first control would ever be displayed. I may look into this another time, unless anyone else can shed light on it?
Final Solution
By now I was feeling pretty annoyed, and my Google searches were getting wider and wider. I picked up a third-party control from CodeProject, called Glacial ListView Control. It’s written in C# and is arguably complete overkill for what I need, but I’m glad I found it. (I’m now re-thinking some early DataGridViews I did and wondering if I could refactor them to use this).
Using the Glacial ListView Control I could add a CheckBox as a control quite easily:
For Each drRow As DataRow In SourceData.Rows
chkBox = New CheckBox
chkBox.Text = drRow(SourceDataDescColumn).ToString
chkBox.CheckState = Windows.Forms.CheckState.Indeterminate
Dim GLItem As GlacialComponents.Controls.GLItem = MultiSelectBox.Items.Add(drRow(0).ToString)
GLItem.SubItems(0).Control = chkBox
Next
The CheckBox renders exactly as you might expect it to. Even better, it’s a doddle to work out afterwards what’s been clicked and what hasn’t:
For Each GLRowItem As GLItem In chkBox.Items
chkCheckBox = CType(GLRowItem.SubItems(0).Control, CheckBox)
If chkCheckBox.CheckState = Windows.Forms.CheckState.Checked Then
SelectedItems.Add(CInt(GLRowItem.Text))
ElseIf chkCheckBox.CheckState = Windows.Forms.CheckState.Unchecked Then
UnSelectedItems.Add(CInt(GLRowItem.Text))
End If
Next
There’s many other things that the Glacial ListView Control can do, such as the pretty alternative row colourings, multiple control embedding and other such stuff. I’m glad I’ve found it, and I’m sure I’ll be using it again in the future.