可绑定、可排序、自动调整大小的 ListView
一个支持数据绑定、排序和自动调整大小的 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
自行创建,我将在下一部分解释。在下图中,您可以看到一个绑定到DataTable
的bsaListView
示例,其类型为System.DateTime
(“出生日期”)、String
(“姓名、城市和邮编”)、int
(“员工”)和double
(“体重”)。每当单击列标题时,数据都会进行排序。
第二部分:自动调整内容大小
您可以通过将列的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
时,第一个屏幕可能如下所示。
您可以看到列的大小已调整为适合其最大项目(出生日期、姓名、城市和邮编),或者列标题的宽度(员工编号和体重)。如果您选择“更新 ds1”按钮,则会更新DataSet
并将城市“Huntsville/Birmingham AL”分配给第3行。列的宽度将像下图一样进行调整。
如果用户在按下“更新”按钮之前调整了“城市”列的大小,您将看到下图。
宽度不再调整,就像您预期的那样。