ListView 继承自简单的没有特色的ListBox。增加了对基于列显示的支持,并增加了快速切换视图或显示模式的能力,而不需要重新绑定数据以及重新构建列表。
ListView类是一个特殊的列表类,它是专门针对显示相同数据的不同视图而设计的。如果要构建显示每个数据项几部分信息的多列视图,ListView 控件特别有用。ListView类继承自ListBox类,并使用 View 属性进行扩展。
GridView
GridView类继承自ViewBase类,表示具有多列的列表视图。通过GridVeiw.Columns 集合添加GridViewColumn 对象可定义这些列。
<ListView Grid.Row="0" Grid.Column="0" Margin="5" ItemsSource="{Binding Path=Orders}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=Price}" />
<GridViewColumn Header="Volume" DisplayMemberBinding="{Binding Path=Volume}" />
<GridViewColumn Header="OrderDate" DisplayMemberBinding="{Binding Path=OrderDate, StringFormat={}{0:D}}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
单元格模板
可以视图单元格模板 CellTemplate 属性来重写列,它与DataTemplate很相似,但是只能应用于一列数据。单元格模板并不局限于只能使用TextBlock属性,也可以使用其他元素,比如使用 转化器帮助从文件系统加载相应的图像文件。
<ListView Grid.Row="0" Grid.Column="1" Margin="5" ItemsSource="{Binding Path=Orders}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=Price}" />
<GridViewColumn Header="Volume" DisplayMemberBinding="{Binding Path=Volume}" />
<GridViewColumn Header="OrderDate" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=OrderDate, StringFormat={}{0:D}}" TextWrapping="Wrap"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Image" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=Image, Converter={StaticResource ImagePathConverter}}"></Image>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
创建自定义视图
如果GridView视图不能满足需要,可以创建自己的视图以扩展ListView 控件的功能。不过,这并不容易实现。
视图通过重写两个受保护的属性进行工作:DefaultStyleKey 和 ItemContainerDefaultStyleKey。这两个属性都返回名为 ResourceKey 的特殊对象,该对象指向在XAML中定义的样式。DefaultStyleKey 将被用于配置整个ListView控件的样式,而ItemContainerDefaultStyleKey 则将被用于配置ListView控件中的每个ListViewItem元素的样式。尽管这些样式可修改任意属性,但它们通常通过替换用于ListView 控件的ControlTemplate 以及用于每个ListViewItem元素的DataTemplate 进行工作。
视图类
这里除了ViewBase 所需的DefaultStyleKey 与 ItemContainerDefaultStyleKey 外,还另外定义了三个属性:ItemTemplate、SelectedBackground、SelectedBorderBrush,其中ItemTemplate用于使用时提供正确的数据模板,SelectedBackground与SelectedBorderBrush用于为视图传递额外信息,在视图样式中可以绑定到这两个属性来使用。
public class TileView : ViewBase
{
private DataTemplate itemTemplate;
public DataTemplate ItemTemplate
{
get { return itemTemplate; }
set { itemTemplate = value; }
}
private Brush selectedBackground = Brushes.Transparent;
public Brush SelectedBackground
{
get { return selectedBackground; }
set { selectedBackground = value; }
}
private Brush selectedBorderBrush = Brushes.Black;
public Brush SelectedBorderBrush
{
get { return selectedBorderBrush; }
set { selectedBorderBrush = value; }
}
protected override object DefaultStyleKey
{
get { return new ComponentResourceKey(GetType(), "TileView"); }
}
protected override object ItemContainerDefaultStyleKey
{
get { return new ComponentResourceKey(GetType(), "TileViewItem"); }
}
}
视图样式
视图样式需要放到资源文件,为了自动检索到,我们这里将其命名为Generic.xaml 并放到Themes文件夹下。视图样式中主要为 ListView 的 ItemsPanel 属性应用了 WrapPanel,将View.ItemTemplate 绑定到ListViewItem的 ContentTemplate属性上,同时应用了再视图里面定义的 SelectedBackground与SelectedBorderBrush 这两个属性。
Themes/Generic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestListView">
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:TileView}, ResourceId=TileView}" TargetType="{x:Type ListView}" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="BorderBrush" Value="Black"></Setter>
<Setter Property="BorderThickness" Value="0.5"></Setter>
<Setter Property="Grid.IsSharedSizeScope" Value="True"></Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:TileView},ResourceId=TileViewItem}" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Padding" Value="3"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="ContentTemplate" Value="{Binding Path=View.ItemTemplate, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="Border" BorderThickness="1" CornerRadius="3" >
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="{Binding Path=View.SelectedBorderBrush, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"/>
<Setter TargetName="Border" Property="Background" Value="{Binding Path=View.SelectedBackground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
创建视图
我们在窗口资源里面来创建视图,主要需要为视图类提供数据模板,如果有需要的话,也可以为在视图类里面定义的SelectedBackground与SelectedBorderBrush 这两个属性提供数据。
<Window.Resources>
<local:ImagePathConverter x:Key="ImagePathConverter"></local:ImagePathConverter>
<GridView x:Key="NormalGridView">
<GridView.Columns>
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=Price}" />
<GridViewColumn Header="Volume" DisplayMemberBinding="{Binding Path=Volume}" />
<GridViewColumn Header="OrderDate" DisplayMemberBinding="{Binding Path=OrderDate, StringFormat={}{0:D}}" />
</GridView.Columns>
</GridView>
<local:TileView x:Key="ImageView" SelectedBackground="DarkRed">
<local:TileView.ItemTemplate>
<DataTemplate>
<StackPanel Width="150" VerticalAlignment="Top">
<Image Source="{Binding Path=Image, Converter={StaticResource ImagePathConverter}}"></Image>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding Path=Price}" ></TextBlock>
</StackPanel>
</DataTemplate>
</local:TileView.ItemTemplate>
</local:TileView>
<local:TileView x:Key="ImageDetailView" SelectedBackground="LightSteelBlue">
<local:TileView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto" SharedSizeGroup="Col2"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Margin="5" Width="100" Source="{Binding Path=Image, Converter={StaticResource ImagePathConverter}}"></Image>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock FontWeight="Bold" Text="{Binding Path=Price}"></TextBlock>
<TextBlock Text="{Binding Path=Volume}"></TextBlock>
<TextBlock Text="{Binding Path=OrderDate, StringFormat={}{0:D}}"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</local:TileView.ItemTemplate>
</local:TileView>
</Window.Resources>
使用视图
使用视图比较简单,视图已经在窗口资源里面定义了,只需要绑定到ListView的View属性上即可。因为这里创建了多个视图,我们可以根据用户选择来使用不同的视图。
<ListView Grid.Row="1" Grid.ColumnSpan="2" Margin="5" ItemsSource="{Binding Path=Orders}" View="{StaticResource ImageView}" Name="customListView" />
<Grid Grid.Row="2" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" VerticalAlignment="Center">Choose your view:</TextBlock>
<ComboBox Grid.Column="1" Margin="5" Width="150" HorizontalAlignment="Left" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem>NormalGridView</ComboBoxItem>
<ComboBoxItem>ImageView</ComboBoxItem>
<ComboBoxItem>ImageDetailView</ComboBoxItem>
</ComboBox>
</Grid>
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem selectedItem = (ComboBoxItem)((ComboBox)sender).SelectedItem;
customListView.View = (ViewBase)this.FindResource(selectedItem.Content);
}
下面给出完整代码,Themes/Generic.xmal 在前面已经完整给出了,这里不再重复
MainWindow.xaml
<Window x:Class="TestListView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestListView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:ImagePathConverter x:Key="ImagePathConverter"></local:ImagePathConverter>
<GridView x:Key="GridView">
<GridView.Columns>
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=Price}" />
<GridViewColumn Header="Volume" DisplayMemberBinding="{Binding Path=Volume}" />
<GridViewColumn Header="OrderDate" DisplayMemberBinding="{Binding Path=OrderDate, StringFormat={}{0:D}}" />
<GridViewColumn Header="Image" DisplayMemberBinding="{Binding Path=Image, Converter={StaticResource ImagePathConverter}}" />
</GridView.Columns>
</GridView>
<local:TileView x:Key="ImageView" SelectedBackground="DarkRed">
<local:TileView.ItemTemplate>
<DataTemplate>
<StackPanel Width="150" VerticalAlignment="Top">
<Image Source="{Binding Path=Image, Converter={StaticResource ImagePathConverter}}"></Image>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding Path=Price}" ></TextBlock>
</StackPanel>
</DataTemplate>
</local:TileView.ItemTemplate>
</local:TileView>
<local:TileView x:Key="ImageDetailView" SelectedBackground="LightSteelBlue">
<local:TileView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto" SharedSizeGroup="Col2"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Margin="5" Width="100" Source="{Binding Path=Image, Converter={StaticResource ImagePathConverter}}"></Image>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock FontWeight="Bold" Text="{Binding Path=Price}"></TextBlock>
<TextBlock Text="{Binding Path=Volume}"></TextBlock>
<TextBlock Text="{Binding Path=OrderDate, StringFormat={}{0:D}}"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</local:TileView.ItemTemplate>
</local:TileView>
</Window.Resources>
<Grid Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView Grid.Row="0" Grid.Column="0" Margin="5" ItemsSource="{Binding Path=Orders}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=Price}" />
<GridViewColumn Header="Volume" DisplayMemberBinding="{Binding Path=Volume}" />
<GridViewColumn Header="OrderDate" DisplayMemberBinding="{Binding Path=OrderDate, StringFormat={}{0:D}}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<ListView Grid.Row="0" Grid.Column="1" Margin="5" ItemsSource="{Binding Path=Orders}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=Price}" />
<GridViewColumn Header="Volume" DisplayMemberBinding="{Binding Path=Volume}" />
<GridViewColumn Header="OrderDate" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=OrderDate, StringFormat={}{0:D}}" TextWrapping="Wrap"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Image" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=Image, Converter={StaticResource ImagePathConverter}}"></Image>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<ListView Grid.Row="1" Grid.ColumnSpan="2" Margin="5" ItemsSource="{Binding Path=Orders}" View="{StaticResource GridView}" Name="customListView" />
<Grid Grid.Row="2" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" VerticalAlignment="Center">Choose your view:</TextBlock>
<ComboBox Grid.Column="1" Margin="5" Width="150" HorizontalAlignment="Left" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem>GridView</ComboBoxItem>
<ComboBoxItem>ImageView</ComboBoxItem>
<ComboBoxItem>ImageDetailView</ComboBoxItem>
</ComboBox>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace TestListView;
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T member, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(member, value))
{
return false;
}
member = value;
OnPropertyChanged(propertyName);
return true;
}
}
public class Order : ViewModelBase
{
public decimal price = 0;
public decimal Price { get => price; set => SetProperty(ref price, value); }
public int volume = 0;
public int Volume { get => volume; set => SetProperty(ref volume, value); }
public DateTime orderDate = DateTime.Now;
public DateTime OrderDate { get => orderDate; set => SetProperty(ref orderDate, value); }
public string image = string.Empty;
public string Image { get => image; set => SetProperty(ref image, value); }
}
public class ImagePathConverter : IValueConverter
{
private string imageDirectory = Directory.GetCurrentDirectory();
public string ImageDirectory
{
get { return imageDirectory; }
set { imageDirectory = value; }
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string imagePath = Path.Combine(ImageDirectory, (string)value);
return new BitmapImage(new Uri(imagePath));
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("The method or operation is not implemented.");
}
}
public class TileView : ViewBase
{
private DataTemplate itemTemplate;
public DataTemplate ItemTemplate
{
get { return itemTemplate; }
set { itemTemplate = value; }
}
private Brush selectedBackground = Brushes.Transparent;
public Brush SelectedBackground
{
get { return selectedBackground; }
set { selectedBackground = value; }
}
private Brush selectedBorderBrush = Brushes.Black;
public Brush SelectedBorderBrush
{
get { return selectedBorderBrush; }
set { selectedBorderBrush = value; }
}
protected override object DefaultStyleKey
{
get { return new ComponentResourceKey(GetType(), "TileView"); }
}
protected override object ItemContainerDefaultStyleKey
{
get { return new ComponentResourceKey(GetType(), "TileViewItem"); }
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myGrid.DataContext = this;
InitOrder();
}
public ObservableCollection<Order> Orders { get; set; } = new();
public void InitOrder()
{
Order order1 = new Order();
Order order2 = new Order();
Order order3 = new Order();
Order order4 = new Order();
order1.Price = 100;
order1.Volume = 10;
order1.Image = "image1.gif";
order2.Price = 1000;
order2.Volume = 100;
order2.Image = "image2.gif";
order3.Price = 10000;
order3.Volume = 1000;
order3.Image = "image3.gif";
order4.Price = 100000;
order4.Volume = 10000;
order4.Image = "image4.gif";
Orders.Add(order1);
Orders.Add(order2);
Orders.Add(order3);
Orders.Add(order4);
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem selectedItem = (ComboBoxItem)((ComboBox)sender).SelectedItem;
customListView.View = (ViewBase)this.FindResource(selectedItem.Content);
}
}