How to group ListBoxItems by first letter in WPF using XAML?

末鹿安然 提交于 2019-12-04 22:54:58

问题


First, here is the previous post that deals with the ListBox AccountListBox data binding to my ObservableCollection<Account> Accounts from the AccountsCollection.cs class.

So now I have a binding object AccountsCollection and a DataTemplate named AccountTemplate for my ListBox defined in the resources:

<Window.Resources>
    <controller:AccountsWindowController x:Key="AccountsCollection" />
    <DataTemplate x:Key="AccountTemplate">
        <DockPanel>
            <Button Name="EditButton" 
                    DockPanel.Dock="Right" 
                    Margin="3 0 3 0" 
                    VerticalAlignment="Center" 
                    Content="Edit" />
            <Button Name="DeleteButton" 
                    DockPanel.Dock="Right" 
                    Margin="3 0 3 0" 
                    VerticalAlignment="Center" 
                    Content="Delete" />
            <TextBlock Name="AccountName" 
                       VerticalAlignment="Center" 
                       Text="{Binding Name}" 
                       TextWrapping="NoWrap"
                       TextTrimming="CharacterEllipsis" />
        </DockPanel>
    </DataTemplate>
<Window.Resources>

And here is the code related to the LisBox itself:

<ListBox Name="AccountsListBox" 
         Margin="12,38,12,41" 
         HorizontalContentAlignment="Stretch" 
         ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
         ItemsSource="{Binding Accounts, 
             Source={StaticResource ResourceKey=AccountsCollection}}" 
         ItemTemplate="{StaticResource ResourceKey=AccountTemplate}" 
         MouseDoubleClick="AccountsListBox_MouseDoubleClick">
</ListBox>

I want my list to be designed to group all accounts by starting letter and to show that letter in the list (Also I want to apply some design to that letter header). The final result should be something like this:

Thanks for all the help!

UPDATE: Here's the code with grouping successfully implemented.

<Window x:Class="Gui.Wpf.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:entities="clr-namespace:Entities.Accounts;assembly=Entities" 
    xmlns:contollers="clr-namespace:Gui.Wpf.Controllers" 
    xmlns:converters="clr-namespace:Gui.Wpf.Converters" 
    xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase" 
    Title="MainWindow" 
    Width="525" 
    Height="350" >

<Window.Resources>

    <!-- Main window controller -->
    <contollers:MainWindowController 
        x:Key="MainWindowController" />

    <!-- Converter for first letter extraction from the account name -->
    <converters:FirstLetterConverter x:Key="FirstLetterConv" />

    <!-- View source for the AccountsListBox -->
    <CollectionViewSource 
        x:Key="AccountsView" 
        Source="{Binding Accounts, Source={StaticResource ResourceKey=MainWindowController}}">

        <!-- Sorting -->
        <CollectionViewSource.SortDescriptions>
            <componentModel:SortDescription PropertyName="AccountName" />
        </CollectionViewSource.SortDescriptions>

        <!-- Grouping -->
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="AccountName" Converter="{StaticResource ResourceKey=FirstLetterConv}" />
        </CollectionViewSource.GroupDescriptions>

    </CollectionViewSource>

    <!-- Data template for the type Account -->
    <DataTemplate 
        DataType="{x:Type entities:Account}">
        <DockPanel>
            <Button 
                Name="DeleteButton" 
                DockPanel.Dock="Right" 
                Margin="3, 1, 3, 1" 
                VerticalAlignment="Center" 
                Content="Delete" />
            <Button 
                Name="EditButton" 
                DockPanel.Dock="Right" 
                Margin="3, 1, 3, 1" 
                VerticalAlignment="Center" 
                Content="Edit" />
            <TextBlock 
                Name="AccountNameTextBlock" 
                VerticalAlignment="Center" 
                Text="{Binding AccountName}" 
                TextWrapping="NoWrap" 
                TextTrimming="CharacterEllipsis" />
        </DockPanel>

    </DataTemplate>

    <!-- Data template for AccountListBox grouping -->
    <DataTemplate x:Key="GroupingHeader">
        <TextBlock Text="{Binding Path=Name}" Background="Black" Foreground="White" />
    </DataTemplate>

</Window.Resources>

<Grid>
    <ListBox 
        Name="AccountsListBox" 
        Width="300" 
        Height="200" 
        HorizontalAlignment="Center" 
        VerticalAlignment="Center" 
        ItemsSource="{Binding Source={StaticResource ResourceKey=AccountsView}}" 
        HorizontalContentAlignment="Stretch" >
        <ListBox.GroupStyle>
            <GroupStyle 
                HeaderTemplate="{StaticResource ResourceKey=GroupingHeader}" />
        </ListBox.GroupStyle>
    </ListBox>
</Grid>


回答1:


You can use a CollectionViewSource, and a converter to extract the first letter:

<local:FirstLetterConverter x:Key="firstLetterConverter" />

<CollectionViewSource x:Key="cvs" Source="{Binding Accounts, Source={StaticResource AccountsCollection}}">
    <CollectionViewSource.SortDescriptions>
        <scm:SortDescription PropertyName="AccountName" />
    </CollectionViewSource.SortDescriptions>
    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="AccountName" Converter="{StaticResource firstLetterConverter}" />
    </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

...

<ItemsControl ItemsSource="{Binding Source={StaticResource cvs}}">
    ...

Converter:

public class FirstLetterConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string s = value as string;
        if (s != null && s.Length > 0)
            return s.Substring(0, 1);
        return string.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

If you want to apply a style to the group, you can use the GroupStyle property:

  ...
  <ItemsControl.GroupStyle>
    <GroupStyle>
      <GroupStyle.HeaderTemplate>
        <DataTemplate>
          <TextBlock FontWeight="Bold" FontSize="15" Text="{Binding Path=Name}" />
        </DataTemplate>
      </GroupStyle.HeaderTemplate>
      <GroupStyle.ContainerStyle>
        <Style TargetType="{x:Type GroupItem}">
          <Setter Property="Background" Value="Gray" />
          <Setter Property="Foreground" Value="White" />
        </Style>
      </GroupStyle.ContainerStyle>
    </GroupStyle>
  </ItemsControl.GroupStyle>
  ...



回答2:


Here is an example of a solution that is very similar:

Firstly, we need to generate a better collection for your DataContext - here's an example that you could easily modify for your purposes -

public Window1()
{
    InitializeComponent();
    var s = new[] { "Dave", "Adam", "Jeny", "Nick", "James" };
    DataContext = s
        .Select(n => n[0])
        .Distinct()
        .ToDictionary(l => l.ToString(), l => s
            .Where(w => w
                .StartsWith(l.ToString())));
}

then we just need nested ItemsControls for the UI -

<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
  <DataTemplate>
    <StackPanel>
      <TextBlock Foreground="Red" Text="{Binding Key}" FontSize="12" Margin="5" />
      <ItemsControl ItemsSource="{Binding Value}">
        <ItemsControl.ItemTemplate>
          <DataTemplate>
            <StackPanel Orientation="Horizontal" Margin="5">
              <Button Content ="View" Margin="0,0,5,0" />
              <Button Content ="Delete"  Margin="0,0,5,0" />
              <TextBlock Text="{Binding}" />
            </StackPanel>
          </DataTemplate>
        </ItemsControl.ItemTemplate>
      </ItemsControl>
    </StackPanel>
  </DataTemplate>
</ItemsControl.ItemTemplate>

and we get this:



来源:https://stackoverflow.com/questions/4266311/how-to-group-listboxitems-by-first-letter-in-wpf-using-xaml

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!