65.9K
CodeProject 正在变化。 阅读更多。
Home

一个多色、多字体的 CheckedListBox

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (10投票s)

2015 年 12 月 7 日

CPOL

5分钟阅读

viewsIcon

36559

downloadIcon

1748

一个具有动态前景色、背景色和字体的 CheckedListBox

CustomCheckedListBox test driver

CustomCheckedListBox test driver 2

CustomCheckedListBox test driver 3

引言

考虑到大多数 WinForms 控件内置的灵活性,您可能会认为 CheckedListBox 会提供一些基本功能,例如项目背景色。但无论出于何种原因,它曾经并且仍然是一个简单而有限的控件。搜索互联网可以找到大量关于设置颜色的讨论,建议从重写 OnDrawItem 到放弃该控件而改用 ListBox。这些充其量只是部分解决方案。

背景

在我为一台集成电路晶圆处理机设计的界面中,我需要一个自定义的 CheckedListBox。二十五个 IC 晶圆被放置在一个塑料托盘中,最多可以加载四个托盘到机器中。界面显示每个晶圆的状态(未处理已处理 等),以及某些病态情况,例如错位或双槽晶圆。托盘插槽也可能是空的。背景颜色用于一目了然地轻松确定整体状态。

下图摘自实际界面,尽管 CheckedListBox 的内容是随机生成的测试数据,仅用于验证界面的正确运行。

Wafer Sorter

愿望清单

一个漂亮的 CheckedListBox 控件可以让你

  1. 动态设置项目背景色
  2. 动态设置项目前景色
  3. 动态设置项目字体
  4. 将项目高度设置为非标准值
  5. 控制焦点/选择视觉属性
  6. 与 Visual Studio 集成

意外问题

重写 OnDrawItem 来处理更改前景色、背景色和字体应该不难,我当时是这么想的。DrawItemEventArgs 对象具有 ForeColorBackColorFont 属性。只需在新 DrawItemEventArgs e2 中实现你想要设置这三个属性的任何逻辑,然后调用 base.OnDrawItem(e2)CheckedListBox 有一个 ItemHeight 属性,所以如果你想要独立于字体的高度,这也很容易管理。没什么难的。除了它不起作用。颜色正确更改,但无论我做什么都无法让控件使用我指定的字体,而项目高度则随心所欲。

卢克,看看源码

好吧,这很烦人。是时候深入研究一下了。CheckedListBox 的源代码可以在 dotnetframework.org 上在线获取。OnDrawItem(DrawItemEventArgs e) 证实了我的猜测:项目是使用 e.ForeColore.BackColor 绘制的,但它完全忽略了 e.Font,而是使用了控件的 Font。项目高度是某些晦涩计算的焦点。

此时,我只想说,试图将此控件的源代码复制到您自己的项目中,然后修改它以实现您想要的功能,注定会失败。但不要害怕,因为所有这些问题都有可行的、尽管不完美的解决方案。

不完美,但有进步

本文介绍了一个名为 CustomCheckedListBox 的 C# 类,它继承自 CheckedListBox,提供了列表中的所有功能,或多或少。该类和一个测试驱动程序(在 VS2015 解决方案中)可以通过页面顶部的链接下载。让我们从查看委托和属性开始。

public class CustomCheckedListBox : CheckedListBox
{
	public delegate Color GetColorDelegate(CustomCheckedListBox listbox, DrawItemEventArgs e);
	public delegate Font GetFontDelegate(CustomCheckedListBox listbox, DrawItemEventArgs e);

	[Description("Supply a foreground color for each item")]
	public event GetColorDelegate GetForeColor = null;
	[Description("Supply a background color for each item")]
	public event GetColorDelegate GetBackColor = null;
	[Description("Supply a font for each item")]
	public event GetFontDelegate GetFont = null;

	[Description("Set this if you don't like the standard selection appearance")]
	public bool DrawFocusedIndicator { get; set; }

	public override int ItemHeight { get; set; }

与各种在线解决方案相比,委托是 CustomCheckedListBox 的第一个重大区别。仅仅在控件中设置颜色会使所有项目看起来都一样,也许会因状态而区分。没意思。通过为这些委托提供方法,每个项目都可以根据您选择的任何标准着色,并且颜色可以动态更改。相同的机制可用于提供每个项目的字体。对于我的晶圆分拣机,当分拣过程开始时,项目列表是固定的。背景色会发生变化,以指示每个晶圆的当前状态。

ItemHeight 属性在此处必须被重写,因为基类属性实际上是只读的。DrawFocusedIndicator 将在下面解释。让我们接下来看看构造函数。

public CustomCheckedListBox(GetColorDelegate back = null, 
	GetColorDelegate fore = null, GetFontDelegate font = null)
{
	GetForeColor = fore;
	GetBackColor = back;
	GetFont = font;

	//******************************************************************
	// If you want to set the item height to a specific value that can
	// be independent of the font size, this is the place to do it.
	//******************************************************************
	ItemHeight = 14;
}

由于所有委托都为 null,因此整个类实际上是惰性的,因此在您提供这些委托的方法之前,它将表现得像一个普通的 CheckedListBox。这里又出现了 ItemHeight,我们将在下一节讨论。它必须在此处初始化,以防止 Visual Studio 设计器崩溃。默认情况下,ItemHeight 设置为 14,这似乎与 CheckedListBox 使用的值匹配。让我们继续 OnDrawItem 方法。

protected override void OnDrawItem(DrawItemEventArgs e)
{
	Color foreColor = (GetForeColor != null) ? GetForeColor(this, e) : e.ForeColor;
	Color backColor = (GetBackColor != null) ? GetBackColor(this, e) : e.BackColor;

	//
	// The CheckListBox is going to ignore the font in the event
	// args and use its own.  So, make its own the one we want it
	// to be.  For this to always work right, we will have to shut
	// down the OnFontChanged method below.
	//
	if (GetFont != null) this.Font = GetFont(this, e);

	//
	// If desired, draw an item focused, but not selected.
	//
	DrawItemState state = (DrawFocusedIndicator)
		? ((e.State & DrawItemState.Focus) == DrawItemState.Focus )
			? DrawItemState.Focus 
			: DrawItemState.None
		: e.State;

	//
	// e.Font is going to be ignored.
	//
	DrawItemEventArgs e2 = new DrawItemEventArgs(e.Graphics, 
		e.Font, e.Bounds, e.Index, state, foreColor, backColor);

	base.OnDrawItem(e2);
}

protected override void OnFontChanged(EventArgs e)
{
	if (GetFont == null) base.OnFontChanged(e);
}

基本上,此方法仅修改 DrawItemEventArgs 以反映委托为每个项目从应用程序获取的颜色。if (GetFont != null) 行会更改基类 CheckedListBox 的字体。在某些情况下,base.OnFontChanged() 会导致问题,因此如果我们提供每个项目的字体,则禁用它。

DrawFocusedIndicator 会抑制正常的视觉选择指示器(基本上是蓝色背景上的白色项目文本)。取而代之的是,选定的项目会显示一个焦点指示器,这只是一个虚线边框。您可以根据需要打开或关闭此功能。

一些注意事项

首先,您可以为每个项目提供不同的字体,但它们都需要相同的大小。CheckedListBox 不容易处理多个项目高度。其次,您必须在构造函数中设置一个固定高度,并且无法更改。

Visual Studio 集成

Visual Studio Integration

如果您将 CustomCheckedListBox 添加到项目中,并将 using Qodex 添加到您的窗体中,Visual Studio 会将该控件添加到您的工具箱中。您现在可以像其他任何控件一样将该控件拖到窗体上,并访问其所有属性和事件。这与制作自定义控件不同,后者超出了本文的范围,但您可能会发现它正好能满足您的需求。

功劳归功于

此控件的灵感来自 这个 StackOverflow 文章。虽然它仅限于静态颜色控制,但它为我指明了正确的方向。

轮到你了

就这样。我希望您觉得这篇文章很有趣,甚至可能很有用。您可能对现有的 CustomCheckedListBox 感到满意,或者您可能已经在考虑一些您希望看到的增强功能。也许您希望复选框与项目背景色匹配。请告诉我您所做的任何巧妙修改。

历史

  • 2015 年 12 月 06 日:初始版本
© . All rights reserved.