原文:
wpf CollectionViewSource与ListBox的折叠、分组显示,及输入关键字 Filter的筛选
View Code
View Code
View Code
View Code
View Code
View Code
在wpf中虽然ObservableCollection<T>作为ListBox的Itemsource,很好,很强大!但是CollectionViewSource与ListBox才是天作之合!
wpf中ListBox支持分组显示,CollectionViewSource.GroupDescriptions为其实现了分组。废话不多说,下面上ListBox分组显示的Demo代码:
XAML:
<Window x:Class="WpfListGroup.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero" Title="MainWindow" Height="450" Width="525"> <Window.Resources> <CollectionViewSource x:Key="employeeCollectionViewSource" Filter="employeeCollectionViewSource_Filter"> <CollectionViewSource.SortDescriptions> <!--排序描述--> <scm:SortDescription PropertyName="Num"/> </CollectionViewSource.SortDescriptions> <CollectionViewSource.GroupDescriptions> <!--分组描述--> <PropertyGroupDescription PropertyName="Title"/> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> <Style x:Key="ButtonFocusVisual"> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate> <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/> </ControlTemplate> </Setter.Value> </Setter> </Style> <LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#F3F3F3" Offset="0"/> <GradientStop Color="#EBEBEB" Offset="0.5"/> <GradientStop Color="#DDDDDD" Offset="0.5"/> <GradientStop Color="#CDCDCD" Offset="1"/> </LinearGradientBrush> <SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070"/> <Style x:Key="nocheckedButtonStyle" TargetType="{x:Type Button}"> <Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/> <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/> <Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Padding" Value="1"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Grid Width="29.72"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="MouseOver"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="contentPresenter"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="90"/> </DoubleAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="ellipse"> <EasingColorKeyFrame KeyTime="0" Value="#FF2CA50B"/> </ColorAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"/> <VisualState x:Name="Disabled"/> </VisualStateGroup> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Unfocused"/> <VisualState x:Name="Focused"/> </VisualStateGroup> <VisualStateGroup x:Name="ValidationStates"> <VisualState x:Name="Valid"/> <VisualState x:Name="InvalidFocused"/> <VisualState x:Name="InvalidUnfocused"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Ellipse x:Name="ellipse" Fill="#FF75AB80" Margin="0" Stroke="{x:Null}" VerticalAlignment="Stretch" Width="16" Height="16"/> <Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" SnapsToDevicePixels="true" > <ContentPresenter x:Name="contentPresenter" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RenderTransformOrigin="0.5,0.5"> <ContentPresenter.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform/> <TranslateTransform/> </TransformGroup> </ContentPresenter.RenderTransform> </ContentPresenter> </Microsoft_Windows_Themes:ButtonChrome> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"/> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="25"/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="150"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <DockPanel Grid.Row="0" LastChildFill="True" > <TextBlock VerticalAlignment="Center" DockPanel.Dock="Left" Text="搜索:"/> <Button Content=" × " VerticalAlignment="Center" DockPanel.Dock="Right" Background="White" BorderBrush="{x:Null}" Margin="0" Style="{DynamicResource nocheckedButtonStyle}" HorizontalAlignment="Right" FontFamily="Forte" Foreground="White" ToolTip="清空" Click="btnClearKeyword_Click"/> <TextBox x:Name="txtEmployeeKeyword" VerticalAlignment="Center" TextChanged="txtEmployeeKeyword_TextChanged" /> </DockPanel> <ScrollViewer x:Name="scv1" Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"> <ListBox x:Name="lbx1" SelectionMode="Extended" ItemsSource="{Binding Source={StaticResource ResourceKey=employeeCollectionViewSource}}"> <!--分组样式--> <ListBox.GroupStyle> <GroupStyle> <GroupStyle.ContainerStyle> <Style TargetType="{x:Type GroupItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type GroupItem}"> <Expander> <Expander.Header> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Margin="0,0,10,0"> <!--分组的组名--> <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" /> <!--该分组元素(员工)的总和数--> <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount, StringFormat=(共{0}条)}"/> </StackPanel> <Line Grid.Column="1" SnapsToDevicePixels="true" X1="0" X2="1" Stretch="Fill" StrokeThickness="1"/> </Grid> </Expander.Header> <ItemsPresenter /> </Expander> </ControlTemplate> </Setter.Value> </Setter> </Style> </GroupStyle.ContainerStyle> </GroupStyle> </ListBox.GroupStyle> <!--右键菜单--> <ListBox.ContextMenu> <ContextMenu> <MenuItem Header="Show" Click="MenuItem_Click"/> </ContextMenu> </ListBox.ContextMenu> <!--“没有”绑定ListBox.ItemTemplate,是因为在Employee类重写了ToString()方法--> </ListBox> </ScrollViewer> <ScrollViewer x:Name="scv2" Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Visibility="Collapsed"> <ListBox Name="lbx2" ItemsSource="{Binding Source={StaticResource employeeCollectionViewSource}}" SelectionMode="Extended"> <!--按Ctrl键可多选--> <ListBox.ContextMenu> <ContextMenu> <!--右键菜单--> <MenuItem Header="Show" Click="MenuItem_Click"/> </ContextMenu> </ListBox.ContextMenu> </ListBox> </ScrollViewer> </Grid> </Window>
C#:
using System; using System.Collections.Generic; using System.Linq; 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.Navigation; using System.Windows.Shapes; using System.ComponentModel; using System.Collections.ObjectModel; namespace WpfListGroup { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); #region 基础数据(员工集合) ObservableCollection<Employee> employeeList = new ObservableCollection<Employee> { new Employee{EmployeeNum="0027",EmployeeName="张三",Sex="男",Title="副经理"}, new Employee{EmployeeNum="1086",EmployeeName="春丽",Sex="女",Title="秘书"}, new Employee{EmployeeNum="1031",EmployeeName="王五",Sex="男",Title="普通员工"}, new Employee{EmployeeNum="1211",EmployeeName="赵阳",Sex="男",Title="普通员工"}, new Employee{EmployeeNum="1201",EmployeeName="孙迪",Sex="男",Title="普通员工"}, new Employee{EmployeeNum="1416",EmployeeName="李玥玥",Sex="女",Title="秘书"}, new Employee{EmployeeNum="0017",EmployeeName="钱哆哆",Sex="男",Title="副经理"}, new Employee{EmployeeNum="1016",EmployeeName="周畅",Sex="女",Title="秘书"}, new Employee{EmployeeNum="1231",EmployeeName="郑超",Sex="男",Title="普通员工"}, new Employee{EmployeeNum="1131",EmployeeName="王思聪",Sex="男",Title="普通员工"}, new Employee{EmployeeNum="1871",EmployeeName="李文",Sex="男",Title="普通员工"}, new Employee{EmployeeNum="1266",EmployeeName="周琪妹",Sex="女",Title="秘书"} }; #endregion CollectionViewSource employeeCvs = (CollectionViewSource)this.FindResource("employeeCollectionViewSource"); employeeCvs.Source = employeeList; } /// <summary> /// 右键菜单、按住Ctrl键可多选 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MenuItem_Click(object sender, RoutedEventArgs e) { //获取关键字 string keyword = txtEmployeeKeyword.Text.Trim(); if (string.IsNullOrEmpty(keyword))//如果没有关键字 { if (lbx1.SelectedItem != null)//判断lbx1有没有选中项 { foreach (var item in lbx1.SelectedItems) { Employee employee = item as Employee; string msg = string.Format("姓名:{0},工号:{1},性别:{2},职位:{3}", employee.EmployeeName, employee.EmployeeNum, employee.Sex, employee.Title); MessageBox.Show(msg); } } } else { if (lbx2.SelectedItem != null)//有关键字的话,显示lbx2 { foreach (var item in lbx2.SelectedItems)//判断lbx2有没有选中项 { Employee employee = item as Employee; string msg = string.Format("姓名:{0},工号:{1},性别:{2},职位:{3}", employee.EmployeeName, employee.EmployeeNum, employee.Sex, employee.Title); MessageBox.Show(msg); } } } } /// <summary> /// 关键字改变时触发 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void txtEmployeeKeyword_TextChanged(object sender, TextChangedEventArgs e) { string keyword = txtEmployeeKeyword.Text.Trim(); if (string.IsNullOrEmpty(keyword))//无关键字,显示scv1下的listbox(有分组) { scv1.Visibility = Visibility.Visible; scv2.Visibility = Visibility.Collapsed; } else//有关键字,显示scv2下的listbox(无分组) { scv1.Visibility = Visibility.Collapsed; scv2.Visibility = Visibility.Visible; } CollectionViewSource employeeCvs = (CollectionViewSource)this.FindResource("employeeCollectionViewSource"); employeeCvs.View.Refresh();//刷新View } /// <summary> /// 根据关键字(工号或姓名)筛选员工 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void employeeCollectionViewSource_Filter(object sender, FilterEventArgs e) { string keyword = txtEmployeeKeyword.Text.Trim(); Employee employee = e.Item as Employee; if (employee != null) { if (string.IsNullOrEmpty(keyword))//无关键字,直接Accept { e.Accepted = true; } else { //有关键字、筛选员工号或姓名中包含关键字的员工 e.Accepted = employee.EmployeeNum.Contains(keyword) || employee.EmployeeName.Contains(keyword); } } } /// <summary> /// 清空关键字 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnClearKeyword_Click(object sender, RoutedEventArgs e) { this.txtEmployeeKeyword.Clear(); } } public class Employee:INotifyPropertyChanged { #region 实现更改通知 public event PropertyChangedEventHandler PropertyChanged; public void RaisePropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } #endregion /// <summary> /// 重载ToString()方法 /// </summary> /// <returns></returns> public override string ToString() { return this.EmployeeNum + " " + this.EmployeeName; } private string title; /// <summary> /// 职位 /// </summary> public string Title { get { return title; } set { title = value; RaisePropertyChanged("Title"); } } private string employeeName; /// <summary> /// 姓名 /// </summary> public string EmployeeName { get { return employeeName; } set { employeeName = value; RaisePropertyChanged("EmployeeName"); } } private string employeeNum; /// <summary> /// 工号 /// </summary> public string EmployeeNum { get { return employeeNum; } set { employeeNum = value; RaisePropertyChanged("EmployeeNum"); } } private string sex; /// <summary> /// 性别 /// </summary> public string Sex { get { return sex; } set { sex = value; RaisePropertyChanged("Sex"); } } } }
运行效果:
右键菜单点击“Show” 弹出选中项的员工信息:
输入关键字"同步"筛选模糊查询员工:
点击清空按钮清空关键字,“恢复”分组数据:
总结核心xaml:
①资源CollectionViewSource, CollectionViewSource.GroupDescriptions:分组描述(依据),CollectionViewSource.SortDescriptions:分组排序(描述)
在资源中:
<CollectionViewSource x:Key="employeeCollectionViewSource" Filter="employeeCollectionViewSource_Filter"> <CollectionViewSource.SortDescriptions> <!--排序描述--> <scm:SortDescription PropertyName="Num"/> </CollectionViewSource.SortDescriptions> <CollectionViewSource.GroupDescriptions> <!--分组描述--> <PropertyGroupDescription PropertyName="Title"/> </CollectionViewSource.GroupDescriptions> </CollectionViewSource>
②绑定到ListBox的Itemsource上,设置分组样式,使用Expander控件使分组可以折叠:
<ScrollViewer x:Name="scv1" Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"> <ListBox x:Name="lbx1" SelectionMode="Extended" ItemsSource="{Binding Source={StaticResource ResourceKey=employeeCollectionViewSource}}"> <!--分组样式--> <ListBox.GroupStyle> <GroupStyle> <GroupStyle.ContainerStyle> <Style TargetType="{x:Type GroupItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type GroupItem}"> <Expander> <Expander.Header> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Margin="0,0,10,0"> <!--分组的组名--> <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" /> <!--该分组元素(员工)的总和数--> <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount, StringFormat=(共{0}条)}"/> </StackPanel> <Line Grid.Column="1" SnapsToDevicePixels="true" X1="0" X2="1" Stretch="Fill" StrokeThickness="1"/> </Grid> </Expander.Header> <ItemsPresenter /> </Expander> </ControlTemplate> </Setter.Value> </Setter> </Style> </GroupStyle.ContainerStyle> </GroupStyle> </ListBox.GroupStyle> <!--右键菜单--> <ListBox.ContextMenu> <ContextMenu> <MenuItem Header="Show" Click="MenuItem_Click"/> </ContextMenu> </ListBox.ContextMenu> <!--“没有”绑定ListBox.ItemTemplate,是因为在Employee类重写了ToString()方法--> </ListBox> </ScrollViewer>
总结核心C#:
①CollectionViewSource的筛选器Filter的方法:
/// <summary> /// 根据关键字(工号或姓名)筛选员工 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void employeeCollectionViewSource_Filter(object sender, FilterEventArgs e) { string keyword = txtEmployeeKeyword.Text.Trim(); Employee employee = e.Item as Employee; if (employee != null) { if (string.IsNullOrEmpty(keyword))//无关键字,直接Accept { e.Accepted = true; } else { //有关键字、筛选员工号或姓名中包含关键字的员工 e.Accepted = employee.EmployeeNum.Contains(keyword) || employee.EmployeeName.Contains(keyword); } } }
②关键字文本框的文本发生改变时触发的事件:
/// <summary> /// 关键字改变时触发 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void txtEmployeeKeyword_TextChanged(object sender, TextChangedEventArgs e) { string keyword = txtEmployeeKeyword.Text.Trim(); if (string.IsNullOrEmpty(keyword))//无关键字,显示scv1下的listbox(有分组) { scv1.Visibility = Visibility.Visible; scv2.Visibility = Visibility.Collapsed; } else//有关键字,显示scv2下的listbox(无分组) { scv1.Visibility = Visibility.Collapsed; scv2.Visibility = Visibility.Visible; } CollectionViewSource employeeCvs = (CollectionViewSource)this.FindResource("employeeCollectionViewSource"); employeeCvs.View.Refresh();//刷新View }
总结:以上就是ListBox的分组、折叠、筛选显示的Demo。日积月累,水滴石穿!