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

可绑定、可排序、自动调整大小的 ListView

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (8投票s)

2005年5月17日

CPOL

3分钟阅读

viewsIcon

104916

downloadIcon

1347

一个支持数据绑定、排序和自动调整大小的 ListView,并在重新绑定数据后重新选择之前选择的项目。

引言

在我最近的一个项目中,我需要一个ListView来显示数据库查询的结果,并具有排序、自动调整内容大小和重新选择项目等功能。当我完成自动调整大小部分时,我觉得这应该分享出来,尤其是自动调整大小部分,因为我在网上找不到任何符合我需求的示例。

第一部分:ListView 的数据绑定、创建列和排序器

我的“bsaListView”的数据绑定部分主要由两部分组成:设置DataTable属性和调用DataBind

this.listView1.DataTable = CreateDataTable1( );
this.listView1.DataBind( );

调用DataTable的setter时,ListView将枚举DataTable的数据列,为其创建列,并根据数据类型为其创建排序器。

private System.Data.DataTable _dataTable;
[ Bindable      ( true), 
Category        ( "Whoua.Src"), 
Description     ( "DataTable")]
public System.Data.DataTable DataTable
{
     get{ return( _dataTable); }
     set
     {
          if( _dataTable == value)
          {
               return;
          }
          _dataTable = value;
          this.CreateListViewColumns( _dataTable);
     }
}

CreateListViewColumns创建实际的列,代码如下所示。

ColumnHeader[] columnHeaders = new ColumnHeader[ dataTable.Columns.Count];
System.Windows.Forms.ColumnHeader columnHeader = null;

this.Columns.Clear( );

// --- Enumerate DataColumns and create ColumnHeaders for them (20050418 SDE)

int i = 0;
foreach( System.Data.DataColumn dataColumn in dataTable.Columns)
{
     columnHeader = getSortableListviewColumnHeader( dataColumn);
     columnHeader.Text = dataColumn.ColumnName;
     columnHeader.TextAlign = HorizontalAlignment.Left;
     columnHeaders[ i] = columnHeader;
     i++;
}

// --- Tell listview to create the columns (20050309 SDE)
this.Columns.AddRange( columnHeaders);

这里没有什么花哨的操作,只是调用了getSortableListviewColumnHeader,它返回一个SortableListviewColumnHeader列标题。此列标题用于排序和自动调整内容大小。

我阅读了Eddie Velasquez撰写的关于为ListView实现排序的优秀文章。在这篇文章中,Eddy 描述了一种根据列使用的数 据类型设置排序管理器的方法。由于我使用的是数据绑定的ListView,因此ListView本身可以处理此问题,我将其完全移入了ListView(并且还删除了我不需要的其他一些功能)。

设置排序的第一步由getSortableListviewColumnHeader执行,它根据列的数据类型返回一个排序器。

SortableListviewColumnHeader sortableListviewColumnHeader = 
                     new SortableListviewColumnHeader( );

System.Type type = dataColumn.DataType;

if( type == typeof(System.String))
{
     sortableListviewColumnHeader.ListviewSorter = 
                    new ListViewTextCaseInsensitiveSorter( );
     return( sortableListviewColumnHeader);
}

else if( type == typeof( System.Int32))
{
     sortableListviewColumnHeader.ListviewSorter = 
                             new ListViewInt32Sorter( );
     return( sortableListviewColumnHeader);
}

//etc..

每当单击列时,都会调用在Initialize中创建的“ListView_ColumnClick”事件处理程序,该处理程序基本上会调用Eddy描述的相同排序逻辑。

this.ColumnClick += new ColumnClickEventHandler( ListView_ColumnClick);

private void ListView_ColumnClick( object sender, ColumnClickEventArgs e)
{
     // --- Perform sorting

     System.Windows.Forms.ListView listview = sender as ListView;

     SortableListviewColumnHeader sorter = 
            listview.Columns[ e.Column] as SortableListviewColumnHeader;

     if( listview.Sorting == SortOrder.None)
     {
          listview.Sorting = SortOrder.Ascending;
     }
     else if( listview.Sorting == SortOrder.Ascending)
     {
          listview.Sorting = SortOrder.Descending;
     }
     else
     {
          listview.Sorting = SortOrder.Ascending;
     }

     sorter.Column = e.Column;
     this.ListViewItemSorter = sorter;
}

数据通过调用DataBind自行创建,我将在下一部分解释。在下图中,您可以看到一个绑定到DataTablebsaListView示例,其类型为System.DateTime(“出生日期”)、String(“姓名、城市和邮编”)、int(“员工”)和double(“体重”)。每当单击列标题时,数据都会进行排序。

Sorting

第二部分:自动调整内容大小

您可以通过将列的width属性设置为-2(适合最大项目)或-1(适合列标题)来告诉ListView调整其内容或标题的大小。

foreach( System.Windows.Forms.ColumnHeader c in this.listView1.Columns)
{
     c.Width = -2; 
}

您需要在创建列表项后执行此操作。

使用选项-2似乎是一个不错的选择,但事实并非如此。考虑一下用户更改列的宽度,因为他不需要查看特定列的情况。每当更新ListView中的数据时,标准ListView都会将列宽恢复为适合最大项目,这非常烦人。此外,使用选项-2应该在列中最大的项目小于列标题时适合列标题,但这并不是常规ListView的行为。

在我的解决方案中,我使用Pierre Arnaud的文章中的代码来计算每个项目 的宽度。Data2Listview还会检查最大项目是否小于列标题,如果是,则使用列标题宽度作为列的宽度。此宽度存储在一个专门的列标题中,即SortableListviewColumnHeader

private void Data2Listview( System.Data.DataTable dt)
{
     System.Diagnostics.Debug.Assert( dt.Columns.Count == this.Columns.Count, 
                      string.Format( "Columncount != listview columncount"));

     System.Windows.Forms.ListViewItem listViewItem = null;

     // --- Add a ListView-items for each row
     //     and see if it has the largest string-length
     //     of items so we can fit the columnheader (20050228 SDE)

     Graphics g = this.CreateGraphics( );

     string item = string.Empty;

     foreach( System.Data.DataRow dr in dt.Rows)
     {
          int i = 0; // --- Enumerating the columns (20050515 SDE)

          ArrayList items = new ArrayList( );
          SortableListviewColumnHeader slc = null;
          float itemWidth = 0.0F;
          float     columnWidth = 0.0F;
          float     width = 0.0F;
          int       itemLength     = 0;

          foreach( System.Data.DataColumn dc in dt.Columns)
          {
               item = dr[ dc].ToString().Trim();
               items.Add( item);
               slc = (SortableListviewColumnHeader) this.Columns[ i];

               // --- If the widht of a columnheader
               //     is larger then the largest item in the
               //     list, we use the columnheader (20050515 SDE)

               columnWidth = MeasureDisplayStringWidth( g, 
                                         slc.Text, this.Font);

               // --- Length item (20050515 SDE)

               itemLength     = item.Length;
               if( item.Trim() != string.Empty)
               {
                    itemWidth = MeasureDisplayStringWidth( g, 
                                             item, this.Font);

                    if( itemWidth > columnWidth)
                    {
                         width = itemWidth;
                    }
                    else
                    {
                         width = columnWidth;
                    }

                    if( width > slc.LargestSize)
                    {
                         slc.LargestSize = width;
                    }
               }

               // --- Use columnwidth when values are null (20050515 SDE)

               else
               {
                    width = columnWidth;

                    if( width > slc.LargestSize)
                    {
                         slc.LargestSize = width;
                    }
               }
               i++;
          }

          string[] listItems = (string []) items.ToArray( typeof( string));
          listViewItem = new ListViewItem( listItems);
          this.Items.Add( listViewItem);
     }
}

调用Data2Listview后,DataBind调用AdjustColuzmnWidths,该函数仅当用户未更改列宽时才负责设置列宽。

private void AdjustColumnWidths( )
{
     string s = string.Empty;

     foreach( SortableListviewColumnHeader slc in this.Columns)
     {
          // --- If the PreviousWidth equals
          //     the Width, the user hasn't modified a columns
          //     width and we need to autofit
          //     it to the largest item. Otherwise, 
          //     PreviousWidth is not equal
          //     to the width, the user has modified the width
          //     of a column, and we need to leave
          //     it the way it is (20050515 SDE)

          if( slc.PreviousWidth == slc.Width || slc.PreviousWidth == 0)
          {
               slc.Width = System.Convert.ToInt32( slc.LargestSize);
               slc.PreviousWidth = slc.Width;
          }
     }
}

这就是绕过标准ListView列标题缺点所需的一切。

使用此ListView时,第一个屏幕可能如下所示。

Fit1

您可以看到列的大小已调整为适合其最大项目(出生日期、姓名、城市和邮编),或者列标题的宽度(员工编号和体重)。如果您选择“更新 ds1”按钮,则会更新DataSet并将城市“Huntsville/Birmingham AL”分配给第3行。列的宽度将像下图一样进行调整。

Fit2

如果用户在按下“更新”按钮之前调整了“城市”列的大小,您将看到下图。

Fit3

宽度不再调整,就像您预期的那样。

© . All rights reserved.