Windows Forms: Working with CheckedListBox (VB.NET)
This article provides core basics for working with a Windows Form control CheckedListBox from populating with string, setting the DataSource to a DataTable to a List along with getting/setting checked items, finding items, limiting checked items and more. There will be a discussion on creating a custom CheckedListBox control versus having a decent understanding of the CheckedListBox to decide if a custom CheckedListBox is needed or work with the standard CheckedListBox.
CheckedListBox overview
The CheckedListBox provides the capability to show text along with a Checkbox for each item within the control which allows a user to select one or more items. For example, presenting a options dialog for an application where each item presented in the CheckedListBox would allow the user to customize the user experience, another case would be to present items for building a product with options to the standard product.
Populating
There are various ways to populate a CheckedListBox
With string
For fixed items that will not change type in items at design time.
Or use a standard collection like month names.
Imports System.Globalization
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
MonthsCheckedListBox.Items.AddRange(
(
From month In CultureInfo.CurrentCulture.DateTimeFormat.MonthNames
Where Not String.IsNullOrEmpty(month)).ToArray
)
MonthsCheckedListBox.SelectedIndex = Now.Month -1
End Sub
End Class
With classes
A class may be used by setting the CheckedListBox.DataSource with a list of a concrete class. To display a specific property for the Text property set the DisplayMember or override the ToString method.
In the example below the class overrides ToString with ProductName so there is no need to set DisplayMember. If DisplayMember is not set and ToString is not overridden at runtime the object type is shown which does not good.
Namespace DataClasses
Public Class Product
Public Property ProductID As Integer
Public Property ProductName As String
Public Property SupplierID As Integer?
Public Property CategoryID As Integer?
Public Property QuantityPerUnit As String
Public Property UnitPrice As Decimal?
Public Property UnitsInStock As Short?
Public Property UnitsOnOrder As Short?
Public Property ReorderLevel As Short?
Public Property Discontinued As Boolean
Public Property DiscontinuedDate As DateTime?
Public Overrides Function ToString() As String
Return ProductName
End Function
End Class
End Namespace
Data is returned from a database table while other options range from reading items from a file or from a service.
Imports System.Data.SqlClient
Imports BaseConnectionLibrary
Namespace DataClasses
Public Class SqlServerOperations
Inherits ConnectionClasses.SqlServerConnection
Public Sub New()
DefaultCatalog = "NorthWindAzure1"
DatabaseServer = "KARENS-PC"
End Sub
Public Function ProductsByCategoryIdentifier(pCategoryIdentifier As Integer) As List(Of Product)
Dim productList As New List(Of Product)
Dim selectStatement =
<SQL>
SELECT ProductID
,ProductName
,SupplierID
,QuantityPerUnit
,UnitPrice
,UnitsInStock
,UnitsOnOrder
,ReorderLevel
,Discontinued
,DiscontinuedDate
FROM NorthWindAzure1.dbo.Products
WHERE CategoryID = <%= pCategoryIdentifier %>
</SQL>.Value
Using cn As New SqlConnection With {.ConnectionString = ConnectionString}
Using cmd As New SqlCommand With {.Connection = cn, .CommandText = selectStatement}
Try
cn.Open()
Dim reader = cmd.ExecuteReader()
While reader.Read()
productList.Add(New Product() With
{
.ProductID = reader.GetInt32(0),
.ProductName = reader.GetString(1),
.Discontinued = reader.GetBoolean(8)
})
End While
Catch ex As Exception
mHasException = True
mLastException = ex
End Try
End Using
End Using
Return productList
End Function
End Class
End Namespace
In the form an import statement is used as the class above is in a different namespace than the form. The SqlServerOperations class is created as a private variable which means methods are always available in the form. a List of Product is returned and set as the DataSource for the CheckedListBox.
Imports CheckOnlyOneItem.DataClasses
Public Class Form1
Private operations As SqlServerOperations = New SqlServerOperations()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim products = operations.ProductsByCategoryIdentifier(1)
CheckedListBox1.DataSource = products
End Sub
To select an item, in this case, the selected item, cast to Product as the DataSource is a List(Of Product). Some property is Nothing because they were never set.
To check an item use SetItemChecked which accepts the index of the item to check and the checked state of true or false.
CheckedListBox1.SetItemChecked(0, True)
Checking items
For CheckedListBox populated with string cast Items property to string and using a for next to find the item then use the indexer of the for as the index into the CheckedListBox to use SetItemChecked(foundIndex, true or false).
When dealing with a List or a DataTable the following extension methods keep code clean and perform similarly as with using string but with LINQ statements.
Imports FindAndCheckItem.DataClasses
Namespace Extensions
Module ExtensionMethods
''' <summary>
''' Find a specific product by name and check or uncheck the item if found
''' </summary>
''' <param name="sender"></param>
''' <param name="pValueToLocate">Product name</param>
''' <param name="pChecked">True to check, False to uncheck</param>
<Runtime.CompilerServices.Extension()>
Public Sub FindItemAndSetChecked(
sender As CheckedListBox,
pValueToLocate As String,
Optional pChecked As Boolean = True)
Dim result =
(
From this In sender.Items.Cast(Of Product)().Select(Function(item, index) New With
{
.Item = item,
.Index = index
})
Where this.Item.ProductName = pValueToLocate
).FirstOrDefault
If result IsNot Nothing Then
sender.SetItemChecked(result.Index, pChecked)
End If
End Sub
''' <summary>
''' Find a specific value by field name and value in a DataTable
''' </summary>
''' <param name="sender"></param>
''' <param name="pValueToLocate">Value to find</param>
''' <param name="pFieldName">Field to locate in</param>
''' <param name="pChecked">True to check, False to uncheck</param>
<Runtime.CompilerServices.Extension()>
Public Sub FindItemAndSetChecked(
sender As CheckedListBox,
pValueToLocate As String,
pFieldName As String,
Optional pChecked As Boolean = True)
Dim result =
(
From this In sender.Items.Cast(Of DataRowView)().Select(Function(item, index) New With
{.Item = item, .Index = index})
Where this.Item.Row.Field(Of String)(pFieldName).ToLower = pValueToLocate.ToLower
).FirstOrDefault
If result IsNot Nothing Then
sender.SetItemChecked(result.Index, pChecked)
End If
End Sub
End Module
End Namespace
To check in the List(Of Product) the following seeks two products and if found checks each product in the CheckedListBox.
Imports FindAndCheckItem.DataClasses
Imports FindAndCheckItem.Extensions
Public Class Form1
Private operations As SqlServerOperations = New SqlServerOperations()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim products = operations.ProductsByCategoryIdentifier(1)
CheckedListBox1.DataSource = products
CheckedListBox1.FindItemAndSetChecked("Steeleye Stout")
CheckedListBox1.FindItemAndSetChecked("Outback Lager")
End Sub
End Class
Get checked items
Use GetItemChecked(desired index). In the following example, ItemCheck event is used to see if the current item is checked and if so append or remove from a multi-line TextBox.
Imports TrackingCheckedItems.DataClasses
Public Class Form1
Private operations As SqlServerOperations = New SqlServerOperations()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim products = operations.ProductsByCategoryIdentifier()
CheckedListBox1.DataSource = products
End Sub
''' <summary>
''' Change Product.Selected dependent on the check state of the current item.
'''
''' Show checked products in the TextBox, in a real application this might be
''' done in a button click for making a final selection for the current process.
'''
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Sub CheckedListBox1_ItemCheck(sender As Object, e As ItemCheckEventArgs) Handles CheckedListBox1.ItemCheck
CType(CheckedListBox1.Items(e.Index), Product).Selected = Not CheckedListBox1.GetItemChecked(e.Index)
TextBox1.Text = String.Join(Environment.NewLine, CType(CheckedListBox1.DataSource, List(Of Product)).
Where(Function(product) product.Selected).
Select(Function(product) product.DisplayData).ToArray())
End Sub
End Class
Obtain checked item names the following extension method provides names.
<Runtime.CompilerServices.Extension()>
Public Function CheckedItemsNamesList(sender As CheckedListBox) As List(Of String)
Dim results As New List(Of String)
For index As Integer = 0 To (sender.Items.Count - 1)
If sender.GetItemChecked(index) Then
results.Add(sender.Items(index).ToString)
End If
Next
Return results
End Function
Usage
Dim productItems = CheckedListBox1.CheckedItemsNamesList()
For Each productItem As String In productItems
Console.WriteLine(productItem)
Next
The above is works but means to gain access to each product a find operation must be performed on the CheckedListBox.DataSource casted as a List(Of Product), a better method are to get checked indexes of the CheckedListBox using the following language extension.
<Runtime.CompilerServices.Extension()>
Public Function CheckedItemsIndexesList(sender As CheckedListBox) As List(Of Integer)
Dim results As New List(Of Integer)
For index As Integer = 0 To (sender.Items.Count - 1)
If sender.GetItemChecked(index) Then
results.Add(index)
End If
Next
Return results
End Function
The usage which gets each product using the index returned from the extension method above. In this case, the ProductName is shown as ToString has been overridden as shown above in the Product class.
For index As Integer = 0 To productItems.Count - 1
Dim product = CType(CheckedListBox1.Items(index), Product)
Console.WriteLine(product)
Next
Disabling items from being checked
Consider an application which allows users to purchase items using a CheckedListBox. They open the application on Monday and a product they want appears, they end up thinking about the product and not purchase but come back the next day and the product is not shown. The developer dynamically populated the CheckedListBox with available items and on the next day it’s out of stock and not shown. This may leave the customer frustrated. Consider checking specific items disallows other options in the CheckedListBox. There are several options, remove/add using business logic, presenting a message if the item is not available because other options are checked or disable the item(s).
Disabling items coupled with text on the form to indicate why one or more items are not available or use a tooltip. To disable item(s) a custom CheckedListBox which requires a fair amount of knowledge for coding may be used or write a few lines of code.
In this case for using a standard CheckedListBox against products that are discontinued. Products are loaded including the field which indicates if the product is discontinued (another option is to check stock level available).
Public Function ProductsByCategoryIdentifier(pCategoryIdentifier As Integer) As List(Of Product)
Dim productList As New List(Of Product)
Dim selectStatement =
<SQL>
SELECT ProductID
,ProductName
,SupplierID
,QuantityPerUnit
,UnitPrice
,UnitsInStock
,UnitsOnOrder
,ReorderLevel
,Discontinued
,DiscontinuedDate
FROM NorthWindAzure1.dbo.Products
WHERE CategoryID = <%= pCategoryIdentifier %>
</SQL>.Value
Using cn As New SqlConnection With {.ConnectionString = ConnectionString}
Using cmd As New SqlCommand With {.Connection = cn, .CommandText = selectStatement}
Try
cn.Open()
Dim reader = cmd.ExecuteReader()
While reader.Read()
productList.Add(New Product() With
{
.ProductID = reader.GetInt32(0),
.ProductName = reader.GetString(1),
.Discontinued = reader.GetBoolean(8)
})
End While
Catch ex As Exception
mHasException = True
mLastException = ex
End Try
End Using
End Using
Return productList
End Function
Products are loaded by a category identifier, in a real application there would be control, ComboBox or ListBox populated with a List(Of Category), upon selected item change cast the selected item to a Category and get the Category id.
Dim products = operations.ProductsByCategoryIdentifier(6)
In the form Load or Shown event add items to the CheckedListBox as follows marking discontinued products with an Indeterminate CheckState.
'
' Set state to Indeterminate then enforce the state in ItemCheck event
'
For Each product As Product In products
If product.Discontinued Then
CheckedListBox1.Items.Add(product, CheckState.Indeterminate)
Else
CheckedListBox1.Items.Add(product)
End If
Next
Subscribe to ItemCheck event of the CheckedListBox, if the CurrentValue is Indeterminate then reset it which keeps the item disabled.
Check one item
Although only permitting one checked item is not something normally done this question has been asked on the web several times. To only permit one checked item subscribe to the ItemCheck event and check the current item NewValue for CheckState.Checked, if so iterate all items and if a checked item is found other than the current item using SetItemChecked to false.
Imports CheckOnlyOneItem.DataClasses
Public Class Form1
Private operations As SqlServerOperations = New SqlServerOperations()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim products = operations.ProductsByCategoryIdentifier(1)
CheckedListBox1.DataSource = products
End Sub
Private Sub CheckedListBox1_ItemCheck(sender As Object, e As ItemCheckEventArgs) Handles CheckedListBox1.ItemCheck
If e.NewValue = CheckState.Checked Then
For index As Integer = 0 To CheckedListBox1.Items.Count - 1
If index <> e.Index Then
CheckedListBox1.SetItemChecked(index, False)
End If
Next
End If
End Sub
End Class
Select all option
For providing the ability to check or uncheck all iterate all items in a button click event using SetItemChecked or use a custom CheckedListBox found here in the included source code.
Properties of interest
CheckOnClick
By default two clicks are required to check or uncheck items, to perform a check or uncheck with one click set the property CheckOnClick in the CheckedListBox property window of in code.
CheckedItems
A collection of checked items, when the DataSource is a List using Cast to cast the items to the specific type
Dim checkedProducts = CheckedListBox1.CheckedItems.Cast(Of Product)
CheckedIndices
This property provides a collection of checked indexes in the CheckedListBox.
Dim productIndexes = CheckedListBox1.CheckedIndices
For index As Integer = 0 To productIndexes.Count - 1
Dim product = CType(CheckedListBox1.Items(index), Product)
Console.WriteLine(product)
Next
Running sample code
- Create a SQL-Server database and running the following script.
- Each project with SqlServerOperation class change the server name and default catalog as per this sample.
- Right click on solution explorer top node, select "Restore NuGet Packages"
- Build the solution
- Run each project.
Required NuGet package
BaseConnectionLibrary - source code project.
Summary
This article has shown how to utilize a CheckedListBox in common scenarios and several uncommon scenarios using classes as the data source for the CheckedListBox while use of DataTable may be used although using a DataTable has extra baggage not needed e.g. events, methods to change the state of data rows etc. Keep in mind when considering a CheckedListBox if this is the right option over conventional Checkbox controls.
See also
VB.NET: Defensive data programming (Part 3)
VB.NET Working with parameterized SQL operations part 2
C# DataGridView - ListBox - CheckListBox - ListView move items up/down
Source code
Source code provided in the following GitHub repository.