在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="",EmployeeName="张三",Sex="男",Title="副经理"},
new Employee{EmployeeNum="",EmployeeName="春丽",Sex="女",Title="秘书"},
new Employee{EmployeeNum="",EmployeeName="王五",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="赵阳",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="孙迪",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="李玥玥",Sex="女",Title="秘书"},
new Employee{EmployeeNum="",EmployeeName="钱哆哆",Sex="男",Title="副经理"},
new Employee{EmployeeNum="",EmployeeName="周畅",Sex="女",Title="秘书"},
new Employee{EmployeeNum="",EmployeeName="郑超",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="王思聪",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",EmployeeName="李文",Sex="男",Title="普通员工"},
new Employee{EmployeeNum="",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。日积月累,水滴石穿!