.NET MAUI Chat ItemTemplateSelector
The RadChat
control exposes an ItemTemplateSelector
property which you can use to apply different templates to each Chat item depending on a specific condition.
Default ItemTemplateSelector
Any change in the appearance of the Chat items depends on the ChatItemTemplateSelector
and the containing templates and referenced styles. The default selector includes separate templates for the incoming and outgoing messages (so they're aligned to the left or right) and for single, first, middle, and last messages (in the case there area a few messages in a row)—this is needed to achieve the "balloon" look & feel of the messages. In addition, the TimeBreak
template is also assigned through the ItemTemplateSelector
.
Below you can find the default ItemTemplateSelector
, which you can use as a base for any further customizations to the way the messages look.
The default templates contain:
- a
RadBorder
control (used to achieve the rounded edges). - an
Image
control (used for the avatar image only for the single and first messages). - a
Label
for the text message itself.
The code snippet below contains the default templates and the accompanying styles:
<ResourceDictionary>
<Style x:Key="MessageImageStyle" TargetType="Image">
<Setter Property="WidthRequest" Value="24" />
<Setter Property="HeightRequest" Value="24" />
<Setter Property="VerticalOptions" Value="Start" />
</Style>
<Style x:Key="OutgoingMessageImageStyle" TargetType="Image" BasedOn="{StaticResource MessageImageStyle}">
<Setter Property="HorizontalOptions" Value="End" />
<Setter Property="Margin" Value="0, 4, 10, 4" />
</Style>
<Style x:Key="IncomingMessageImageStyle" TargetType="Image" BasedOn="{StaticResource MessageImageStyle}">
<Setter Property="HorizontalOptions" Value="Start" />
<Setter Property="Margin" Value="10, 4, 0, 4" />
</Style>
<Style x:Key="IncomingBorderStyle" TargetType="telerik:RadBorder">
<Setter Property="BackgroundColor" Value="#FFFFFF" />
<Setter Property="Margin" Value="45, 0, 70, 0" />
<Setter Property="HorizontalOptions" Value="Start" />
</Style>
<Style x:Key="OutgoingBorderStyle" TargetType="telerik:RadBorder">
<Setter Property="BackgroundColor" Value="#E0E0E0" />
<Setter Property="Margin" Value="70, 0, 45, 0" />
<Setter Property="HorizontalOptions" Value="End" />
</Style>
<Style x:Key="DefaultLabelStyle" TargetType="Label">
<Setter Property="Margin" Value="20, 5, 20, 5" />
<Setter Property="FontSize" Value="16" />
<Setter Property="LineBreakMode" Value="WordWrap" />
<Setter Property="TextColor" Value="Black" />
<Setter Property="HorizontalOptions" Value="Start" />
</Style>
<Style x:Key="OutgoingLabelStyle" TargetType="Label" BasedOn="{StaticResource DefaultLabelStyle}">
<Setter Property="HorizontalOptions" Value="End" />
</Style>
<DataTemplate x:Key="OutgoingSingleTemplate">
<Grid Padding="0, 2, 0, 10">
<Image Source="{Binding Author.Avatar}" Style="{StaticResource OutgoingMessageImageStyle}" />
<telerik:RadBorder Style="{StaticResource OutgoingBorderStyle}"
CornerRadius="7, 0, 7, 7" >
<Label Text="{Binding Text}" Style="{StaticResource OutgoingLabelStyle}" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<DataTemplate x:Key="OutgoingFirstTemplate">
<Grid Padding="0, 2, 0, 2">
<Image Source="{Binding Author.Avatar}"
Style="{StaticResource OutgoingMessageImageStyle}" />
<telerik:RadBorder Style="{StaticResource OutgoingBorderStyle}"
CornerRadius="7, 0, 0, 7" >
<Label Text="{Binding Text}" Style="{StaticResource OutgoingLabelStyle}" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<DataTemplate x:Key="OutgoingMiddleTemplate">
<Grid Padding="0, 2, 0, 2">
<telerik:RadBorder Style="{StaticResource OutgoingBorderStyle}"
CornerRadius="7, 0, 0, 7" >
<Label Text="{Binding Text}" Style="{StaticResource OutgoingLabelStyle}" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<DataTemplate x:Key="OutgoingLastTemplate">
<Grid Padding="0, 2, 0, 10">
<telerik:RadBorder Style="{StaticResource OutgoingBorderStyle}"
CornerRadius="7, 0, 7, 7" >
<Label Text="{Binding Text}" Style="{StaticResource OutgoingLabelStyle}" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<DataTemplate x:Key="IncomingSingleTemplate">
<Grid Padding="0, 2, 0, 10">
<Image Source="{Binding Author.Avatar}"
Style="{StaticResource IncomingMessageImageStyle}" />
<telerik:RadBorder Style="{StaticResource IncomingBorderStyle}"
CornerRadius="0, 7, 7, 7" >
<Label Text="{Binding Text}" Style="{StaticResource DefaultLabelStyle}" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<DataTemplate x:Key="IncomingFirstTemplate">
<Grid Padding="0, 2, 0, 2">
<Image Source="{Binding Author.Avatar}"
Style="{StaticResource IncomingMessageImageStyle}" />
<telerik:RadBorder Style="{StaticResource IncomingBorderStyle}"
CornerRadius="0, 7, 7, 0" >
<Label Text="{Binding Text}" Style="{StaticResource DefaultLabelStyle}" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<DataTemplate x:Key="IncomingMiddleTemplate">
<Grid Padding="0, 2, 0, 2">
<telerik:RadBorder Style="{StaticResource IncomingBorderStyle}"
CornerRadius="0, 7, 7, 0" >
<Label Text="{Binding Text}" Style="{StaticResource DefaultLabelStyle}" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<DataTemplate x:Key="IncomingLastTemplate">
<Grid Padding="0, 2, 0, 10">
<telerik:RadBorder Style="{StaticResource IncomingBorderStyle}"
CornerRadius="0, 7, 7, 7" >
<Label Text="{Binding Text}" Style="{StaticResource DefaultLabelStyle}" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<ControlTemplate x:Key="TimeBreakView_ControlTemplate">
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<BoxView BackgroundColor="{TemplateBinding Stroke}"
HeightRequest="{TemplateBinding StrokeThickness}"
VerticalOptions="Center" />
<Label Text="{TemplateBinding Text}"
TextColor="{TemplateBinding TextColor}"
FontSize="{TemplateBinding FontSize}"
FontFamily="{TemplateBinding FontFamily}"
FontAttributes="{TemplateBinding FontAttributes}"
Grid.Column="1"
VerticalOptions="Center" />
<BoxView BackgroundColor="{TemplateBinding Stroke}"
HeightRequest="{TemplateBinding StrokeThickness}"
Grid.Column="2"
VerticalOptions="Center" />
</Grid>
</ControlTemplate>
<DataTemplate x:Key="TimeBreakTemplate">
<telerik:TimeBreakView Text="{Binding Text}"
ControlTemplate="{StaticResource TimeBreakView_ControlTemplate}" />
</DataTemplate>
<telerik:ChatItemTemplateSelector x:Key="MyChatItemTemplateSelector"
IncomingFirstTextMessageTemplate="{StaticResource IncomingFirstTemplate}"
IncomingMiddleTextMessageTemplate="{StaticResource IncomingMiddleTemplate}"
IncomingSingleTextMessageTemplate="{StaticResource IncomingSingleTemplate}"
IncomingLastTextMessageTemplate="{StaticResource IncomingLastTemplate}"
OutgoingFirstTextMessageTemplate="{StaticResource OutgoingFirstTemplate}"
OutgoingMiddleTextMessageTemplate="{StaticResource OutgoingMiddleTemplate}"
OutgoingSingleTextMessageTemplate="{StaticResource OutgoingSingleTemplate}"
OutgoingLastTextMessageTemplate="{StaticResource OutgoingLastTemplate}"
TimeBreakTemplate="{StaticResource TimeBreakTemplate}" />
</ResourceDictionary>
You can apply any changes to the templates and then assign the template selector to the ItemTemplateSelector
property of the Chat control:
<telerik:RadChat x:Name="chat"
ItemTemplateSelector="{StaticResource MyChatItemTemplateSelector}" />
Custom ItemTemplateSelector
You can create a custom ChatItemTemplateSelector
to conditionally apply different message styles depending on any of the used Chat item properties.
The following example demonstrates how to create a custom ChatItemTemplateSelector
:
1. To apply a distinct style to the important messages, add the following ChatItem
class with a custom MessageCategory
property:
public enum MessageCategory
{
Important,
Normal
}
public class SimpleChatItem
{
public Author Author { get; set; }
public string Text { get; set; }
public MessageCategory Category { get; set; }
}
2. Add a few sample Items to the Chat's ItemsSource
:
public class ViewModel : NotifyPropertyChangedBase
{
private Author me;
public ViewModel()
{
this.Me = new Author() { Name = "human", Avatar = "sampleavatar.png" };
this.Bot = new Author() { Name = "Bot", Avatar = "samplebot.png" };
this.Items = new ObservableCollection<SimpleChatItem>();
// Simulate async data loading
Device.StartTimer(TimeSpan.FromMilliseconds(500), () =>
{
this.Items.Add(new SimpleChatItem { Author = this.Bot, Text = "Hello.", Category = MessageCategory.Normal });
this.Items.Add(new SimpleChatItem { Author = this.Bot, Text = "Here is what you need to know about our updated privacy policy: ...", Category = MessageCategory.Important });
return false;
});
}
public Author Me
{
get
{
return this.me;
}
set
{
if (this.me != value)
{
this.me = value;
this.OnPropertyChanged(nameof(this.Me));
}
}
}
public Author Bot { get; set; }
public IList<SimpleChatItem> Items { get; set; }
}
You need to supply an
ItemsConverter
as you're using custom items as demonstrated inside MVVM Support topic.
public class SimpleChatItemConverter : IChatItemConverter
{
public ChatItem ConvertToChatItem(object dataItem, ChatItemConverterContext context)
{
SimpleChatItem item = (SimpleChatItem)dataItem;
TextMessage textMessage = new TextMessage()
{
Data = dataItem,
Author = item.Author,
Text = item.Text
};
return textMessage;
}
public object ConvertToDataItem(object message, ChatItemConverterContext context)
{
ViewModel vm = (ViewModel)context.Chat.BindingContext;
return new SimpleChatItem { Author = vm.Me, Text = (string)message, Category = MessageCategory.Normal };
}
}
3. Create a CustomChatItemTemplateSelector
class that derives from the ChatItemTemplateSelector
:
public class CustomChatItemTemplateSelector : ChatItemTemplateSelector
{
public DataTemplate ImportantMessageTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
ChatItem chatItem = item as ChatItem;
var myItem = chatItem?.Data as SimpleChatItem;
if (myItem != null && myItem.Category == MessageCategory.Important)
{
return this.ImportantMessageTemplate;
}
return base.OnSelectTemplate(item, container);
}
}
4. Create the needed XAML resources:
<ResourceDictionary>
<local:SimpleChatItemConverter x:Key="SimpleChatItemConverter" />
<DataTemplate x:Key="ImportantMessageTemplate">
<Grid Margin="0, 2, 0, 10">
<Image Source="{Binding Author.Avatar}"
WidthRequest="{OnPlatform Default=24, WinUI=32}"
HeightRequest="{OnPlatform Default=24, WinUI=32}"
Margin="{OnPlatform Default=0, WinUI='10, 0, 0, 0', MacCatalyst='12, 0, 0, 0'}"
HorizontalOptions="Start" />
<telerik:RadBorder CornerRadius="0, 4, 4, 4"
Padding="8, 4"
Margin="{OnPlatform Default='28, 0, 0, 0', MacCatalyst='39, 0, 0, 0', WinUI='46, 0, 0, 0'}"
HorizontalOptions="Start"
BackgroundColor="#FFDDB1">
<Label Text="{Binding Text, StringFormat='⚠ {0}'}"
FontAttributes="Italic" />
</telerik:RadBorder>
</Grid>
</DataTemplate>
<local:CustomChatItemTemplateSelector x:Key="CustomChatItemTemplateSelector"
ImportantMessageTemplate="{StaticResource ImportantMessageTemplate}" />
</ResourceDictionary>
5. Set it to the Chat's ItemTemplateSelector
property:
<telerik:RadChat x:Name="chat"
Author="{Binding Me}"
ItemsSource="{Binding Items}"
ItemConverter="{StaticResource SimpleChatItemConverter}"
ItemTemplateSelector="{StaticResource CustomChatItemTemplateSelector}">
<telerik:RadChat.BindingContext>
<local:ViewModel />
</telerik:RadChat.BindingContext>
</telerik:RadChat>
The image below shows how the customized Chat control can look: