Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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:

alt text

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>

like image 641
Boris Avatar asked Nov 24 '10 11:11

Boris


2 Answers

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>
  ...
like image 87
Thomas Levesque Avatar answered Sep 23 '22 18:09

Thomas Levesque


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:

alt text

like image 2
Dean Chalk Avatar answered Sep 21 '22 18:09

Dean Chalk