WPF 进阶 数据视图

33-36  2.1h 

1.View对象

当将集合(或DataTabIe)绑定到ItemsControI控件时,会不加通告地在后台创建数据视
图一一位于数据源和绑定的控件之间。数据视图是进入数据源的窗口,可以跟踪当前项,并且
支持各种功能,如排序、过滤以及分组。这些功能和数据对象本身是相互独立的,这意味着可
在窗口的不同部分(或应用程序的不同部分)使用不同的方式绑定相同的数据。例如,可将同
产品集合绑定到两个不同的列表,并对产品进行过滤以显示不同的记录。

使用的视图对象取决于数据对象的类型。所有视图都继承自CoIIectionVlew类,并且有两个
继承自CoIIectionView类的特殊实现:ListCollectionView和BindmgListCollectionView。下面是
CollectionView类的工作原理:
*如果数据源实现了IBindtngLlst接口,就会创建BindingListCollectionView视图。当绑定
到ADO.NET中的DataTabIe对象时会创建该视图。
*如果数据源没有实现1BindingList接口,但实现了旧st接口,就会创建ListCoIIectionView
视图。当绑定到ObservableCollection集合(如产品列表)时会创建该视图。
*如果数据没有实现IBindmgList或IList接口,但实现了IEnumerable接口,就会得到基
本的CollectionView视图。

检索视图对象

为得到前使用的视图对象,可使用System.Windows.Data.CoIIectionViewSource类的
GetDefaultView()静态方法。当调用GetDefaultView()方法时,传入数据.一一正在使用的集合
或DataTable对象。下面的示例获取绑定到列表的产品集合的视图:
ICoIIectionView view=CollectionViewSource.GetDefaultView(lstProduct.ItemsSource),

视图导航

可使用视图对象完成的最简单一件事是确定列表中的项数(通过Count属性),以及获取当
前数据对象的引用(通过CurrentItem属性)或当前位置索引(通过CurrentIndex属性)。还可以用
少数方法从一条记录移到另一条记录,如MoveCurrentToFirst()、MoveCurrentToLast()、Move-
CurrentToNext()、MoveCurrentToPrevious()以及MoveCurrentToPosition()。

<Window x:Class="DataBinding.NavigateCollection"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="NavigateCollection" Height="367" Width="422"
        xmlns:local="clr-namespace:DataBinding"
    >
 
  <Grid Margin="10">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
      </Grid.RowDefinitions>
        <!--为得到有趣的效果,可为这个窗体添加列表控件,这样用户就可以使用按钮逐一查看每条
            记录,或亻吏用列表直接跳到特定的项,-->
      <ComboBox Name="lstProducts" DisplayMemberPath="ModelName" Text="{Binding Path=ModelName}"
                IsSynchronizedWithCurrentItem="True"
                SelectionChanged="lstProducts_SelectionChanged" ></ComboBox>

      <Border Grid.Row="1" Padding="5" Margin="0,5,0,5" Background="LightSteelBlue">
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
          </Grid.RowDefinitions>

                <!--为绑定的产品显示数据的绑定文本框保持不变。它们只需要指明合适的属性,-->
          <TextBlock Margin="7">Model Number:</TextBlock>
          <TextBox Margin="5" Grid.Column="1" Text="{Binding Path=ModelNumber}"></TextBox>
            
          <TextBlock Margin="7" Grid.Row="1">Model Name:</TextBlock>
          <TextBox Margin="5" Grid.Row="1" Grid.Column="1" Text="{Binding Path=ModelName}"></TextBox>
          <TextBlock Margin="7" Grid.Row="2">Unit Cost:</TextBlock>
          <TextBox Margin="5" Grid.Row="2" Grid.Column="1" Text="{Binding Path=UnitCost,StringFormat={}{0:C}}"></TextBox>
          <TextBlock Margin="7,7,7,0" Grid.Row="3">Description:</TextBlock>
          <TextBox Margin="7" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" 
                   TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" Text="{Binding Path=Description}"></TextBox>
        </Grid>        
      </Border>

    <Grid Grid.Row="2">
      <StackPanel Orientation="Horizontal">
        <Button Name="cmdPrev" Click="cmdPrev_Click">&lt;</Button>
        <TextBlock Margin="5,0,5,0" Name="lblPosition" VerticalAlignment="Center"></TextBlock>
        <Button Name="cmdNext" Click="cmdNext_Click">&gt;</Button>
      </StackPanel>
    </Grid>
    </Grid>
</Window>
NavigateCollection.xaml
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using StoreDatabase;

namespace DataBinding
{
    /// <summary>
    /// Interaction logic for NavigateCollection.xaml
    /// </summary>

    public partial class NavigateCollection : System.Windows.Window
    {
        private ICollection<Product> products;
        //然而,这个示例没有提供任何列表控件,所以需要由您负责控制导航。为简单起见,可在
        //窗凵类中使用成员变量来保存视图的引用:
        private ListCollectionView view;

        public NavigateCollection()
        {
            InitializeComponent();
            //当首次加载窗口时可以获取数据,将数据放到窗口的DataContext属性中,并保存指向视
            //图的一个引用:
            products = App.StoreDb.GetProducts();
            this.DataContext = products;
            //在这个示例中,代码将视图转换成恰当的视图类型(Lis℃ollectionView),而不使用
            //℃ollectionView接凵。虽然ICoIIectionView接口提供了大多数相同的功能,但缺少能提供集合
            //中项数的Count属性。
            view = (ListCollectionView)CollectionViewSource.GetDefaultView(this.DataContext);
            //示例中有一行额外的代码语句。该行代码为视图的Curren℃hanged事件连接事件处
            //理程序。当引发这个事件时,可执行一些有用的操作·例如,根据当前位置启用或禁用上一个
            //按钮和下一个按钮,以及在窗口底部的TextBIock元素中显示当前位置。

            view.CurrentChanged += new EventHandler(view_CurrentChanged);
            //当首次检索产品集合时,绑定列表:
            lstProducts.ItemsSource = products;            
        }
        //最后一步是为前面的按钮和后面的按钮编写逻辑。因为当不能应用它们时,会自动禁用这
        //些按钮,所以不必担心移到第一项之前或最后一项之后。
        private void cmdNext_Click(object sender, RoutedEventArgs e)
        {    
            view.MoveCurrentToNext();          
        }
        private void cmdPrev_Click(object sender, RoutedEventArgs e)
        {
            view.MoveCurrentToPrevious();
        }
        //默认情况下,在ItemsControI控件中选择的项和视图中
       // 的当前项并不同步。这意味着当从列表中进行新的选霧埘,不是导航到新记录,而是最终会修
        //改当前记录的ModeIName属性。幸运的是,可采用两种简单方法解决这个问题。
        //粗鲁的强制方法是无论何时在列表中选择一条记录,都简单地移动到新记录。下面的代码
        //可以完成这一工作:

        private void lstProducts_SelectionChanged(object sender, RoutedEventArgs e)
        {
            // view.MoveCurrentTo(lstProducts.SelectedItem);
          // 更简单的方法是,将1temsControl.IsSynchronizedWithCurrentItem属性设置为true。使用这
         //种方法,当前选择的项会被自动同步,从而匹配视图的当前位置,而且不需要使用任何代码。
        }

        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; 
        }
    }
}
NavigateCollection.xaml.cs

运行

 

2.过滤排序

过滤集合

 

在将集合用作数据源时,可使用视图对象的Filter属性设置过滤器。

Filter属性的实现有些笨拙。它接受一个Predicate委托,该委托指向自定义的过滤方法。

过滤器检查集合中的每个数据项,并且如果被检查的项满足过滤条件,就返回true,否则
则返回false。当创建predicate对象时,指定进行检查的对象类型。笨拙的部分是视图期望用户
使用Predicate<object>实例一一一您不能使用更有用的内容(如Predicate<Product>),这样就不可避
免地使用类型转代码。

过滤DataTable对象

排序

还可以,使用视进行排序。最简单的方法是根据每个数据项中的一个或多个属性的值进行
排序。使用System℃omponentModel.SortDescription对象确定希望使用的字段。每个SortDescnption
对象确定希望用于排序的字段和排序方向〔升序或降序)。按照希望应用它们的顺序添加
SortDescnption对象。例如,可首先根据类别进行排序,然后再根据型号名称进行排序。

<Window x:Class="DataBinding.FilterCollection"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataBinding"
    Title="FilterCollection" Height="390" Width="628"
    >
 
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="3*"></ColumnDefinition>
      <ColumnDefinition Width="5*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
      </Grid.RowDefinitions>

      <Button Margin="7,7,7,0" Padding="2" Click="cmdGetProducts_Click">Get Products</Button>
      <ListBox Grid.Row="1" Margin="7,3,7,10" Name="lstProducts" DisplayMemberPath="ModelName"></ListBox>
            
      
      <Border Grid.Row="2" Margin="7" Padding="7" BorderBrush="SteelBlue" BorderThickness="1" >
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
          </Grid.RowDefinitions>
          <Label>Price > Than</Label>
          <TextBox Grid.Column="1" Name="txtMinPrice" TextChanged="txtMinPrice_TextChanged">200</TextBox>
          
            <Button Grid.Row="1" Margin="2" Padding="2" Click="cmdFilter_Click">Filter</Button>
            <Button Grid.Row="1" Grid.Column="1" Margin="2" Padding="2" Click="cmdRemoveFilter_Click">Remove Filter</Button>
          
        </Grid>
      </Border>
    </Grid>

    <GridSplitter Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Stretch"
                   Width="5"></GridSplitter>
    
    <Border Grid.Column="1" Padding="7" Margin="7" Background="LightSteelBlue">
      <Grid DataContext="{Binding ElementName=lstProducts, Path=SelectedItem}" >
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto"></ColumnDefinition>
          <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <TextBlock Margin="7">Model Number:</TextBlock>
        <TextBox Margin="5" Grid.Column="1" Text="{Binding Path=ModelNumber}"></TextBox>
        <TextBlock Margin="7" Grid.Row="1">Model Name:</TextBlock>
        <TextBox Margin="5" Grid.Row="1" Grid.Column="1" Text="{Binding Path=ModelName}"></TextBox>
        <TextBlock Margin="7" Grid.Row="2">Unit Cost:</TextBlock>
                <TextBox Margin="5" Grid.Row="2" Grid.Column="1" Text="{Binding Path=UnitCost,StringFormat={}{0:C}}"></TextBox>
        <TextBlock Margin="7,7,7,0" Grid.Row="3">Description:</TextBlock>
        <TextBox Margin="7" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" 
                 TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" Text="{Binding Path=Description}"></TextBox>


      </Grid>
    </Border>
  
  </Grid>
</Window>
FilterCollection.xaml
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using StoreDatabase;

namespace DataBinding
{
    /// <summary>
    /// Interaction logic for FilterCollection.xaml
    /// </summary>

    public partial class FilterCollection : System.Windows.Window
    {

        public FilterCollection()
        {
            InitializeComponent();
        }


        private ICollection<Product> products;

        private void cmdGetProducts_Click(object sender, RoutedEventArgs e)
        {
            products = App.StoreDb.GetProducts();
            lstProducts.ItemsSource = products;

            ICollectionView view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
            
            view.SortDescriptions.Add(new SortDescription("ModelName", ListSortDirection.Ascending));

            ListCollectionView lcview = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource) as ListCollectionView;
            // Now if you edit and reduce the price (below the filter condition) the record will disappear automatically.
            lcview.IsLiveFiltering = true;
            lcview.LiveFilteringProperties.Add("UnitCost");

            //view.GroupDescriptions.Add(new PropertyGroupDescription("CategoryName"));

            //ListCollectionView view = (ListCollectionView)CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
            //view.CustomSort = new SortByModelNameLength();

        }

        private void cmdFilter_Click(object sender, RoutedEventArgs e)
        {
            decimal minimumPrice;
            if (Decimal.TryParse(txtMinPrice.Text, out minimumPrice))
            {
                ListCollectionView view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource) as ListCollectionView;

                if (view != null)
                {
                    filterer = new ProductByPriceFilterer(minimumPrice);
                    view.Filter = new Predicate<object>(filterer.FilterItem);
                    view.Refresh();
                }
            }
        }               

        private void cmdRemoveFilter_Click(object sender, RoutedEventArgs e)
        {
            ListCollectionView view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource) as ListCollectionView;
            if (view != null)
            {
                view.Filter = null;
            }
        }

        private ProductByPriceFilterer filterer;

        private void txtMinPrice_TextChanged(object sender, TextChangedEventArgs e)
        {
            ListCollectionView view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource) as ListCollectionView;
            if (view != null)
            {                
                decimal minimumPrice;
                if (Decimal.TryParse(txtMinPrice.Text, out minimumPrice) && (filterer != null))
                {
                    filterer.MinimumPrice = minimumPrice;
                    //view.Refresh();
                }
            }
        }
    }

    public class ProductByPriceFilterer
    {
        public decimal MinimumPrice
        {
            get;
            set;
        }

        public ProductByPriceFilterer(decimal minimumPrice)
        {
            MinimumPrice = minimumPrice;
        }

        public bool FilterItem(Object item)
        {
            Product product = item as Product;
            if (product != null)
            {
                if (product.UnitCost > MinimumPrice)
                {
                    return true;
                }
            }
            return false;
        }
    }

    public class SortByModelNameLength : System.Collections.IComparer
    {
        public int Compare(object x, object y)
        {
            Product productX = (Product)x;
            Product productY = (Product)y;
            return productX.ModelName.Length.CompareTo(productY.ModelName.Length);
        }
    }

}
FilterCollection.xaml.cs

3.分组

与支持排序的方式相同,视图也支持分组。与排序一样,可使用简单的方式进行分组(根据
单个属性值),也可以使用复杂的方式进行分组(使用自定义的回调函数)。

<Window x:Class="DataBinding.GroupList"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="GroupList" Height="390" Width="628"
    >
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="3*"></ColumnDefinition>
      <ColumnDefinition Width="5*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
      </Grid.RowDefinitions>

      <Button Margin="7,7,7,0" Padding="2" Click="cmdGetProducts_Click">Get Products</Button>
      <ListBox Grid.Row="1" Margin="7,3,7,10" Name="lstProducts" DisplayMemberPath="ModelName">
                <!--当使用分组时,列
                表为每个分组创建了单独的GroupItem对象,并且为列表添加了这些GroupItem对象。GroupItem是
                内容控件,所以每个GroupItem对象都包含一个适当的具有实际数据的容器(如对象)。
                显示分组的秘密是格式化GroupItem元素,使其突出显示。
                可使用样式为列表中的所有GroupItem对象应用格式。然而,您不仅可能希望调整格式一一例
                如,可能还希望显示分组标题,这就需要使用板。幸运的是,ItemsControI类通过它的
                ItemsControl.GroupStyIe属性简化了这两项任务,、性提供了一个GroupStyle对象的集合。虽
                然属性名称中包含了“StyIe”,但GroupStyle类并不是样式。它只是一个简便的包,为配置
                GroupItem对象封装了一些有用的设置。。-->
        <ListBox.GroupStyle>
          <GroupStyle>
                  <!--为添加分组标题,需要设置GroupStyle.HeaderTemplate属性。可亻吏用普通的数据模板填充
                    该属性,就像在第20章中看到的那样。也可在模板中使用元素和数据绑定表达式的任意组合。
                    然而,此处有一个技巧。当编写绑定表达式时,不能绑定到列表中的数据对象(在这个示例
                    中是Product对象),而要绑定到分组的PorpertyGroupDescription对象。这意味着,如果希望显
                    示分组的字段值(如图21.4所示),就需要绑定PorpertyGroupDescription.Name属性,而不是绑
                    定Product℃ategoryName属性。
                    -->
            <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>
    </Grid>
    <GridSplitter Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Stretch"
                   Width="5"></GridSplitter>
    
    <Border Grid.Column="1" Padding="7" Margin="7" Background="LightSteelBlue">
      <Grid DataContext="{Binding ElementName=lstProducts, Path=SelectedItem}" >
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto"></ColumnDefinition>
          <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <TextBlock Margin="7">Model Number:</TextBlock>
        <TextBox Margin="5" Grid.Column="1" Text="{Binding Path=ModelNumber}"></TextBox>
        <TextBlock Margin="7" Grid.Row="1">Model Name:</TextBlock>
        <TextBox Margin="5" Grid.Row="1" Grid.Column="1" Text="{Binding Path=ModelName}"></TextBox>
        <TextBlock Margin="7" Grid.Row="2">Unit Cost:</TextBlock>
                <TextBox Margin="5" Grid.Row="2" Grid.Column="1" Text="{Binding Path=UnitCost,StringFormat={}{0:C}}"></TextBox>
        <TextBlock Margin="7,7,7,0" Grid.Row="3">Description:</TextBlock>
        <TextBox Margin="7" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" 
                 TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" Text="{Binding Path=Description}"></TextBox>
      </Grid>
    </Border>
  </Grid>
</Window>
GroupList.xaml
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using StoreDatabase;

namespace DataBinding
{
    /// <summary>
    /// Interaction logic for FilterCollection.xaml
    /// </summary>

    public partial class GroupList : System.Windows.Window
    {

        public GroupList()
        {
            InitializeComponent();
        }
        private ICollection<Product> products;

        private void cmdGetProducts_Click(object sender, RoutedEventArgs e)
        {
            products = App.StoreDb.GetProducts();
            lstProducts.ItemsSource = products;

            ICollectionView view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);

            //如果希望对分组进行排序,只需要确保用于排序的第一
           // 个SortDescnption对象基于分组字段即可。下面的代码根据类别名称按字母顺序对类别进行排
            //序,然后根据型号名称对类别中的每个产品按字母顺序进行排序。
            view.SortDescriptions.Add(new SortDescription("CategoryName", ListSortDirection.Ascending));
            view.SortDescriptions.Add(new SortDescription("ModelName", ListSortDirection.Ascending));
            //为执行分组,需要为CollectionView.GroupDescnptions集合添加System℃omponent-
            //ModeI.PropenyGroupDescnption对象。下面的示例根据类别名称进行分组:
            view.GroupDescriptions.Add(new PropertyGroupDescription("CategoryName"));
        }
    }
}
GroupList.xaml.cs

使用范围分组

可创建一个类,检查一些信息并为了显示目的而将它放置
到一个概念组中。这种技术通常用于使用特定范围内的数字或日期信息对数据对象进行分组。
例如,可为小于50美元的产品创建一个组,为50美元和100美元之间的产品创建另一个组,
等等。
为创建这个解决方案,需要提供值转换器,检查数据源中的一个字段(或多个字段,如果实
现了IMultiValueConverter接口),并返回组标题。只要为多个数据对象使用相同的组标题,这
些对象就会被放到相同的逻辑分组中。

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.Globalization;

namespace DataBinding
{
    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.");
        }
    }


}
PriceRangeProductGrouper .cs
<Window x:Class="DataBinding.GroupInRanges"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:component="clr-namespace:System.ComponentModel;assembly=WindowsBase"
         xmlns:local="clr-namespace:DataBinding"
    Title="GroupInRanges" Height="300" Width="300"
    >

  <Window.Resources>
    <local:PriceRangeProductGrouper x:Key="Price50Grouper" GroupInterval="50"/>
    <CollectionViewSource x:Key="GroupByRangeView">
      <CollectionViewSource.SortDescriptions>
        <component:SortDescription PropertyName="UnitCost" Direction="Ascending"/>        
      </CollectionViewSource.SortDescriptions>
      <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="UnitCost" Converter="{StaticResource Price50Grouper}"/>
      </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>
  </Window.Resources>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"></RowDefinition>
      <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <Button Margin="7,7,7,0" Padding="2" Click="cmdGetProducts_Click">Get Products</Button>
    <ListBox Grid.Row="1" Margin="7,3,7,10" Name="lstProducts" ItemsSource="{Binding Source={StaticResource GroupByRangeView}}">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <TextBlock>
            <TextBlock Text="{Binding ModelName}"></TextBlock>
            (<TextBlock Text="{Binding UnitCost,StringFormat={}{0:C}}"></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>    
  </Grid>
</Window>
GroupInRanges.xaml
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using StoreDatabase;

namespace DataBinding
{
    /// <summary>
    /// Interaction logic for GroupInRanges.xaml
    /// </summary>

    public partial class GroupInRanges : System.Windows.Window
    {

        public GroupInRanges()
        {
            InitializeComponent();
        }
        private ICollection<Product> products;

        private void cmdGetProducts_Click(object sender, RoutedEventArgs e)
        {
            products = App.StoreDb.GetProducts();
            CollectionViewSource viewSource = (CollectionViewSource)this.FindResource("GroupByRangeView");
            viewSource.Source = products;
            //lstProducts.ItemsSource = products;


            //下面的代码使用转换器应用范围分组。注意,首先必须根据价格对产品进行排序,否则将
            //会根据它们在列表中的位置进行分组。
            //ICollectionView view = CollectionViewSource.GetDefaultView(lstProducts.ItemsSource);
            //view.SortDescriptions.Add(new SortDescription("UnitCost", ListSortDirection.Ascending));
            //PriceRangeProductGrouper grouper = new PriceRangeProductGrouper();
            //grouper.GroupInterval = 50;
            //view.GroupDescriptions.Add(new PropertyGroupDescription("UnitCost", grouper));
        }
    }
}
GroupInRanges.xaml.cs

分组和虚拟化

虚拟化功能降低了控件的内存开销,而且在绑定极长的列表时提升了速
度。但是,即使控件支持虚拟化,也不会在启用虚拟化时使用。WPF亻吏用新的VirtualizingStack-
Panel.IsVirtualizmgWhenGroupmg属纠正了这个问题。将其设置为true,分组列表将与未分组
列表获得相同的虚拟化性能提升效果

posted @ 2021-03-02 11:50  KnowledgePorter  阅读(136)  评论(0)    收藏  举报