带复选框的 OwnerDraw ComboBox






4.74/5 (24投票s)
带复选框的 OwnerDraw ComboBox
引言
我需要向列表视图添加过滤功能。列表视图已经有一个ToolStrip
,我可以向其中添加适当的控件。我决定使用带有CheckBox
的下拉ComboBox
来打开和关闭项目是最节省空间和最直观的实现方式。但是,这需要一个自绘的ComboBox
。幸运的是,自 2.0 起 .NET Framework 包含一个辅助类CheckBoxRenderer
,只要给定正确的参数,它就会将复选框渲染到下拉列表中。
Using the Code
第一步是子类化System.Windows.Forms.ComboBox
类
public partial class CheckComboBox : ComboBox
{
public CheckComboBox()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
...
}
...
}
请注意,我设置了DrawMode
属性来告诉ComboBox
,我们打算自己渲染下拉列表项。下一步是定义一个类来包含我们的下拉列表项数据并维护状态。这是一个简单的类
public class CheckComboBoxItem
{
public CheckComboBoxItem( string text, bool initialCheckState )
{
_checkState = initialCheckState;
_text = text;
}
private bool _checkState = false;
public bool CheckState
{
get { return _checkState; }
set { _checkState = value; }
}
private string _text = "";
public string Text
{
get { return _text; }
set { _text = value; }
}
public override string ToString()
{
return "Select Options";
}
}
然后我们将委托连接到ComboBox
的DrawItem
事件
this.DrawItem += new DrawItemEventHandler(CheckComboBox_DrawItem);
并按如下方式实现它
void CheckComboBox_DrawItem( object sender, DrawItemEventArgs e )
{
if (e.Index == -1)
{
return;
}
if( !( Items[ e.Index ] is CheckComboBoxItem ) )
{
e.Graphics.DrawString(
Items[ e.Index ].ToString(),
this.Font,
Brushes.Black,
new Point( e.Bounds.X, e.Bounds.Y ) );
return;
}
CheckComboBoxItem box = (CheckComboBoxItem)Items[ e.Index ];
CheckBoxRenderer.RenderMatchingApplicationState = true;
CheckBoxRenderer.DrawCheckBox(
e.Graphics,
new Point( e.Bounds.X, e.Bounds.Y ),
e.Bounds,
box.Text,
this.Font,
( e.State & DrawItemState.Focus ) == 0,
box.CheckState ? CheckBoxState.CheckedNormal :
CheckBoxState.UncheckedNormal );
}
在此委托中,我们做的第一件事是验证我们正在渲染的项目是否已添加为CheckComboBoxItem
。如果不是,我们将其渲染为简单的字符串。否则,我们从Items
集合中获取相应的CheckComboBoxItem
(使用DrawItemEventArgs.Index
属性)。然后我们调用CheckBoxRenderer.DrawCheckBox()
方法,传入我们想要渲染CheckBox
的Graphics
对象,以及位置、大小、文本、字体、焦点和选中状态。
结果是它会将我们的下拉列表项渲染成复选框的样子
接下来,我们希望在选择其中一个项目时切换选中状态。所以我们连接另一个委托,但这次连接到SelectedIndexChanged
事件
this.SelectedIndexChanged +=
new EventHandler( CheckComboBox_SelectedIndexChanged );
并按如下方式实现它
void CheckComboBox_SelectedIndexChanged( object sender, EventArgs e )
{
CheckComboBoxItem item = (CheckComboBoxItem)SelectedItem;
item.CheckState = !item.CheckState;
...
}
这允许我们切换下拉列表中的复选框,但不允许此控件的用户知道发生了任何事情。所以我们还添加一个公共事件,以通知控件的用户下拉列表中项目的选中状态发生了更改
public event EventHandler CheckStateChanged;
并且我们在上面切换状态时触发此事件,因此完整的方法如下所示
void CheckComboBox_SelectedIndexChanged( object sender, EventArgs e )
{
CheckComboBoxItem item = (CheckComboBoxItem)SelectedItem;
item.CheckState = !item.CheckState;
if (CheckStateChanged != null)
CheckStateChanged(item, e);
}
使用控件
要使用该控件,您只需将其添加到您选择的容器,然后像这样向其添加项目
checkComboBox1.Items.Add(new CheckComboBox.CheckComboBoxItem("One", true));
checkComboBox1.Items.Add(new CheckComboBox.CheckComboBoxItem("Two", true));
checkComboBox1.Items.Add(new CheckComboBox.CheckComboBoxItem("Three", true));
要获取用户更改了选中状态的通知
this.checkComboBox1.CheckStateChanged +=
new EventHandler(this.checkComboBox1_CheckStateChanged);
可以像这样处理
private void checkComboBox1_CheckStateChanged(object sender, EventArgs e)
{
if (sender is CheckComboBox.CheckComboBoxItem)
{
CheckComboBox.CheckComboBoxItem item =
(CheckComboBox.CheckComboBoxItem)sender;
...
}
}
关注点
有一个让我烦恼的地方,我确信有比我提出的更好的解决方案。我希望控件的TextBox
部分包含固定文本且不可编辑。因此,作为权宜之计,我将其初始化为字符串“Select Options”并覆盖CheckComboBoxItem
类的ToString()
方法以始终返回相同的字符串。这样,无论用户最后选择哪个项目,该字符串永远不会改变。但这并没有使其不可编辑。
如果时间允许,我想进行许多添加
- 正确对齐文本,使其左对齐(需要找出如何计算复选框位图的宽度才能正确执行此操作)
- 在下拉列表的顶部添加带有分隔符的“全选”和“全不选”项
- 允许其他控件类型进入下拉列表(例如 - 单选按钮)
历史
- 2007 年 5 月 24 日:发布原始版本