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

ListView 布局管理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (66投票s)

2008 年 4 月 8 日

CPOL

4分钟阅读

viewsIcon

627003

downloadIcon

9140

WPF: 自定义 ListView/GridView 列布局

ListView_layout_manager/ListViewLayoutManager.gif

引言

使用 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,它负责以下事项:

  • 防止调整 FixedProportional 类型列的大小。
  • 强制执行 Range 类型列的允许宽度范围。
  • ListView 控件大小变化时更新列布局。
  • 在各个列宽度变化时更新列布局。

为了正确接收所需信息,有必要分析 ListView 控件的可视化树。Thumb 对象提供列宽度变化的事件。为了确保正确显示鼠标光标,会处理 PreviewMouseMovePreviewMouseLeftButtonDown 事件。

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 日
    • 首次发布
© . All rights reserved.