扩展 C# ListView 的可折叠组(第一部分)
探讨一种在 .NET ListView 控件中创建更实用的可扩展组的快速方法。
注意:这篇帖子有点长。然而,大部分长度是由于代码发布(即使删掉了一些额外的东西)。即便如此,我还是将其分成了两部分:您现在正在阅读的部分,以及将在扩展 C# ListView 的可折叠组(第二部分)中找到的后续部分。请耐心阅读!
同样值得关注:
过去两个月,我一直沉浸在工作项目中。遗憾的是,没什么令人兴奋的,没有激动人心的尖端技术,只是另一个企业数据库,使用 .NET 平台中成熟且略显沉闷的 Winforms 库。
然而,我偶然发现了一个有趣的开发需求,本质上是一个可扩展的通用 `ListView` 控件组,可以想象成 `ListView` 和 `TreeView` 的结合,或者 Listview 控件内置的“组”可以展开/折叠、右键单击等。
图 1 - 单个组展开
图 2 - 多个组展开(注意容器控件上的滚动条)
图 3 - 扩展列(注意特定 ListGroup 上的滚动条)
对于那些要指出 `ObjectListView` 中存在此类控件的人,我已知晓。但是,我需要使用标准的 .NET 库来完成。我也知道标准的 Listview 组可以强制展开/折叠,但我需要一个更快的解决方案。此外,导致标准 Listview 组展开/折叠似乎依赖于大量的 Windows API 调用,而我对那个晦涩的领域并不熟练。
我的解决方案是扩展 Listview 控件,然后在 `FlowLayoutPanel` 控件中组装多个 Listview 控件。每个 Listview 的 `ColumnHeaders` 双倍用作“组”。单击最左边的列会切换组的展开/折叠。展开/折叠状态通过列左端的实心箭头图像指示。在组为空(不包含 ListViewItems,看起来就像一个折叠的组)的情况下,箭头图像为空。
为清晰起见,我将聚合控件称为“GroupedListControl”,将每个包含的 ListView 称为“ListGroup”。我相信这个命名方案有很大的改进空间。在本叙述中,假设以下内容:
- 一个 `GroupedListControl` 包含一个或多个 ListGroups,ListGroups 包含 ListViewItems。
- `GroupedListControl` 是一个继承自 `FlowLoyoutPanel` 的容器。
- `ListGroup` 是一个继承自 Winforms `ListView` 的容器。
使用内部类扩展原生 ListView 行为
首先,我需要扩展一些标准的 .net ListView 控件的基本行为。例如,为了区分对待最左边的列(第 0 列),并监控列的添加和删除,以便控制和调整滚动条的外观,我需要一个在列添加和删除时触发的事件。我在网上查阅了一些资料,并在Code Project 上的帖子中找到了我的解决方案。我能够借鉴那里的核心概念并实现我需要的功能。
每个 ListGroup 希望拥有的基本行为列表包括:
- 单击 ListGroup 的最左边 `ColumnHeader` 应切换组的展开/折叠。
- 当添加第一列时,应将展开/折叠指示箭头添加到最左边的 `ColumnHeader`。
- 如果任何 `ListGroup` 中列的总宽度超过其包含的 `GroupedListControl` 的客户端区域宽度,则 ListGroup 应显示水平滚动条。
- 每个 `ListGroup` 的高度应调整,以便在展开状态下显示所有包含的 Listview 项目,最多可达设计时或运行时确定的可选最大高度。如果包含的 ListViewItems 数量超过此最大值,则会显示 Individual ListGroup 垂直滚动条。
- 任何时候当特定 ListGroup 显示水平滚动条时,该 ListGroup 的客户端区域应进行调整,以便水平滚动条不会部分遮挡最后显示的 `ListViewItem`。
- 检测对特定 ListView ColumnHeaders 的鼠标右键单击,并允许使用特定于右键单击 ColumnHeaders 与 ListView Items 的上下文菜单。
这涵盖了控件正常运行所需的一些基本功能。让我们看看这里的基本代码骨架。我们将在帖子后面填充一些空的代码存根。
此项目的完整源代码可在GitHub 上的GroupedListControl 项目源代码中找到。源代码包含此处未涵盖的额外代码。我们将在接下来的帖子中讨论其中一些内容。
首先,我定义了一些自定义事件参数类,它们将在控件内部使用。虽然它们在功能上与一些现有的 `ListView` 事件参数类相似,但我希望尽可能保持清晰的命名。这些是后续代码所必需的。
请注意,所有示例都需要在您的代码文件开头添加以下引用:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Layout;
using System.Runtime.InteropServices;
using System.ComponentModel;
自定义事件参数:
namespace GroupedListControl
{
public class ListGroupColumnEventArgs : EventArgs
{
public ListGroupColumnEventArgs(int Columnindex)
{
this.ColumnIndex = ColumnIndex;
}
public int ColumnIndex { get; set; }
}
public class ListGroupItemEventArgs : EventArgs
{
public ListGroupItemEventArgs(ListViewItem Item)
{
this.Item = Item;
}
public ListGroupItemEventArgs(ListViewItem[] Items)
{
this.Items = Items;
}
public ListViewItem Item { get; set; }
public ListViewItem[] Items { get; set; }
}
}
现在是 `ListGroup` 类本身。为了简洁起见,我省略了一些额外的函数(即使这样,这仍然是一大块代码……),并留下了一些稍后要填写的代码存根。这只是为了给您一个最基本的类结构和核心功能需求的概念。目前的这段代码是无法运行的。
ListGroup 类 - 要点和代码存根
public class ListGroup : ListView
{
// DELEGATES AND ASSOCIATED EVENTS:
// NOTE: The events and delegates related to Column and Item Addition/Removal are
// called by the inner classes ListGroupColumnCollection and LIstGroupItemCollection.
// The proper function of the control depends upon these.
// Delegates to handle Column addition and removal Events:
public delegate void ColumnAddedHandler(object sender, ListGroupColumnEventArgs e);
public delegate void ColumnRemovedHandler(object sender, ListGroupColumnEventArgs e);
// Events related to Column Addition and removal:
public event ColumnAddedHandler ColumnAdded;
public event ColumnRemovedHandler ColumnRemoved;
// Delegates to handle Item Addition and Removal events:
public delegate void ItemAddedHandler(object sender, ListGroupItemEventArgs e);
public delegate void ItemRemovedHandler(object sender, ListGroupItemEventArgs e);
// Events related to Item Addition and Removal:
public event ItemAddedHandler ItemAdded;
public event ItemRemovedHandler ItemRemoved;
// Delegate and related events to process Group Expansion and Collapse:
public delegate void GroupExpansionHandler(object sender, EventArgs e);
public event GroupExpansionHandler GroupExpanded;
public event GroupExpansionHandler GroupCollapsed;
// Delegate and related Events to handle Listview Header Right Clicks:
public delegate void ColumnRightClickHandler(object sender, ColumnClickEventArgs e);
public event ColumnRightClickHandler ColumnRightClick;
// PRIVATE INSTANCES OF INNER CLASSES
// Instances of our inner classes, declared as private members so
// that our public Property accessors can be Read-only, yet allow
// direct manipulation from within the control instance
private ListGroupItemCollection _Items;
private ListGroupColumnCollection _Columns;
/// <summary>
/// Constructor Stub
/// </summary>
public ListGroup() : base()
{
// Implementation Code . . .
}
// INNER CLASS INSTANCE ACCESSORS:
/// <summary>
/// Hides the ListViewItemCollection internal to the base class,
/// and uses the new implementation defined as an inner class,
/// which sources an "ItemAdded" Event:
/// </summary>
public new ListGroupItemCollection Items
{
get { return _Items; }
}
/// <summary>
/// Hides the ColumnHeaderCollection internal to the base class,
/// and uses the new implementation defined as an inner class,
/// which sources a "ColumnAdded" Event:
/// </summary>
public new ListGroupColumnCollection Columns
{
get { return _Columns; }
}
// INNER CLASS DEFINITIONS:
/// <summary>
/// Inner class used to hide the ListViewColumnHeaderCollection
/// built in to the ListView Control and provide required extended behaviors.
/// </summary>
public class ListGroupColumnCollection : ListView.ColumnHeaderCollection
{
// Implementation Code Here . . .
}
/// <summary>
/// Inner class defined for ListGroup to contain List items.
/// Derived from ListViewItemCollection and modified to source events
/// indicating item addition and removal.
/// </summary>
public class ListGroupItemCollection : ListView.ListViewItemCollection
{
// Implementation Code Here . . .
}
// ITEM ADDITION AND REMOVAL:
/// <summary>
/// Raises the ItemAdded Event when a new item is
/// added to the items collection.
/// </summary>
private void OnItemAdded(ListViewItem Item)
{
// Code to set the size of the control to display all the items
// so far . . .
// Raise the ItemAddded event to any subscribers:
if (ItemAdded != null)
this.ItemAdded(this, new ListGroupItemEventArgs(Item));
}
/// <summary>
/// Raises the ItemRemoved Event when an item is
/// removed from the items collection.
/// </summary>
private void OnItemRemoved(ListViewItem Item)
{
// Code to set the size of the control to display all the items
// remaining after the current one is removed . . .
// Raise the ItemRemoved event to any subscribers:
if (ItemRemoved != null)
this.ItemRemoved(this, new ListGroupItemEventArgs(Item));
}
// COLUMN ADDITION AND REMOVAL:
/// <summary>
/// Raises the ColumnAdded Event when a new column is
/// added to the ColumnHeaders collection.
/// </summary>
private void OnColumnAdded(int ColumnIndex)
{
// Code to manage column additions. The first column added
// needs to have the Expand/Collapse/Empty image added . . .
// Raise the ColumnAdded event to any subscribers:
if (this.ColumnAdded != null)
this.ColumnAdded(this, new ListGroupColumnEventArgs(ColumnIndex));
}
/// <summary>
/// Raises the ColumnRemoved Event when a column is
/// remmoved from the ColumnHeaders collection.
/// </summary>
private void OnColumnRemoved(int ColumnIndex)
{
// Code to manage column removals . . .
// Raise the ColumnRemoved event to any subscribers:
if (this.ColumnRemoved != null)
this.ColumnRemoved(this, new ListGroupColumnEventArgs(ColumnIndex));
}
// USER ACTIONS:
/// <summary>
/// Handles the ListGroup ColumnClick Event sourced by the base.
/// </summary>
void ListGroup_ColumnClick(object sender, ColumnClickEventArgs e)
{
// Code to manage ColumnHeader Clicks. If the Event is sourced from
// the first Column (Column[0], toggle expansion/collapse of the list . . .
}
// CONTROL BEHAVIORS:
/// <summary>
/// Causes the list of items to expand, showing all items in the
/// Items collection.
/// </summary>
public void Expand()
{
// Do stuff to make the control expand . . .
// Raise the Expanded event to notify client code that the ListGroup has expanded:
if (this.GroupExpanded != null)
this.GroupExpanded(this, new EventArgs());
}
/// <summary>
/// Causes the Displayed list of items to collapse, hiding all items and
/// displaying only the columnheaders.
/// </summary>
public void Collapse()
{
// Do stuff to make the control collapse
// Raise the Collapsed event to notify client code that the ListGroup has expanded:
if (this.GroupCollapsed != null)
this.GroupCollapsed(this, new EventArgs());
}
}
现在,请密切关注我们定义内部类 `ListGroupColumnCollection` 和 `ListGroupItemCollection` 的代码存根。请注意,每个类都派生自 Winforms ListView 控件中相应的类。这就是我们实现许多自定义事件源行为的地方。同样,我在这里简化了类定义,省略了核心方法所需的各种重载(例如,有多种方法可以“添加”项目到任一集合中——这里我只涵盖最基本的。其余的定义在GroupedListControl 项目源代码中,可从我的 GitHub Repo 获取)。
请注意,每个内部类中的代码会导致在包含的 ListGroup 类中引发事件?这提供了列和 ListViewItems 添加/删除的事件源,因为这些事件未在基础 Winforms ListView 类中定义。我们需要它们才能在响应添加和删除时实现正确的控件展开和折叠。
注意:以这种方式使用内部类的核心概念改编自Simon Segal 在 Code Project 上的这篇文章。
以下代码将替换我们 ListGroup 类定义中的 `ListGroupColumnCollection` 类的代码存根。
ListGroupColumnCollection 类
/// <summary>
/// Inner class defined for ListGroup to contain ColumnHeaders.
/// Derived from ListView.ColumnHeaderCollection and modified to
/// source events indicating column addition and removal.
/// </summary>
public class ListGroupColumnCollection : ListView.ColumnHeaderCollection
{
// Reference to the containing ListGroup Control
private ListGroup _Owner;
public ListGroupColumnCollection(ListGroup Owner) : base(Owner)
{
_Owner = Owner;
}
/// <summary>
/// Gets the total width of all columns currently defined in the control.
/// </summary>
public int TotalColumnWidths
{
get
{
int totalColumnWidths = 0;
foreach(ColumnHeader clm in this)
totalColumnWidths = totalColumnWidths + clm.Width;
return totalColumnWidths;
}
}
/// <summary>
/// Adds a column to the current collection and raises
/// the OnColumnAddedEvent on the parent control.
/// </summary>
public new ColumnHeader Add(string text, int width, HorizontalAlignment textAlign)
{
ColumnHeader clm = base.Add(text, width, textAlign);
_Owner.OnColumnAdded(clm.Index);
return clm;
}
/// <summary>
/// Removes a column from the current collection and
/// raises the OnColumnRemoved Event on the parent control.
/// </summary>
public new void Remove(ColumnHeader column)
{
int index = column.Index;
base.Remove(column);
_Owner.OnColumnRemoved(index);
}
public new void Clear()
{
base.Clear();
}
} // ListGroupColumnCollection
以下代码将替换我们原始 `ListGroup` 类定义中 `ListGroupItemCollection` 类的空存根。
ListGroupItemCollection 类
/// <summary>
/// Inner class defined for ListGroup to contain List items. Derived from ListViewItemCollection
/// and modified to source events indicating item addition and removal.
/// </summary>
public class ListGroupItemCollection : System.Windows.Forms.ListView.ListViewItemCollection
{
private ListGroup _Owner;
public ListGroupItemCollection(ListGroup Owner) : base(Owner)
{
_Owner = Owner;
}
/// <summary>
/// New implementation of Add method hides Add method defined on base class
/// and causes an event to be sourced informing the parent about item additions.
/// </summary>
public new ListViewItem Add(string text)
{
ListViewItem item = base.Add(text);
_Owner.OnItemAdded(item);
return item;
}
/// <summary>
/// New implementation of Remove method hides Remove method defined on base class
/// and causes an event to be sourced informing the parent about item Removals.
/// </summary>
public new void Remove(ListViewItem Item)
{
base.Remove(Item);
_Owner.OnItemRemoved(Item);
}
} // ListGroupItemCollection
填充所有内容
我们正在寻求对标准 `ListView` 控件现有功能的根本扩展是让每个 `ListGroup` 响应特定用户输入能够“展开”和/或“折叠”。我们将希望定义一个单一的方法调用来执行所需的操作,因此我们将向 `ListGroup` 类添加一个名为 `SetControlHeight` 的方法。此方法评估控件的当前状态(折叠或展开),并调用相应的方法来切换到相反的状态。
每个 `ListGroup` 控件的最小折叠高度应足以显示列标题。`Expanded` 高度可以是无限的,或者可以通过设置 `MaximumHeight` 属性来约束。然而,在任何一种情况下,控件的高度都应包含足够的空间来显示列标题,以及偶数数量的 `ListViewItem`,以便最后一个项目在显示中完全可见。
如果控件的高度等于列标题的高度且项目数大于 0,则可识别折叠状态。否则,控件必须处于展开状态。我不得不随意地将标题高度设置为一个常量,该常量与标准 `ListView` 控件(25)的默认标题高度匹配。然而,这可以根据需要进行修改。
为了完成以上所有操作,我们需要在 `ListGroup` 类中定义一些私有成员。将以下代码添加到类声明区域(我仍然有点老派,我将大部分声明放在类声明之后,位于类的顶部)。在源文件中,以下内容出现在构造函数之前:
其他成员声明
// Text strings used as Image keys for the expanded/Collapsed image in the
// left-most columnHeader:
static string COLLAPSED_IMAGE_KEY = "CollapsedImage";
static string EXPANDED_IMAGE_KEY = "ExpandedImageKey";
static string EMPTY_IMAGE_KEY = "EmptyImageKey";
// "Magic number" approximates the height of the List View Column Header:
static int HEADER_HEIGHT = 25;
此时我们还需要一个构造函数。请注意,构造函数使用存储在项目资源(Properties.Resources)中的一些图像初始化 `ListView.SmallImageList`。这些图像包含在GroupedListControl 项目源代码中。用以下代码替换构造函数代码存根:
构造函数
public ListGroup() : base()
{
this.Columns = new ListGroupColumnCollection(this);
this.Items = new ListGroupItemCollection(this);
// The Imagelist is used to hold images for the expanded and contracted icons in the
// Left-most columnheader:
this.SmallImageList = new ImageList();
// The tilting arrow images are available in the app resources:
this.SmallImageList.Images.Add
(COLLAPSED_IMAGE_KEY, Properties.Resources.CollapsedGroupSmall_png_1616);
this.SmallImageList.Images.Add
(EXPANDED_IMAGE_KEY, Properties.Resources.ExpandedGroupSmall_png_1616);
this.SmallImageList.Images.Add
(EMPTY_IMAGE_KEY, Properties.Resources.EmptyGroupSmall_png_1616);
// Default configuration (for this sample. Obviously, configure to fit your needs:
this.View = System.Windows.Forms.View.Details;
this.FullRowSelect = true;
this.GridLines = true;
this.LabelEdit = false;
this.Margin = new Padding(0);
this.SetAutoSizeMode(AutoSizeMode.GrowAndShrink);
this.MaximumSize = new System.Drawing.Size(1000, 2000);
// Subscribe to local Events:
this.ColumnClick += new ColumnClickEventHandler(ListGroup_ColumnClick);
this.ItemAdded += new ItemAddedHandler(ListGroup_ItemAdded);
}
现在我们可以添加代码来管理控件响应用户操作的重新调整大小。虽然我们为 `Expand()` 和 `Collapse()` 方法存在存根,因为它们构成了我们控件的明显行为,但接下来的两个必须添加。`SetControlHeight()` 方法是我们用于调整控件高度的一站式调用。
SetControlHeight 方法
/// <summary>
/// Adjusts the item display area of the control in response to changes in the
/// expanded or collapsed state of the control.
/// </summary>
public void SetControlHeight()
{
if (this.Height == HEADER_HEIGHT && this.Items.Count != 0)
this.Expand();
else
this.Collapse();
}
将以上内容以及接下来的三个方法添加到我们的 `ListGroup` 类中。接下来的三个方法实际上执行了调整控件展开/折叠状态方面的工作。第一个函数通过评估多个因素(在注释中解释)返回适当的控件高度。我们的现有结构中没有此函数的存根,因此将其添加到 `SetControlHeight` 方法正下方。
PreferredControlHeight 函数
private int PreferredControlHeight() { int output = HEADER_HEIGHT; int rowHeight = 0; // determine the height of an individual list item: if(this.Items.Count > 0) rowHeight = this.Items[0].Bounds.Height; // In case the horizontal scrollbar makes an appearance, we will // need to modify the height of the expanded list so that it does not // obscure the last item (default is 10 px to leave a little space // no matter what): int horizScrollBarOffset = 10; // if the Width of the columns is greater than the width of the control, // the vertical scroll bar will be shown. Increase that offset height by the // height of the scrollbar (approximately the same as the height of a row): if (this.Columns.TotalColumnWidths > this.Width) horizScrollBarOffset = rowHeight + 10; // Increase the height of the control to accomodate the Columnheader, // all of the current items, and the value of the // horizontal scroll bar (if present): output = HEADER_HEIGHT + (this.Items.Count) * rowHeight + horizScrollBarOffset + this.Groups.Count * HEADER_HEIGHT; return output; }
然后,用以下内容替换 `Expand()` 和 `Collapse()` 代码存根。虽然 `PreferredControlHeight` 函数提供了 `ListGroup` 的最佳高度,但 `Expand` 和 `Collapse` 方法执行请求的操作,并确保展开/折叠/空图像在最左边的列中正确显示。
Expand 和 Collapse 方法
/// <summary>
/// Causes the list of items to expand, showing all items in the
/// Items collection.
/// </summary>
public void Expand()
{
if (this.Columns.Count > 0)
{
this.Height = this.PreferredControlHeight();
if (this.Items.Count > 0)
// Set the image in the first column to indicate an expanded state:
this.Columns[0].ImageKey = EXPANDED_IMAGE_KEY;
else
// Set the image in the first column to indicate an empty state:
this.Columns[0].ImageKey = EMPTY_IMAGE_KEY;
this.Scrollable = true;
// Raise the Expanded event to notify client code
// that the ListGroup has expanded:
if (this.GroupExpanded != null)
this.GroupExpanded(this, new EventArgs());
}
}
/// <summary>
/// Causes the Displayed list of items to collapse, hiding all items and
/// displaying only the columnheaders.
/// </summary>
public void Collapse()
{
if (this.Columns.Count > 0)
{
this.Scrollable = false;
// Collapse the ListGroup to show only the header:
this.Height = HEADER_HEIGHT;
if (this.Items.Count > 0)
// Set the image in the first column to indicate a collapsed state:
this.Columns[0].ImageKey = COLLAPSED_IMAGE_KEY;
else
// Set the image in the first column to indicate an empty state:
this.Columns[0].ImageKey = EMPTY_IMAGE_KEY;
// Raise the Collapsed event to notify client code that
// the ListGroup has expanded:
if (this.GroupCollapsed != null)
this.GroupCollapsed(this, new EventArgs());
}
}
将所有内容连接起来
现在我们需要将行为(展开/折叠)连接到适当的事件。其中一些是显而易见的。当用户单击最左边的列(带有展开/折叠状态图像)时,控件应切换此状态。但是,我们还需要控件在添加/删除项目时,以及在添加/删除列时调整其显示的区域(并可能切换状态图像)(因为添加列时,可能需要将状态图像添加到第一列)。
首先,我们将处理列的添加/删除。当向控件添加列时,如果它是第一列,它将需要添加初始状态图像,并且控件需要调整大小。由于它是第一列,因此可以合理地(但不完全)假设尚未添加任何项目,因此 `ListGroup` 是空的,并且状态图像应反映这一点。
将高亮显示的项目添加到 OnColumnAdded() 方法。
private void OnColumnAdded(int ColumnIndex)
{
if (ColumnIndex == 0)
{
this.Columns[0].ImageKey = EMPTY_IMAGE_KEY;
this.SetControlHeight();
}
if(this.ColumnAdded != null)
{
this.ColumnAdded(this, new ListGroupColumnEventArgs(ColumnIndex));
}
}
如果 ListGroup 已填充项目,并且最后一个列被删除,会发生什么?我对此没有很好的答案,除了清空 ListItems。在我看来,控件会失去其身份和目的。如果您对此有任何想法,请在评论中讨论,或分支GitHub 上的代码。无论如何,在删除列时,我们需要测试并查看被删除的列是否是控件中的最后一列。如果是,则调用 Clear() 方法。
private void OnColumnRemoved(int ColumnIndex)
{
// REMOVE ColumnWidthChanged Handler before removing columns:
this.ColumnWidthChanged -= new ColumnWidthChangedEventHandler(ListGroup_ColumnWidthChanged);
this.Height = this.PreferredControlHeight();
// RESTORE ColumnWidthChanged Event Handler:
this.ColumnWidthChanged += new ColumnWidthChangedEventHandler(ListGroup_ColumnWidthChanged);
if(this.ColumnRemoved != null)
this.ColumnRemoved(this, new ListGroupColumnEventArgs(ColumnIndex));
}
现在,我们简化的 `ListGroup` 的最后一块就是用户单击最左边的列(第 0 列),然后控件展开或折叠。状态图像为用户提供了一种视觉提示/暗示,暗示了当前状态。我们所要做的就是处理 `ColumnClick` 事件(由基础类 `ListView` 提供),这样我们就完成了(简化的)控件的这一部分。
用以下内容替换 **ListGroup_ColumnClick** 代码存根。
处理 ColumnClick 事件:
void ListGroup_ColumnClick(object sender, ColumnClickEventArgs e)
{
int columnClicked = e.Column;
// The first column (Column[0]) is what activates the expansion/collapse of the
// List view item group:
if (columnClicked == 0)
{
this.SuspendLayout();
this.SetControlHeight();
this.ResumeLayout();
}
}
第一部分总结
很遗憾,我需要将此项目分成两部分。即使是这部分也太长了(尽管大部分长度只是代码示例)。
在第一篇帖子中,我们研究了如何扩展 Winforms `ListView` 类,使其可以作为容器控件内的组件。我们还研究了通过使用内部类来扩展 `ListView` 控件的事件源,这些内部类用于扩展 `ListViewColumnCollection` 和 `ListViewItemCollection` 类。
在下一篇帖子中,我们将把 `ListGroup` 类集成到容器 `GroupedListControl` 中。在此过程中,我们将需要调用一些 Windows API。我讨厌那样做,但事实就是如此(我讨厌它是因为我对 Win32 API 不太熟悉,所以这类事情对我来说很难!)。之后,我们将研究一些特殊方法来生成特定于右键单击 List Group 列标题的自定义上下文菜单,以及一些其他使整个控件正常工作的有趣细节。
本文继续:扩展 C# ListView 的可折叠组(第二部分)
报告错误。提交改进。行善。帮助我变得更好!
我将尝试在几天内发布下一篇帖子。在此期间,如果您在代码中发现任何错误,或者发现整体实现有改进的空间,请报告错误(在此处的评论中,或在 Github 上),并随时分支源代码并提交拉取请求以进行改进。我曾多次说过,我需要所有能得到的帮助!