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

扩展 C# ListView 的可折叠组(第一部分)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2012 年 9 月 3 日

CPOL

11分钟阅读

viewsIcon

70975

探讨一种在 .NET ListView 控件中创建更实用的可扩展组的快速方法。

注意:这篇帖子有点长。然而,大部分长度是由于代码发布(即使删掉了一些额外的东西)。即便如此,我还是将其分成了两部分:您现在正在阅读的部分,以及将在扩展 C# ListView 的可折叠组(第二部分)中找到的后续部分。请耐心阅读!

同样值得关注:

过去两个月,我一直沉浸在工作项目中。遗憾的是,没什么令人兴奋的,没有激动人心的尖端技术,只是另一个企业数据库,使用 .NET 平台中成熟且略显沉闷的 Winforms 库。

然而,我偶然发现了一个有趣的开发需求,本质上是一个可扩展的通用 `ListView` 控件组,可以想象成 `ListView` 和 `TreeView` 的结合,或者 Listview 控件内置的“组”可以展开/折叠、右键单击等。

图 1 - 单个组展开

Gl-Demo-1_thumb6

图 2 - 多个组展开(注意容器控件上的滚动条)

Gl-Demo-3-Three-Expanded_thumb2

图 3 - 扩展列(注意特定 ListGroup 上的滚动条)

Gl-Demo-4-Widen-Column_thumb3

对于那些要指出 `ObjectListView` 中存在此类控件的人,我已知晓。但是,我需要使用标准的 .NET 库来完成。我也知道标准的 Listview 组可以强制展开/折叠,但我需要一个更快的解决方案。此外,导致标准 Listview 组展开/折叠似乎依赖于大量的 Windows API 调用,而我对那个晦涩的领域并不熟练。

我的解决方案是扩展 Listview 控件,然后在 `FlowLayoutPanel` 控件中组装多个 Listview 控件。每个 Listview 的 `ColumnHeaders` 双倍用作“组”。单击最左边的列会切换组的展开/折叠。展开/折叠状态通过列左端的实心箭头图像指示。在组为空(不包含 ListViewItems,看起来就像一个折叠的组)的情况下,箭头图像为空。

为清晰起见,我将聚合控件称为“GroupedListControl”,将每个包含的 ListView 称为“ListGroup”。我相信这个命名方案有很大的改进空间。在本叙述中,假设以下内容:

  1. 一个 `GroupedListControl` 包含一个或多个 ListGroups,ListGroups 包含 ListViewItems。
  2. `GroupedListControl` 是一个继承自 `FlowLoyoutPanel` 的容器。
  3. `ListGroup` 是一个继承自 Winforms `ListView` 的容器。

使用内部类扩展原生 ListView 行为

首先,我需要扩展一些标准的 .net ListView 控件的基本行为。例如,为了区分对待最左边的列(第 0 列),并监控列的添加和删除,以便控制和调整滚动条的外观,我需要一个在列添加和删除时触发的事件。我在网上查阅了一些资料,并在Code Project 上的帖子中找到了我的解决方案。我能够借鉴那里的核心概念并实现我需要的功能。

每个 ListGroup 希望拥有的基本行为列表包括:

  1. 单击 ListGroup 的最左边 `ColumnHeader` 应切换组的展开/折叠。
  2. 当添加第一列时,应将展开/折叠指示箭头添加到最左边的 `ColumnHeader`。
  3. 如果任何 `ListGroup` 中列的总宽度超过其包含的 `GroupedListControl` 的客户端区域宽度,则 ListGroup 应显示水平滚动条。
  4. 每个 `ListGroup` 的高度应调整,以便在展开状态下显示所有包含的 Listview 项目,最多可达设计时或运行时确定的可选最大高度。如果包含的 ListViewItems 数量超过此最大值,则会显示 Individual ListGroup 垂直滚动条。
  5. 任何时候当特定 ListGroup 显示水平滚动条时,该 ListGroup 的客户端区域应进行调整,以便水平滚动条不会部分遮挡最后显示的 `ListViewItem`。
  6. 检测对特定 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`。这些图像包含在中。用以下代码替换构造函数代码存根:

构造函数

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 上),并随时分支源代码并提交拉取请求以进行改进。我曾多次说过,我需要所有能得到的帮助!

本文引用的内容

© . All rights reserved.