WPF数据视图

将集合绑定到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">&lt;</Button>
                <TextBlock Name="lblPosition" VerticalAlignment="Center"></TextBlock>
                <Button Name="cmdNext" Click="cmdNext_Click">&gt;</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;
        }
    }
}
相关推荐
晚安苏州8 小时前
WPF DataTemplate 数据模板
wpf
甜甜不吃芥末1 天前
WPF依赖属性详解
wpf
Hat_man_1 天前
WPF制作图片闪烁的自定义控件
wpf
晚安苏州3 天前
WPF Binding 绑定
wpf·wpf binding·wpf 绑定
wangnaisheng3 天前
【WPF】RenderTargetBitmap的使用
wpf
dotent·3 天前
WPF 完美解决改变指示灯的颜色
wpf
orangapple5 天前
WPF 用Vlc.DotNet.Wpf实现视频播放、停止、暂停功能
wpf·音视频
ysdysyn5 天前
wpf mvvm 数据绑定数据(按钮文字表头都可以),根据长度进行换行,并把换行的文字居中
c#·wpf·mvvm
orangapple5 天前
WPF 使用LibVLCSharp.WPF实现视频播放、停止、暂停功能
wpf
晚安苏州5 天前
WPF ControlTemplate 控件模板
wpf