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:

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:

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).
CollectionViewSource+GroupDescriptions. Here is the documentation . I will write an example later. - VladD