将集合绑定到ItemsControl控件时,会不加通告的在后台创建数据视图------位于数据源和绑定的控件之间。数据视图是进入数据源的窗口,可以跟踪当前项,并且支持各种功能,如排序、过滤、分组。
这些功能和数据对象本身是相互独立的,这意味着可在窗口的不同部分使用不同的方式绑定相同的数据。例如,可将同一个集合绑定到两个不同的列表,并对集合进行过滤以显示不同的记录。(来自于WPF编程宝典。我实测下来,绑定自同一个数据源的ItemsControl控件会共享一个View,当对该View进行筛选、排序时,会应用到所有绑定到该数据源的控件。)
获取视图的方法:
cs
ListCollectionView? view = CollectionViewSource.GetDefaultView(filterListBox.ItemsSource) as ListCollectionView;
ListCollectionView? view = CollectionViewSource.GetDefaultView(Orders) as ListCollectionView;
可以看到,可以直接通过数据源来获取视图,这也表明,绑定到同一个数据源的控件会公用一个视图。
视图有 MoveCurrentToPrevious()、MoveCurrentToNext() 方法,可以用于视图导航。
cs
private void cmdPrev_Click(object sender, RoutedEventArgs e)
{
View?.MoveCurrentToPrevious();
}
private void cmdNext_Click(object sender, RoutedEventArgs e)
{
View?.MoveCurrentToNext();
}
private void view_CurrentChanged(object? sender, EventArgs e)
{
lblPosition.Text = "Record " + (View?.CurrentPosition + 1).ToString() + " of " + View?.Count.ToString();
cmdPrev.IsEnabled = View?.CurrentPosition > 0;
cmdNext.IsEnabled = View?.CurrentPosition < View?.Count - 1;
}
视图排序
cs
View.SortDescriptions.Add(new SortDescription("Volume", ListSortDirection.Ascending));
View.SortDescriptions.Add(new SortDescription("Price", ListSortDirection.Descending));
视图分组
cs
<ListBox x:Name="groupListBox" ItemsSource="{Binding Path=Orders}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock Text="{Binding Price}"></TextBlock> - <TextBlock Text="{Binding Volume}"></TextBlock>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Foreground="White" Background="LightGreen" Margin="0,5,0,0" Padding="3"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
cs
View.GroupDescriptions.Add(new PropertyGroupDescription("Volume"));
视图过滤
cs
public class ProductByPriceFilterer
{
public ProductByPriceFilterer(decimal minimumPrice)
{
MinimumPrice = minimumPrice;
}
public decimal MinimumPrice { get; set; }
public bool FilterItem(Object item)
{
Order? order = item as Order;
if (order != null)
{
return order.Price > MinimumPrice;
}
return false;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
View = (ListCollectionView)CollectionViewSource.GetDefaultView(Orders);
View.IsLiveFiltering = true;
View.LiveFilteringProperties.Add("Price");
}
public ObservableCollection<Order> Orders { get; set; } = new();
private ListCollectionView? View;
public decimal MinPrice { get; set; } = 200;
private ProductByPriceFilterer? filterer;
private void cmdFilter_Click(object sender, RoutedEventArgs e)
{
if (View != null)
{
filterer = new ProductByPriceFilterer(MinPrice);
View.Filter = new Predicate<object>(filterer.FilterItem);
}
}
private void cmdRemoveFilter_Click(object sender, RoutedEventArgs e)
{
if (View != null)
{
View.Filter = null;
}
}
}
完整代码文件:
MainWindow.xaml
cs
<Window x:Class="DataView.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:DataView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" >
<StackPanel Orientation="Horizontal">
<Button Name="cmdPrev" Click="cmdPrev_Click"><</Button>
<TextBlock Name="lblPosition" VerticalAlignment="Center"></TextBlock>
<Button Name="cmdNext" Click="cmdNext_Click">></Button>
</StackPanel>
<ListBox x:Name="navigateListBox" DisplayMemberPath="Price" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Orders}"/>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">Price > Than</Label>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=MinPrice}"></TextBox>
<Button Grid.Row="1" Grid.Column="0" Click="cmdFilter_Click">Filter</Button>
<Button Grid.Row="1" Grid.Column="1" Click="cmdRemoveFilter_Click">Remove Filter</Button>
</Grid>
<ListBox Name="filterListBox" DisplayMemberPath="Price" ItemsSource="{Binding Path=Orders}"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0">
<ListBox x:Name="groupListBox" ItemsSource="{Binding Path=Orders}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock Text="{Binding Price}"></TextBlock> - <TextBlock Text="{Binding Volume}"></TextBlock>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Foreground="White" Background="LightGreen" Margin="0,5,0,0" Padding="3"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
</StackPanel>
<Button Grid.Row="2" Grid.Column="0" Content="Increase Price" Click="IncreaseButton_Click"/>
<Button Grid.Row="2" Grid.Column="1" Content="Decrease Price" Click="DecreaseButton_Click"/>
</Grid>
</Window>
MainWindow.xaml.cs
cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DataView;
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 ProductByPriceFilterer
{
public ProductByPriceFilterer(decimal minimumPrice)
{
MinimumPrice = minimumPrice;
}
public decimal MinimumPrice { get; set; }
public bool FilterItem(Object item)
{
Order? order = item as Order;
if (order != null)
{
return order.Price > MinimumPrice;
}
return false;
}
}
public class PriceRangeProductGrouper : IValueConverter
{
public int GroupInterval { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
decimal price = (decimal)value;
if (price < GroupInterval)
{
return string.Format("Less than {0:C}", GroupInterval);
}
else
{
int interval = (int)price / GroupInterval;
int lowerLimit = interval * GroupInterval;
int upperLimit = (interval + 1) * GroupInterval;
return string.Format("{0:C} to {1:C}", lowerLimit, upperLimit);
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("This converter is for grouping only.");
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myGrid.DataContext = this;
InitOrders();
InitView();
}
public void InitOrders()
{
Order order1 = new Order();
Order order2 = new Order();
Order order3 = new Order();
Order order4 = new Order();
order1.Price = 100;
order1.Volume = 100;
order1.Image = "image1.gif";
order2.Price = 1000;
order2.Volume = 100;
order2.Image = "image2.gif";
order3.Price = 10000;
order3.Volume = 10000;
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 InitView()
{
View = (ListCollectionView)CollectionViewSource.GetDefaultView(Orders);
if(View != null)
{
View.CurrentChanged += new EventHandler(view_CurrentChanged);
View.SortDescriptions.Add(new SortDescription("Volume", ListSortDirection.Ascending));
View.SortDescriptions.Add(new SortDescription("Price", ListSortDirection.Descending));
View.GroupDescriptions.Add(new PropertyGroupDescription("Volume"));
View.IsLiveFiltering = true;
View.LiveFilteringProperties.Add("Price");
}
}
public ObservableCollection<Order> Orders { get; set; } = new();
private ListCollectionView? View;
private void cmdPrev_Click(object sender, RoutedEventArgs e)
{
View?.MoveCurrentToPrevious();
}
private void cmdNext_Click(object sender, RoutedEventArgs e)
{
View?.MoveCurrentToNext();
}
private void view_CurrentChanged(object? sender, EventArgs e)
{
lblPosition.Text = "Record " + (View?.CurrentPosition + 1).ToString() + " of " + View?.Count.ToString();
cmdPrev.IsEnabled = View?.CurrentPosition > 0;
cmdNext.IsEnabled = View?.CurrentPosition < View?.Count - 1;
}
public decimal MinPrice { get; set; } = 200;
private ProductByPriceFilterer? filterer;
private void cmdFilter_Click(object sender, RoutedEventArgs e)
{
if (View != null)
{
filterer = new ProductByPriceFilterer(MinPrice);
View.Filter = new Predicate<object>(filterer.FilterItem);
}
}
private void cmdRemoveFilter_Click(object sender, RoutedEventArgs e)
{
if (View != null)
{
View.Filter = null;
}
}
private void IncreaseButton_Click(object sender, RoutedEventArgs e)
{
foreach(var order in Orders)
{
order.Price *= 10;
}
}
private void DecreaseButton_Click(object sender, RoutedEventArgs e)
{
foreach (var order in Orders)
{
order.Price /= 10;
}
}
}