Grouping by property value using LINQ
-
21-12-2019 - |
Question
I wanted to create a LongListSelector
control in which values are grouped into many categories. Each item has got these properties:
Public Class SideItem
Private _Title As String
Public Property Title As String
Get
Return _Title
End Get
Set(value As String)
_Title = value
End Set
End Property
Private _Icon As System.Windows.Shapes.Path
Public Property Icon As System.Windows.Shapes.Path
Get
Return _Icon
End Get
Set(value As System.Windows.Shapes.Path)
_Icon = value
End Set
End Property
Private _Category As String
Public Property Category As String
Get
Return _Category
End Get
Set(value As String)
_Category = value
End Set
End Property
Private _NavigationUri As Uri
Public Property NavigationUri As Uri
Get
Return _NavigationUri
End Get
Set(value As Uri)
_NavigationUri = value
End Set
End Property
End Class
I'd like to group a list of SideItem
depending on the value of Category
property. I looked into several examples but I didn't manage to do it. This is my latest attempt (of several ones)
Me.Items = (From data In L
Group data By Key = New With {Key data.Category} Into Group
Select New SideItem With {
.Category = Key.Category, _
.Title = Group.Select(Function(itm As SideItem) itm.Title), _
.Icon = Group.Select(Function(itm As SideItem) itm.Icon), _
.NavigationUri = Group.Select(Function(itm As SideItem) itm.NavigationUri)
}).OfType(Of SideItem)()
UPDATE: SOLUTION
I used the code you can find in verdesrobert's answer voted as best, but I edited the AlphaKeyGroup
class in order to fit my needs.
Public Class AlphaKeyGroup(Of T)
Inherits List(Of T)
Public Delegate Function GetKeyDelegate(item As T) As String
Public Property Key() As String
Get
Return m_Key
End Get
Private Set(value As String)
m_Key = value
End Set
End Property
Private m_Key As String
Public Sub New(key__1 As String)
Key = key__1
End Sub
Private Shared Function CreateGroups(slg As List(Of String)) As List(Of AlphaKeyGroup(Of T))
Dim list As New List(Of AlphaKeyGroup(Of T))()
For Each key As String In slg
list.Add(New AlphaKeyGroup(Of T)(key))
Next
Return list
End Function
Public Shared Function CreateGroups(items As IEnumerable(Of T), ci As CultureInfo, getKey As GetKeyDelegate, sort As Boolean) As List(Of AlphaKeyGroup(Of T))
Dim headerList As New List(Of String)
For Each header As T In items
headerList.Add(getKey(header))
Next
Dim list As List(Of AlphaKeyGroup(Of T)) = CreateGroups(headerList)
For Each item As T In items
list(list.IndexOf(list.Where(Function(I As AlphaKeyGroup(Of T)) I.Key = getKey(item)).First)).Add(item)
Next
If sort Then
For Each group As AlphaKeyGroup(Of T) In list
group.Sort(Function(c0, c1)
Return ci.CompareInfo.Compare(getKey(c0), getKey(c1))
End Function)
Next
End If
Return list
End Function
End Class
Solution
First you need a class that provides a grouped list structure:
Public Class AlphaKeyGroup(Of T)
Inherits List(Of T)
Public Delegate Function GetKeyDelegate(item As T) As String
Public Property Key() As String
Get
Return m_Key
End Get
Private Set
m_Key = Value
End Set
End Property
Private m_Key As String
Public Sub New(key__1 As String)
Key = key__1
End Sub
Private Shared Function CreateGroups(slg As SortedLocaleGrouping) As List(Of AlphaKeyGroup(Of T))
Dim list As New List(Of AlphaKeyGroup(Of T))()
For Each key As String In slg.GroupDisplayNames
list.Add(New AlphaKeyGroup(Of T)(key))
Next
Return list
End Function
Public Shared Function CreateGroups(items As IEnumerable(Of T), ci As CultureInfo, getKey As GetKeyDelegate, sort As Boolean) As List(Of AlphaKeyGroup(Of T))
Dim slg As New SortedLocaleGrouping(ci)
Dim list As List(Of AlphaKeyGroup(Of T)) = CreateGroups(slg)
For Each item As T In items
Dim index As Integer = 0
index = slg.GetGroupIndex(getKey(item))
If index >= 0 AndAlso index < list.Count Then
list(index).Add(item)
End If
Next
If sort Then
For Each group As AlphaKeyGroup(Of T) In list
group.Sort(Function(c0, c1)
Return ci.CompareInfo.Compare(getKey(c0), getKey(c1))
End Function)
Next
End If
Return list
End Function
End Class
Using your class SideItem you should then create a list of SideItem:
// Your code to provide the list of items like this:
Dim source As New List(Of SideItem)()
//add data to the list or skip the creation and provide your data
Then group the list like this:
List<AlphaKeyGroup<SideItem>> DataSource = AlphaKeyGroup<SideItem>.CreateGroups(source,
System.Threading.Thread.CurrentThread.CurrentUICulture,
(SideItem s) => { return s.Category; }, true);
Bind to the LongListSelector ItemsSource:
Me.ItemsSource = DataSource;
LongListSelector XAML:
<phone:LongListSelector
x:Name="Me"
JumpListStyle="{StaticResource SideItemJumpListStyle}"
Background="Transparent"
GroupHeaderTemplate="{StaticResource SideItemGroupHeaderTemplate}"
ItemTemplate="{StaticResource SideItemItemTemplate}"
LayoutMode="List"
IsGroupingEnabled="true"
HideEmptyGroups ="true"/>
ItemTemplate XAML:
<DataTemplate x:Key="SideItemItemTemplate">
<StackPanel VerticalAlignment="Top">
<TextBlock FontWeight="Bold" Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
GroupHeaderTemplate XAML:
<DataTemplate x:Key="SideItemGroupHeaderTemplate">
<Border Background="Transparent" Padding="5">
<Border Background="{StaticResource PhoneAccentBrush}" BorderBrush="{StaticResource PhoneAccentBrush}" BorderThickness="2" Width="62"
Height="62" Margin="0,0,18,0" HorizontalAlignment="Left">
<TextBlock Text="{Binding Key}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="48" Padding="6"
FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
</Border>
</DataTemplate>
JumpListStyle XAML:
<phone:JumpListItemBackgroundConverter x:Key="BackgroundConverter"/>
<phone:JumpListItemForegroundConverter x:Key="ForegroundConverter"/>
<Style x:Key="SideItemJumpListStyle" TargetType="phone:LongListSelector">
<Setter Property="GridCellSize" Value="113,113"/>
<Setter Property="LayoutMode" Value="Grid" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource BackgroundConverter}}" Width="113" Height="113" Margin="6" >
<TextBlock Text="{Binding Key}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="48" Padding="6"
Foreground="{Binding Converter={StaticResource ForegroundConverter}}" VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
This should be enough.
Answer based on http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj244365(v=vs.105).aspx