构建更好的 CheckedListBox 控件






3.10/5 (18投票s)
2003年7月12日
1分钟阅读

158403

4253
这个自定义的 CheckedListBox 控件可以防止用户更改条目的选中状态,修复 TabControl 错误,并添加额外的功能。
问题
- 控件在使用
TabControl
时会忘记条目的选中状态。 - 用户可以选中或取消选中条目。
- 无法根据索引引用条目。
解决方案
所有这些问题都可以通过使用自定义控件来解决。
自定义控件
下面的代码为 CheckedListBox
控件添加了 AutoCheck
属性,该属性与 CheckBox
控件具有相同的属性。它还实现了一个错误修复,并允许像在 ComboBox
中一样引用控件中的条目。要创建我们的 BetterCheckedListBox
控件,请创建一个新的 VB.NET 类库项目,并将以下代码复制到 Class1.vb 中。
'Show the CheckedListBox icon in the Toolbox for our component
<ToolboxBitmap(GetType(CheckedListBox))> _
Public Class BetterCheckedListBox
Inherits System.Windows.Forms.CheckedListBox
'----------------------------------------------------------
' Class level variables
'----------------------------------------------------------
Dim AllowChecks As Boolean 'Controls whether checkstate can be changed
Dim ChkMember As String 'The DataColumn to use for checkstate
Dim dt As DataTable 'Data to display
'----------------------------------------------------------
' Bug Fix
'----------------------------------------------------------
' When the CheckedListBox control is in a Tabcontrol
' and that the Datasource property is used to fill
' up the item list, setting the clb's visible property
' to false, then to true, or flipping the tabs would
' cause the clb to "forget" the checks.
'
' Implement Carl Mercier's workaround
' https://codeproject.org.cn/cs/combobox/FixedCheckedListBox.asp
'----------------------------------------------------------
Public Overloads Property DataSource() As Object
Get
'Return our datatable variable
DataSource = CType(dt, Object)
End Get
Set(ByVal value As Object)
'Set our datatable variable
dt = CType(value, DataTable)
LoadData()
End Set
End Property
Private Function LoadData()
Dim bufAllowChecks = AllowChecks
If AllowChecks = False Then
'This is needed so we can change checkstates
AllowChecks = True
End If
'Clear items
MyBase.Items.Clear()
'Fill it again
Dim i As Integer
For i = 0 To dt.DefaultView.Count - 1
'Determine whether to check each item or not
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
'Added or deleted records won't show without a refresh
Public Overrides Sub Refresh()
LoadData()
End Sub
'----------------------------------------------------------
' Allow / Lock Checkstate Functionality
'----------------------------------------------------------
' Only let users check or uncheck items when you let them.
' Note that you can't programmatically check or uncheck
' items either unless AllowChecks is True.
'
' This is an emulation of the Checkbox control's AutoCheck
' property.
'----------------------------------------------------------
'Show our property under the Behavior section of the Properties window
'So that it can be set at design time
<System.ComponentModel.Description("Allow checkstate to be changed"), _
System.ComponentModel.Category("Behavior")> _
Public Property AutoCheck() As Boolean
Get
'Return our checkstate variable
AutoCheck = AllowChecks
End Get
Set(ByVal value As Boolean)
'Set our checkstate variable
AllowChecks = value
End Set
End Property
'Override the ItemCheck event with our own
Protected Overrides Sub OnItemCheck(ByVal ice As _
System.Windows.Forms.ItemCheckEventArgs)
'Allow checks to be changed only if AutoCheck = True
If AllowChecks = False Then
ice.NewValue = ice.CurrentValue
End If
End Sub
'----------------------------------------------------------
' Reference Item By It's Index Functionality
'----------------------------------------------------------
' Wouldn't it be nice if you could reference items in a
' CheckedListBox control just like you can with a
' ComboBox? Well now you can!
'
' Obtain Checkstatus, Text, and Values via an items index
'----------------------------------------------------------
<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
'Search for the item in CheckedIndices to see if its checked or not
If MyBase.CheckedIndices.IndexOf(index) <> -1 Then
Checked = True
Else
Checked = False
End If
End Get
Set(ByVal value As Boolean)
'Set item's checkstate
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
编译此项目后,将输出文件 BetterCheckedListBox.dll 复制到 C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\PublicAssemblies\。
然后右键单击工具箱,选择“添加/删除”,选择 BetterCheckedListBox
,然后单击确定将我们的自定义控件添加到工具箱。
演示项目
要创建我们的演示项目,请启动一个新的 VB.NET Windows 应用程序项目,并将以下代码复制到 Form1.vb 中。运行该项目,下面也显示了演示的屏幕截图。
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 'Checked is False
dt.Rows.Add(dr)
'Force refresh in order to show changes
BetterCheckedListBox1.Refresh()
End Sub
Private Sub Form1_Load(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles MyBase.Load
'Load some items
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
'Set DataBindings
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
'Allow / Lock items
BetterCheckedListBox1.AutoCheck = Not CheckBox1.Checked
End Sub
Private Sub BetterCheckedListBox1_SelectedIndexChanged(ByVal _
sender As System.Object, ByVal e As _
System.EventArgs) Handles _
BetterCheckedListBox1.SelectedIndexChanged
'Determine whether item is checked or not
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
部署
确保创建的自定义控件已进行强命名。为此,您必须首先生成一个密钥文件并告诉编译器使用它。
在 Visual Studio 命令提示符下运行以下命令生成密钥文件
sn -k C:\Better.snk
然后,将以下内容作为第一行(在 AssemblyInfo.vb 文件的 Imports
下方)添加到您的自定义控件项目的 AssemblyInfo.vb 文件中。
<Assembly: AssemblyKeyFileAttribute("Better.snk")>
完成了。BetterCheckedListBox
控件现在可以轻松部署到全局程序集缓存中。