ListView 布局管理器






4.92/5 (66投票s)
WPF: 自定义 ListView/GridView 列布局

引言
使用 ListViewLayoutManager
可以控制 ListView
/GridView
控件的列布局行为。
- 固定列:具有固定列宽的列。
- 范围列:具有最小和/或最大列宽的列。
- 比例列:具有比例列宽的列。
范围列允许限制列宽,也可以让该列填充剩余的可见区域。
正如 HTML 表格或 Grid
控件所熟知的,比例列按百分比确定列宽。以下因素决定了比例列的宽度:
- 垂直
ListView
滚动条的可见性 ListView
控件宽度的变化- 非比例列宽度的变化
该实现支持通过 XAML 或代码隐藏进行控制。使用 XAML 样式允许将 ListViewLayoutManager
“附加”到现有的 ListView
控件。
ConverterGridColumn
类通过使用 IValueConverter
接口提供对象特定绑定的功能。使用 ImageGridViewColumn
类允许使用 DataTemplate
将列表示为图像/图标。
在文章 用户设置应用 中,我描述了如何持久化 ListView
列的顺序和大小。
XAML 中的 ListView/GridView 布局
固定列
以下示例展示了如何使用 XAML 控制具有固定宽度的列。
<ListView
Name="MyListView"
ctrl:ListViewLayoutManager.Enabled="true">
<ListView.View>
<GridView>
<GridViewColumn
DisplayMemberBinding="{Binding Path=Name}"
ctrl:FixedColumn.Width="100"
Header="Name" />
<GridViewColumn
DisplayMemberBinding="{Binding Path=City}"
ctrl:FixedColumn.Width="300"
Header="City" />
</GridView>
</ListView.View>
</ListView>
设置 Enabled
属性将 ListViewLayoutManager
绑定到 ListView
控件。FixedColumn.Width
属性决定了列宽,并阻止通过鼠标进行调整。
比例列
以下示例展示了如何使用 XAML 控制具有比例宽度的列。
<ListView
Name="MyListView"
ctrl:ListViewLayoutManager.Enabled="true">
<ListView.View>
<GridView>
<GridViewColumn
DisplayMemberBinding="{Binding Path=Name}"
ctrl:ProportionalColumn.Width="1"
Header="Name" />
<GridViewColumn
DisplayMemberBinding="{Binding Path=City}"
ctrl:ProportionalColumn.Width="3"
Header="City" />
</GridView>
</ListView.View>
</ListView>
与 Grid
控件的 RowDefinition.Width
匹配,ProportionalColumn.Width
的值代表百分比。以上场景将 Name
列设置为总宽度的 25%,将 City
列设置为 75%。与固定列类似,禁用了鼠标调整大小。
范围列
以下示例展示了如何使用 XAML 控制具有最小/最大宽度的范围列。
<ListView
Name="MyListView"
ctrl:ListViewLayoutManager.Enabled="true">
<ListView.View>
<GridView>
<GridViewColumn
DisplayMemberBinding="{Binding Path=Name}"
ctrl:RangeColumn.MinWidth="100"
Width="150"
Header="Name" />
<GridViewColumn
DisplayMemberBinding="{Binding Path=City}"
ctrl:RangeColumn.MaxWidth="200"
Width="150"
Header="City" />
<GridViewColumn
DisplayMemberBinding="{Binding Path=Country}"
Width="100"
ctrl:RangeColumn.MinWidth="50"
ctrl:RangeColumn.MaxWidth="150"
Header="Country" />
<GridViewColumn
DisplayMemberBinding="{Binding Path=State}"
Width="100"
ctrl:RangeColumn.MinWidth="100"
ctrl:RangeColumn.IsFillColumn="true"
Header="Country" />
</GridView>
</ListView.View>
</ListView>
第一个 IsFillColumn
属性设置为 true
的范围列将自动调整大小以适应剩余空间。如果 ListView
包含比例列,则该列不会被填充。
调整大小时,将鼠标拖出配置范围,鼠标光标会改变其表示形式以指示这一点。
组合使用
在实际应用中,通常需要组合使用这些列类型。它们的顺序可以根据需要进行调整。
<ListView
Name="MyListView"
ctrl:ListViewLayoutManager.Enabled="true">
<ListView.View>
<GridView>
<GridViewColumn
DisplayMemberBinding="{Binding Path=State}"
ctrl:FixedColumn.Width="25"
Header="Name" />
<GridViewColumn
DisplayMemberBinding="{Binding Path=Name}"
Width="150"
ctrl:RangeColumn.MinWidth="100"
ctrl:RangeColumn.MaxWidth="200"
Header="City" />
<GridViewColumn
DisplayMemberBinding="{Binding Path=City}"
ctrl:ProportionalColumn.Width="1"
Header="Zip" />
<GridViewColumn
DisplayMemberBinding="{Binding Path=Country}"
ctrl:ProportionalColumn.Width="2"
Header="Country" />
</GridView>
</ListView.View>
</ListView>
使用代码隐藏进行 ListView/GridView 布局
也可以在源代码中设置列布局。
ListView listView = new ListView();
new ListViewLayoutManager( listView ); // attach the layout manager
GridView gridView = new GridView();
gridView.Columns.Add( FixedColumn.ApplyWidth( new MyGridViewColumn( "State" ), 25 ) );
gridView.Columns.Add( RangeColumn.ApplyWidth( new MyGridViewColumn( "Name" ), 100,
150, 200 ) ); // 100...200
gridView.Columns.Add( ProportionalColumn.ApplyWidth( new MyGridViewColumn( "City" ),
1 ) ); // 33%
gridView.Columns.Add( ProportionalColumn.ApplyWidth( new MyGridViewColumn(
"Country" ), 2 ) ); // 66%
listView.View = gridView;
具有自定义表示的列
ConverterGridColumn
类作为基类,用于将表格列绑定到单个对象。
// ------------------------------------------------------------------------
public class Customer
{
// ----------------------------------------------------------------------
public Customer()
{
} // Customer
// ----------------------------------------------------------------------
public string FirstName
{
get { return this.firstName; }
set { this.firstName = value; }
} // FirstName
// ----------------------------------------------------------------------
public string LastName
{
get { return this.lastName; }
set { this.lastName = value; }
} // LastName
// ----------------------------------------------------------------------
// members
private string firstName;
private string lastName;
} // class Customer
// ------------------------------------------------------------------------
public class CustomerFullNameColumn : ConverterGridViewColumn
{
// ----------------------------------------------------------------------
public CustomerGridViewColumn() :
base( typeof( Customer ) )
{
} // CustomerGridViewColumn
// ----------------------------------------------------------------------
protected override object ConvertValue( object value )
{
Customer customer = value as Customer;
return string.Concat( customer.FirstName, " ", customer.LastName );
} // ConvertValue
} // class CustomerFullNameColumn
表示为图像的列
ImageGridColumn
类作为基类,用于将表格列绑定到图像/图标。
// ------------------------------------------------------------------------
public class Customer
{
// ----------------------------------------------------------------------
public Customer()
{
} // Customer
// ----------------------------------------------------------------------
public bool IsActive
{
get { return this.isActive; }
set { this.isActive = value; }
} // IsActive
// ----------------------------------------------------------------------
// members
private bool isActive;
} // class Customer
// ------------------------------------------------------------------------
public class CustomerActiveColumn : ImageGridViewColumn
{
// ----------------------------------------------------------------------
public CustomerActiveColumn()
{
} // CustomerActiveColumn
// ----------------------------------------------------------------------
protected override ImageSource GetImageSource( object value )
{
Customer customer = value as Customer;
if ( customer != null )
{
return new BitmapImage( new Uri( customer.IsActive ? "Active.png" :
"Inactive.png" ) );
}
return null;
} // GetImageSource
} // class CustomerActiveColumn
关注点
ListView
控件布局的核心是 ListViewLayoutManager
,它负责以下事项:
- 防止调整
Fixed
和Proportional
类型列的大小。 - 强制执行
Range
类型列的允许宽度范围。 - 在
ListView
控件大小变化时更新列布局。 - 在各个列宽度变化时更新列布局。
为了正确接收所需信息,有必要分析 ListView
控件的可视化树。Thumb
对象提供列宽度变化的事件。为了确保正确显示鼠标光标,会处理 PreviewMouseMove
和 PreviewMouseLeftButtonDown
事件。
ScrollViewer
类的 ScrollChanged
事件会触发由于控件大小变化而进行的更新。只有 Viewport
控件大小的变化与调整大小相关(ScrollChangedEventArgs.ViewportWidthChange
)。
跟踪 GridViewColumn
类的 Width
属性,使用 DependencyPropertyDescriptor
可以通知列宽度的变化。
为了支持集成到现有系统中,所需的列数据保存在附加属性中。使用 DependencyProperty.ReadLocalValue()
方法可以检测自己的属性是否存在于对象中。
ConverterGridViewColumn
类使用简单的 Binding
,同时代表转换器(接口 IValueConverter
)。
ImageGridViewColumn
类在 DataTemplate
中使用 FrameworkElementFactory
来动态嵌入图像。默认情况下,ListView
/GridView
控件中的图像会自动拉伸(属性 Image.Stretch
)。因为 DataTemplate
中的图像是动态创建的,所以必须使用 Binding
为模板元素的属性值赋值。
// ----------------------------------------------------------------------
protected ImageGridViewColumn( Stretch imageStretch )
{
FrameworkElementFactory imageElement = new FrameworkElementFactory( typeof( Image ) );
// image stretching
Binding imageStretchBinding = new Binding();
imageStretchBinding.Source = imageStretch;
imageElement.SetBinding( Image.StretchProperty, imageStretchBinding );
DataTemplate template = new DataTemplate();
template.VisualTree = imageElement;
CellTemplate = template;
} // ImageGridViewColumn
历史
- 2012 年 9 月 28 日
- 为 Visual Studio 2010 添加了项目和解决方案。
- 重构了源代码
ListViewLayoutManager
:使用值范围检查滚动查看器是否已更改。ListViewLayoutManager
:新的公共方法Refresh
。ListViewLayoutManager
:隐藏固定列的缩略图 - 感谢 Name taken。- 2009 年 8 月 3 日
RangeColumn
现在支持自动宽度列(Width="Auto"
)- 感谢 inv_inv。- 2008 年 11 月 27 日
- 通过注销
ListView
事件修复了内存泄漏 - 感谢 Dave 和 John。 - 2008 年 11 月 3 日
- 修复了在某些罕见情况下列调整大小的无限递归问题 - 感谢 ascalonx。
- 修复了嵌套
ListView
时列调整大小的问题 - 感谢 mburi。 - 2008 年 9 月 19 日
- 填充列现在可以位于任何位置 - 将使用第一个出现的列。
- 2008 年 6 月 12 日
- 添加了
RangeColumn.IsFillColumn
以将最后一列填充到可用可见区域。 - 2008 年 6 月 3 日
- 在自动调整大小(鼠标双击)时考虑范围列的限制 - 感谢 paul。
- 2008 年 5 月 14 日
- 修复了在没有比例列时水平滚动的问题 - 感谢 vernarim。
- 2008 年 5 月 10 日
- 添加了文章 用户设置应用 的链接。
- 2008 年 4 月 23 日
- 添加了屏幕截图说明。
- 次要 bug 修复
- 2008 年 4 月 6 日
- 首次发布