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

扩展 ListViewItem

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (4投票s)

2009年5月14日

CPOL

6分钟阅读

viewsIcon

53158

downloadIcon

950

如何扩展 ListViewItem 以容纳一行对象,并将其映射到 ListView 控件的列。

ListViewExtendedItem Demo App

引言

您好,这是我在这里的第一篇文章,所以请大家多包涵。另外,我的英语可能有点“不同”,如有任何错误,我在此表示歉意。

好的,首先是问题:我需要将一个对象的属性映射到一组 ListView 列,并将该对象本身存储在 ListViewItem 中。为什么?因为 OOP 最棒的地方之一就是您可以创建一个类,该类包含一组数据和操作这些数据的方法,并以非常优雅的方式使用它。因为我喜欢使用对象而不是任何其他存储数据的方法,所以我希望 ListViewItem 能够包含一个对象,并自动将其属性映射到 ListView 控件的一组列。

解决方案:ListViewExtendedItem 类。这个类继承自 ListViewItem,并增加了一个新属性、一个不同的构造函数和一个(如果算上重载就是两个)方法。

它是如何工作的?

嗯,因为我创建了自己的类来保存我感兴趣的数据,所以我完全可以控制如何构建它们。另外,我还可以完全控制要显示哪些列以及它们的顺序。因此,我可以使用自定义属性来标记数据类的属性,并指定相应的列名。这是一个简单的解决方案,但有一个很大的缺点:除了那些用自定义属性编写的类之外,不能使用其他类。

  1. 自定义属性 ListViewColumnAttribute
  2. 这是我们用来标记属性并指定它们应映射到的列名的属性。正如您下面所看到的,它是一个非常基础的属性。它只有一个构造函数,接受一个字符串参数(列名),并重写了 ToString() 方法,以便我们以后在检查列名时会更方便一些。

    public class ListViewColumnAttribute : Attribute
    {
       private string _columnName;
    
       public ListViewColumnAttribute(string columnName)
       {
          _columnName = columnName;
       }
    
       public override string ToString()
       {
          return _columnName;
       }
    }

    到目前为止一切顺利,没什么特别的。当我们像这样编写类时,将使用此属性:

    [ListViewColumn("some column")]
  3. ListViewExtendedItem
  4. 这个类完成了所有工作。它继承自 ListViewItem 类,并增加了以下内容:

    1. 一个非常不同的构造函数,ListViewExtendedItem(ListView parentListView, object data),其中 parentListView 是将包含该项的 ListView 控件。在其列必须在创建任何扩展项之前设置好,而 data 是您想添加以显示的那个对象。
    2. 一个名为 Data 的属性,类型为 object,它保存了对象本身;如果您的对象能够执行多项任务(例如,我的对象能够自行打印;我只需要将一个扩展项添加到 ListView,以及一个按钮,该按钮为 ListView 中选定的每一项调用对象的 Print() 方法)。
    3. Update 方法带有两个重载,Update(object data)Update(object data, ListView listView),它们负责将对象属性映射到列。第一个重载只接受一个参数,即一个新(已更新)的对象,需要显示。它依赖于基类 ListViewItem 中的 ListView 属性来获取其列信息。如果未设置(项尚未添加到 ListView),它将抛出异常。第二个重载要求显式提供 ListView

好的,实际执行所有工作的代码包含在 Update 方法的第二个重载的主体中。构造函数和第一个重载都调用这个方法来完成任务。代码如下:

public void Update(object data, ListView listView)
{
    //Clear all the subitems.
    this.SubItems.Clear();
    //Get the type of the data object.
    Type typeOfData = data.GetType();
    //Define this to keep track of whats happening
    bool completed_column = false;
    foreach (ColumnHeader column in listView.Columns)
    {        
        completed_column = false;
        //Get all the properties of the object's type.
        foreach (PropertyInfo pInfo in typeOfData.GetProperties())
        {
            //Get all the custom attributes for the 
            //current property, the use of true here tells
            //the runtime that you wish to check the inherited 
            //class also, this may be usefull if 
            //your objects inherit from a base one.
            foreach (object pAttrib in pInfo.GetCustomAttributes(true))
            {
                //Check to see if the type of the attribute 
                //is that of ListViewColumnAttribute.
                if (pAttrib.GetType() == typeof(ListViewColumnAttribute))
                {
                    //Check to see if the column names coincide.
                    if (pAttrib.ToString() == column.Name)
                    {
                        //Check to see if it has to update it's own 
                        //Text property, or if it has to add subitems.
                        if (column.DisplayIndex == 0)
                        {
                            this.Text = pInfo.GetValue(data, null).ToString();
                            completed_column = true;
                            break;
                        }
                        else
                        {
                            this.SubItems.Add(pInfo.GetValue(data, null).ToString());
                            completed_column = true;
                            break;
                        }
                    }
                }                        
            }
            if (completed_column)
            {
                break;
            }
        }
    }
    //Keep the object here so that it can be easyley retrieved 
    //when the user performs some action on the ListView.
    _data = data;
}

首先,我们清除所有的 SubItem,这样就不会有除必需项之外的其他列。然后,我们获取数据对象的 Type,并定义一个 bool 变量来稍微优化该方法。然后,对于提供的 ListView 控件的每一列,我们必须检查对象的属性,看是否有属性被标记了 ListViewColumnAttribute 类型的自定义属性,并且该属性的列名是否与我们当前正在搜索的列名匹配。代码实际上不那么好看,三个嵌套的 foreach 和一些 if 语句并不赏心悦目。但是,它确实完成了任务,而且目前,我无法想出其他方法来检查类型的成员。一旦找到一个已标记了匹配属性的成员,并且列名与属性中存储的名称匹配,我们就继续检查当前填充的列是否是第一列(DisplayIndex=0),因为该项本身的文本属性会显示出来,而不是由 SubItem 持有的文本。

请注意,有一个 if 语句检查 completed_column 的值。这是为了确保在找到匹配项后,代码不会继续搜索,而是转到下一列。完成所有列后,内部的 _data 对象将被赋值为传递给函数的那个。

这基本上就是全部内容了。ListViewExtendedItem 可以添加到 ListView 控件的 Items 集合中,它将在正确的列中显示已标记的属性。

局限性/烦人的地方

第一个明显的烦人之处在于,当您检索一个扩展项时,您必须将其类型强制转换为 ListViewExtenededItem 才能使用它,并且您还需要解封 Data 属性才能访问其成员。这些都可以通过类的泛型实现来解决;但是,目前,泛型的一些方面对我来说有点难以理解。

请注意,我没有测试过在使用 ListView 的虚拟项时是否有效,但它应该有效,我没看到有什么会破坏它……此外,如果数据对象的属性具有一些晦涩的类型,并且没有重写 ToString() 方法,那么结果不会太有用。这可以通过再次使用属性来解决,以存储您想从该晦涩对象中显示的某个字段的名称(也许可以添加一个接口,提供一个方法来检索属性名,以便在运行时更改它)。

还有最后一个烦人的地方,Visual Studio 不会更新 ListView 中使用的 ColumnHeaderName 属性,您必须在代码中进行设置。

源代码和演示应用程序

在下载(上面链接)中,您会找到一个使用了这个类的演示应用程序。用法非常简单。使用“新建”按钮创建一个新的对象实例,通过属性网格修改其属性,然后单击“添加”将其保存到列表视图中。如果您单击列表视图中的某个项,您将在属性网格中看到该数据对象的属性。修改它们后,单击“更新”以保存更改并将其显示在列表视图控件中。

ListViewColumnAttribute 可以在同名文件中找到,ListViewExtendedItem 类也是如此。

感谢阅读本文。希望对您有所帮助。如果您对扩展项类有任何喜欢或不喜欢的地方,请告诉我。

© . All rights reserved.