所有者绘制控件 - 可扩展 ListBox






4.85/5 (27投票s)
这是关于自绘控件系列文章的第一篇,重点介绍 ListBox 控件。本文包含了基本功能和一些有用的技巧,帮助您开始开发自己的自绘控件。
引言
这是自绘控件系列教程的第一篇。其目的不是创建一个带有大量花哨功能的控件,这留给您自己的想象力,而是演示一种创建自己的自绘 ListBox 控件的方法。
此控件与我在 CP 上看到的其他派生 ListBox 控件不同,因为它允许项目处于折叠或扩展状态。换句话说,当您单击一个项目时,它会切换项目视图,使其可以最小化或最大化,从而允许在最大化时查看其他信息。在截图上,“Just Chill Man!”项目已展开,所有其他项目都已折叠。
对于此演示,我创建了一个 ExtendedListBoxItem
类,该类派生自用于维护项目信息的对象,并包含 DrawCollapsed
、DrawExtended
、DrawBorder
和 AddGlow
方法。这个类只是一个模板,用于展示可能性以及构建您自己的项目类的起点。我相信,一旦掌握了这些技能,您将能够创建一个更大、更好、更棒的 ListBoxItemControl
类,以满足您的特定需求。
入门
ExtendedListBoxControl
派生自 System.Windows.Forms.ListBox
控件,设置了以下属性
ExtendedListBoxControl.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
这就是允许我们自己绘制项目的原因。将 DrawMode
设置为 OwnerDrawVariable
意味着项目可以是任何我们想要的大小,最大为 255 像素。
此外,我们必须重写两个虚拟方法才能实际进行绘制
OnMeasureItem
- 确定项目的大小。OnDrawItem
- 实际绘制项目的方法。
我们将要重写的另一个方法是
OnMouseDown
- 捕获鼠标按下事件。
下一节将更详细地描述这三种方法。
重写
如前所述,我们必须重写两个虚拟方法才能绘制我们自己的控件。第一个是 OnMeasureItem
方法。调用此方法以确定绘制项目的大小。在我们的例子中,它取决于项目是处于折叠状态还是扩展状态。这些值由 MinSize
(折叠大小)和 MaxSize
(扩展大小)属性确定。
///
/// Called when invalidated to find the item bounds. Since we are only concerned
/// with the height we only need to set this value and leave the bounds.width
/// as is!
///
protected override void OnMeasureItem(System.Windows.Forms.MeasureItemEventArgs e)
{
base.OnMeasureItem(e);
if ((e.Index < 0) || (e.Index >= itemCache.Count))
return;
ExtendedListBoxItem xlbi = (ExtendedListBoxItem) itemCache[e.Index];
//If its the current selection and not collapsed
// set height to Max. otherwise just set the item selected to Min. value.
if ((e.Index == _currentIndex) && !isCollapsed)
e.ItemHeight = xlbi.MaxSize;
else
e.ItemHeight = xlbi.MinSize;
e.ItemWidth = this.Width;
}
我们终于得到了最重要的方法,即实际进行绘制的方法。此方法使用 isCollapsed
属性来确定是绘制折叠状态的项目还是扩展状态的项目。
///
/// Called when the item needs to be drawn
///
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
{
base.OnDrawItem(e);
//If not a valid index just ignore
if ((e.Index < 0) || (e.Index >= itemCache.Count))
return;
ExtendedListBoxItem xlbi = (ExtendedListBoxItem) itemCache[e.Index];
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//Draw the item in current state.
if ((e.Index == _currentIndex) && !isCollapsed)
xlbi.DrawExpanded(e);
else
xlbi.DrawCollapsed(e);
}
项目的绘制委托给项目本身;通过这种方式,您可以使用基类提供基本功能,创建自己的类或从 ExtendedLstBoxItem
派生一个类,并使用其全部或部分功能来满足您的需求。
索引值通过 DrawItemEventArgs
传递。我们使用此索引来检索对项目的引用,然后调用其相应的 DrawXXXX
方法来完成这项工作!
接下来,我们重写 OnMouseDown
而不是 OnSelectedIndexChanged
,因为后者仅在用户单击与所选索引不同的索引时才被调用,并且我们需要捕获鼠标按下事件,无论选择了哪个项目。当尝试使列表项无效时,我遇到了一个问题。当尝试展开或折叠项目时,没有调用 OnMeasureItem
处理程序。为了解决这个问题,我搜索了 Google,并找到了一篇晦涩的文章,该文章建议必须先删除要无效的项目,然后再将其重新插入到列表中。以下代码对 Previous 和 Current 项目执行此操作
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
base.OnMouseDown(e);
:
:
//Invalidate previous selection
InvalidateItem(_previousIndex);
//Invalidate current selection
InvalidateItem(_currentIndex);
}
///
/// Invalidates the item at the provided index.
///
public void InvalidateItem(int index)
{
if ((index < 0) || (index >= itemCache.Count))
return;
//All we need to do here is make sure
//we get the correct item index.
this.Items.RemoveAt(index);
this.Items.Insert(index, " ");
}
关注点
在运行演示时,我提供了一个 PropertyGrid
,以便可以直观地设计项目。此功能非常方便,因为它允许您使用不同的属性,并查看发生了什么事情,而无需在每次更改时都重新编译。感谢 Microsoft 提供了这个方便的控件。
注意:MinSize
和 MaxSize
属性不会立即生效;您必须切换项目才能反映更改。
希望这篇文章对您有所帮助!