다음을 통해 공유


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

Presentation at runtime.

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.