There is a class, such as

public class MyClass { public object SomeObject { get; set; } } 

SomeObject may be an instance of any class (in fact, not exactly any, but this is irrelevant to the question)

There is a collection of instances of this class.

 public List<MyClass> Items { get; set; } 

This collection must be displayed on the form. There are no problems with this. Using TemplateSelector, I select the desired template for the list item, depending on the type of SomeObject . But now the task was to group items in a row into one container, if their SomeObject same type. Tell me how to do it correctly?

While I have this idea. Split the List<MyClass> collection into a collection of type List<List<MyClass>> and use it in the VM. But maybe someone has other ideas?

  • For this, use CollectionViewSource + GroupDescriptions . Here is the documentation . I will write an example later. - VladD
  • Ok, I was able to implement through GroupDescriptions only grouping by type: all objects of this type are grouped together, even if there are other types of objects between them. Probably, this is not exactly what you need :-( - VladD

1 answer 1

For the case when you need to group all instances of this type together, the following code is appropriate:

  <Window.Resources> <!-- конвертер, переводящий тип объекта в строку --> <local:TypeNameConverter x:Key="TypeNameConverter"/> <CollectionViewSource x:Key="SRC" Source="{Binding}"> <CollectionViewSource.GroupDescriptions> <!-- группируем по строке - имени типа --> <PropertyGroupDescription Converter="{StaticResource TypeNameConverter}"/> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> </Window.Resources> <Grid> <ListView ItemsSource="{Binding Source={StaticResource SRC}}" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListView.Resources> <!-- шаблоны для разных типов элемента --> <DataTemplate DataType="{x:Type local:MyClass}"> <TextBlock Text="{Binding Text}"/> </DataTemplate> <DataTemplate DataType="{x:Type local:MyBigClass}"> <TextBlock><Run Text="{Binding Text}"/><Run Text=" / "/><Run Text="{Binding MoreText}" Foreground="Gray"/></TextBlock> </DataTemplate> <DataTemplate DataType="{x:Type local:MyExpensiveClass}"> <TextBlock><Run Text="{Binding Text}"/><Run Text=": "/><Run Text="{Binding Price}" Foreground="Red"/></TextBlock> </DataTemplate> </ListView.Resources> <ListView.GroupStyle> <GroupStyle HidesIfEmpty="True"> <GroupStyle.HeaderTemplate> <DataTemplate> <TextBlock Name="Header"> <Run Text="{Binding ItemCount, Mode=OneWay}"/> <Run Text=" items:"/> </TextBlock> <!-- скрываем заголовок, если только один элемент --> <DataTemplate.Triggers> <DataTrigger Binding="{Binding ItemCount}" Value="1"> <Setter Property="Visibility" TargetName="Header" Value="Collapsed"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListView.GroupStyle> </ListView> </Grid> </Window> 
 class TypeNameConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value == null ? "null" : value.GetType().FullName; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 

Initial data:

 public class MyClass { public string Text { get; set; } } public class MyBigClass : MyClass { public string MoreText { get; set; } } public class MyExpensiveClass : MyClass { public decimal Price { get; set; } } DataContext = new MyClass[] { new MyClass { Text = "first" }, new MyBigClass { Text = "second", MoreText = "big" }, new MyBigClass { Text = "third", MoreText = "very big" }, new MyExpensiveClass { Text = "fourth", Price = 1.23M }, new MyExpensiveClass { Text = "fifth", Price = 9.99M }, new MyBigClass { Text = "sixth", MoreText = "not so big actually" }, new MyClass { Text = "seventh" }, new MyClass { Text = "eighth" }, new MyClass { Text = "ninth" }, new MyClass { Text = "ninth" }, new MyClass { Text = "ninth" } }; 

Result:

result 1


If you really need to group without changing the order, so that each type of group can be several times, you have to complicate things a little.

First, you need to add the int GroupId property to the base type MyClass :

 public class MyClass { public string Text { get; set; } public int GroupId { get; set; } } 

We don’t need the converter anymore, change the PropertyGroupDescription to this:

 <CollectionViewSource.GroupDescriptions> <PropertyGroupDescription PropertyName="GroupId"/> </CollectionViewSource.GroupDescriptions> 

Next, we start a procedure that places these same GroupId :

 void AssignGroupIds(IList<MyClass> items) { Type prevType = null; int id = -1; foreach (var item in items) { var type = item.GetType(); if (type != prevType) id++; prevType = type; item.GroupId = id; } } 

We apply this procedure before assignment:

 var items = new MyClass[] { new MyClass { Text = "first" }, new MyBigClass { Text = "second", MoreText = "big" }, new MyBigClass { Text = "third", MoreText = "very big" }, new MyExpensiveClass { Text = "fourth", Price = 1.23M }, new MyExpensiveClass { Text = "fifth", Price = 9.99M }, new MyBigClass { Text = "sixth", MoreText = "not so big actually" }, new MyClass { Text = "seventh" }, new MyClass { Text = "eighth" }, new MyClass { Text = "ninth" }, new MyClass { Text = "ninth" }, new MyClass { Text = "ninth" } }; AssignGroupIds(items); DataContext = items; 

We get:

result 2

Please note that first and sixth do not have a heading, since this is a single-element group (and we turned off this very heading for such groups).

  • Yes, it is necessary without changing the order. Looks like this is what you need. I will try later, thanks! ) - iRumba
  • @iRumba: Please! - VladD