Problems
- Control forgets checked status of items when using a
TabControl
.
- Users are able to check or uncheck items.
- Cannot reference an item based on its index.
Solution
All of these problems can be rectified by using a custom control.
Custom Control
The code below adds the AutoCheck
property, the same property that the CheckBox
control has, to the CheckedListBox
control. It also implements a bug fix, plus allows to reference items in the control just like you can reference them in a ComboBox
. To create our BetterCheckedListBox
control, create a new VB.NET Class Library Project and copy the following code into Class1.vb.
<ToolboxBitmap(GetType(CheckedListBox))> _
Public Class BetterCheckedListBox
Inherits System.Windows.Forms.CheckedListBox
Dim AllowChecks As Boolean
Dim ChkMember As String
Dim dt As DataTable
Public Overloads Property DataSource() As Object
Get
DataSource = CType(dt, Object)
End Get
Set(ByVal value As Object)
dt = CType(value, DataTable)
LoadData()
End Set
End Property
Private Function LoadData()
Dim bufAllowChecks = AllowChecks
If AllowChecks = False Then
AllowChecks = True
End If
MyBase.Items.Clear()
Dim i As Integer
For i = 0 To dt.DefaultView.Count - 1
If dt.Rows(i).Item(ChkMember) = "1" Or _
dt.Rows(i).Item(ChkMember) = "True" Then
MyBase.Items.Add(dt.DefaultView.Item(i), True)
Else
MyBase.Items.Add(dt.DefaultView.Item(i), False)
End If
Next
AllowChecks = bufAllowChecks
End Function
Public Overrides Sub Refresh()
LoadData()
End Sub
<System.ComponentModel.Description("Allow checkstate to be changed"), _
System.ComponentModel.Category("Behavior")> _
Public Property AutoCheck() As Boolean
Get
AutoCheck = AllowChecks
End Get
Set(ByVal value As Boolean)
AllowChecks = value
End Set
End Property
Protected Overrides Sub OnItemCheck(ByVal ice As _
System.Windows.Forms.ItemCheckEventArgs)
If AllowChecks = False Then
ice.NewValue = ice.CurrentValue
End If
End Sub
<System.ComponentModel.Description("Gets or sets the CheckMember")> _
Public Property CheckMember() As String
Get
CheckMember = ChkMember
End Get
Set(ByVal value As String)
ChkMember = value
End Set
End Property
<System.ComponentModel.Description("Gets or sets an items's checkstate")> _
Public Property Checked(ByVal index As Integer) As Boolean
Get
If MyBase.CheckedIndices.IndexOf(index) <> -1 Then
Checked = True
Else
Checked = False
End If
End Get
Set(ByVal value As Boolean)
Dim bufAllowChecks = AllowChecks
If AllowChecks = False Then
AllowChecks = True
End If
MyBase.SetItemChecked(index, value)
AllowChecks = bufAllowChecks
End Set
End Property
<System.ComponentModel.Description("Gets or sets an items's DisplayMember")> _
Public Overloads Property Text(ByVal index As Integer) As String
Get
Text = dt.Rows(index).Item(MyBase.DisplayMember)
End Get
Set(ByVal value As String)
dt.Rows(index).Item(MyBase.DisplayMember) = value
End Set
End Property
<System.ComponentModel.Description("Gets or sets an items's ValueMember")> _
Public Property Value(ByVal index As Integer) As String
Get
Value = dt.Rows(index).Item(MyBase.ValueMember)
End Get
Set(ByVal value As String)
dt.Rows(index).Item(MyBase.ValueMember) = value
End Set
End Property
End Class
Once this project is compiled, copy the output, BetterCheckedListBox.dll, to C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\PublicAssemblies\.
Then right click on the Toolbox, choose 'Add/Remove', select BetterCheckedListBox
, and click OK to add our custom control to the Toolbox.
Demo Project
To create our demo project, start a new VB.NET Windows Application Project and copy the following code into Form1.vb. Run the project, a screen shot of the demo is also shown below.
Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Protected Overloads Overrides Sub _
Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
Private components As System.ComponentModel.IContainer
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents BetterCheckedListBox1 As _
BetterCheckedListBox.BetterCheckedListBox
Friend WithEvents CheckBox1 As System.Windows.Forms.CheckBox
Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
Friend WithEvents Label1 As System.Windows.Forms.Label
Friend WithEvents Label2 As System.Windows.Forms.Label
Friend WithEvents Label3 As System.Windows.Forms.Label
Friend WithEvents TextBox2 As System.Windows.Forms.TextBox
Friend WithEvents Label4 As System.Windows.Forms.Label
Friend WithEvents TextBox3 As System.Windows.Forms.TextBox
<System.Diagnostics.DebuggerStepThrough()>_
Private Sub InitializeComponent()
Me.Button1 = New System.Windows.Forms.Button
Me.BetterCheckedListBox1 = New _
BetterCheckedListBox.BetterCheckedListBox
Me.CheckBox1 = New System.Windows.Forms.CheckBox
Me.TextBox1 = New System.Windows.Forms.TextBox
Me.Label1 = New System.Windows.Forms.Label
Me.Label2 = New System.Windows.Forms.Label
Me.Label3 = New System.Windows.Forms.Label
Me.TextBox2 = New System.Windows.Forms.TextBox
Me.Label4 = New System.Windows.Forms.Label
Me.TextBox3 = New System.Windows.Forms.TextBox
Me.SuspendLayout()
Me.Button1.Location = New System.Drawing.Point(24, 144)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(120, 23)
Me.Button1.TabIndex = 2
Me.Button1.Text = "Add New Person"
Me.BetterCheckedListBox1.AutoCheck = True
Me.BetterCheckedListBox1.CheckMember = Nothing
Me.BetterCheckedListBox1.Location = New System.Drawing.Point(24, 24)
Me.BetterCheckedListBox1.Name = "BetterCheckedListBox1"
Me.BetterCheckedListBox1.Size = New System.Drawing.Size(120, 94)
Me.BetterCheckedListBox1.TabIndex = 3
Me.CheckBox1.Location = New System.Drawing.Point(24, 120)
Me.CheckBox1.Name = "CheckBox1"
Me.CheckBox1.Size = New System.Drawing.Size(120, 16)
Me.CheckBox1.TabIndex = 5
Me.CheckBox1.Text = "Lock CheckBoxes"
Me.TextBox1.Location = New System.Drawing.Point(152, 24)
Me.TextBox1.Name = "TextBox1"
Me.TextBox1.TabIndex = 6
Me.TextBox1.Text = ""
Me.Label1.Location = New System.Drawing.Point(152, 8)
Me.Label1.Name = "Label1"
Me.Label1.Size = New System.Drawing.Size(100, 16)
Me.Label1.TabIndex = 7
Me.Label1.Text = "Checked:"
Me.Label2.Location = New System.Drawing.Point(24, 8)
Me.Label2.Name = "Label2"
Me.Label2.Size = New System.Drawing.Size(100, 16)
Me.Label2.TabIndex = 8
Me.Label2.Text = "People:"
Me.Label3.Location = New System.Drawing.Point(152, 56)
Me.Label3.Name = "Label3"
Me.Label3.Size = New System.Drawing.Size(100, 16)
Me.Label3.TabIndex = 10
Me.Label3.Text = "Text:"
Me.TextBox2.Location = New System.Drawing.Point(152, 72)
Me.TextBox2.Name = "TextBox2"
Me.TextBox2.TabIndex = 9
Me.TextBox2.Text = ""
Me.Label4.Location = New System.Drawing.Point(152, 104)
Me.Label4.Name = "Label4"
Me.Label4.Size = New System.Drawing.Size(100, 16)
Me.Label4.TabIndex = 12
Me.Label4.Text = "Value:"
Me.TextBox3.Location = New System.Drawing.Point(152, 120)
Me.TextBox3.Name = "TextBox3"
Me.TextBox3.TabIndex = 11
Me.TextBox3.Text = ""
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(272, 190)
Me.Controls.Add(Me.Label4)
Me.Controls.Add(Me.TextBox3)
Me.Controls.Add(Me.Label3)
Me.Controls.Add(Me.TextBox2)
Me.Controls.Add(Me.Label2)
Me.Controls.Add(Me.Label1)
Me.Controls.Add(Me.TextBox1)
Me.Controls.Add(Me.CheckBox1)
Me.Controls.Add(Me.BetterCheckedListBox1)
Me.Controls.Add(Me.Button1)
Me.Name = "Form1"
Me.Text = "BetterCheckedListBox Demo"
Me.ResumeLayout(False)
End Sub
#End Region
Dim dt As New DataTable
Private Sub Button1_Click(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles Button1.Click
Dim dr As DataRow = dt.NewRow
dr(0) = "Person " & BetterCheckedListBox1.Items.Count + 1
dr(1) = "x"
dr(2) = 0
dt.Rows.Add(dr)
BetterCheckedListBox1.Refresh()
End Sub
Private Sub Form1_Load(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles MyBase.Load
dt.Columns.Add("txt", GetType(String))
dt.Columns.Add("val", GetType(String))
dt.Columns.Add("chk", GetType(String))
Dim i As Integer
For i = 1 To 5
Dim dr As DataRow = dt.NewRow
dr(0) = "Person " & i
dr(1) = i
dr(2) = "True"
dt.Rows.Add(dr)
Next
BetterCheckedListBox1.DisplayMember = "txt"
BetterCheckedListBox1.ValueMember = "val"
BetterCheckedListBox1.CheckMember = "chk"
BetterCheckedListBox1.DataSource = dt
End Sub
Private Sub CheckBox1_CheckedChanged(ByVal sender As _
System.Object, ByVal e As System.EventArgs) _
Handles CheckBox1.CheckedChanged
BetterCheckedListBox1.AutoCheck = Not CheckBox1.Checked
End Sub
Private Sub BetterCheckedListBox1_SelectedIndexChanged(ByVal _
sender As System.Object, ByVal e As _
System.EventArgs) Handles _
BetterCheckedListBox1.SelectedIndexChanged
TextBox1.Text = BetterCheckedListBox1.Checked(BetterCheckedListBox1.SelectedIndex)
TextBox2.Text = BetterCheckedListBox1.Text(BetterCheckedListBox1.SelectedIndex)
TextBox3.Text = BetterCheckedListBox1.Value(BetterCheckedListBox1.SelectedIndex)
End Sub
Private Sub BetterCheckedListBox1_ItemCheck(ByVal _
sender As Object, ByVal e As _
System.Windows.Forms.ItemCheckEventArgs) _
Handles BetterCheckedListBox1.ItemCheck
TextBox1.Text = _
BetterCheckedListBox1.Checked(BetterCheckedListBox1.SelectedIndex)
End Sub
End Class
Deployment
Make sure the custom control created is strongly named. To do this, you must first generate a key file and tell the compiler to use it.
Generate a key file by running the following command at the Visual Studio Command Prompt:
sn -k C:\Better.snk
Then, add the following as the first line (under the Imports
) in the AssemblyInfo.vb file of your Custom control project.
<Assembly: AssemblyKeyFileAttribute("Better.snk")>
That's it. The BetterCheckedListBox
control can now be easily deployed to the Global Assembly Cache.