I don't think you can do this purely through XAML, you're going to need to have code written somewhere to determine the relationship between each message, i.e., is the the author of message n - 1 the same as n?
I wrote a very quick example that resulted in the desired output. My example and the resulting code snippets are in no way production level code, but it should at least point you in the right direction.
To start, I first created a very simple object to represent the messages:
public class ChatMessage
{
public String Username { get; set; }
public String Message { get; set; }
public DateTime TimeStamp { get; set; }
public Boolean IsConcatenated { get; set; }
}
Next I derived a collection from ObservableCollection to handle determining relationships between each message as they're added:
public class ChatMessageCollection : ObservableCollection<ChatMessage>
{
protected override void InsertItem(int index, ChatMessage item)
{
if (index > 0)
item.IsConcatenated = (this[index - 1].Username == item.Username);
base.InsertItem(index, item);
}
}
This collection can now be exposed by your ViewModel and bound to the ListBox in your view.
There are many ways to display templated items in XAML. Based on your example interface, the only aspect of each item changing is the header so I figured it made the most sent to have each ListBoxItem display a HeaderedContentControl that would show the correct header based on the IsConcatenated value:
<ListBox ItemsSource="{Binding Path=Messages}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type m:ChatMessage}">
<HeaderedContentControl Header="{Binding}">
<HeaderedContentControl.HeaderTemplateSelector>
<m:ChatHeaderTemplateSelector />
</HeaderedContentControl.HeaderTemplateSelector>
<Label Content="{Binding Path=Message}" />
</HeaderedContentControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You'll notice that I am specifying a HeaderTemplateSelector which is responsible for choosing between one of two header templates:
public sealed class ChatHeaderTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var chatItem = item as ChatMessage;
if (chatItem.IsConcatenated)
return ((FrameworkElement)container).FindResource("CompactHeader") as DataTemplate;
return ((FrameworkElement)container).FindResource("FullHeader") as DataTemplate;
}
}
And finally, here are the two header templates which are defined as resources of the view:
<DataTemplate x:Key="FullHeader">
<Border
Background="Lavender"
BorderBrush="Purple"
BorderThickness="1"
CornerRadius="4"
Padding="2"
>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Username}" />
<TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right" Text="{Binding Path=TimeStamp, StringFormat='{}{0:HH:mm:ss}'}" />
</DockPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="CompactHeader">
<Border
Background="Lavender"
BorderBrush="Purple"
BorderThickness="1"
CornerRadius="4"
HorizontalAlignment="Right"
Padding="2"
>
<DockPanel>
<TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right" Text="{Binding Path=TimeStamp, StringFormat='{}{0:HH:mm:ss}'}" />
</DockPanel>
</Border>
</DataTemplate>
Again, this example is not perfect and is probably just one of many that works, but at least it should point you in the right direction.