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;
        }
    }
}
相关推荐
小白18 小时前
WPF自定义Dialog模板,内容用不同的Page填充
wpf
Crazy Struggle1 天前
C# + WPF 音频播放器 界面优雅,体验良好
c#·wpf·音频播放器·本地播放器
James.TCG2 天前
WPF动画
wpf
He BianGu2 天前
笔记:简要介绍WPF中FormattedText是什么,主要有什么功能
笔记·c#·wpf
脚步的影子2 天前
.NET 6.0 + WPF 使用 Prism 框架实现导航
.net·wpf
jyl_sh2 天前
Ribbon (WPF)
ribbon·wpf·client·桌面程序开发·c/s客户端
wo63704312 天前
[Visual Stuidio 2022使用技巧]2.配置及常用快捷键
c#·.net·wpf
小黄人软件2 天前
wpf 字符串 与 变量名或函数名 相互转化:反射
wpf
界面开发小八哥3 天前
DevExpress WPF中文教程:如何解决排序、过滤遇到的常见问题?(二)
.net·wpf·界面控件·devexpress·ui开发
Vae_Mars3 天前
WPF中图片的宫格显示
wpf