Fernando Santiago
Fernando Santiago

Reputation: 2288

How to add different ListBox items WPF

I am implementing a chat application like this image: enter image description here

I started by creating a ListBox and setting a ListBox.ItemTemplate, but i can't figure out how can i control the ListBox to add an item with the layout as a message received or as send (just like Whatsapp).

Here is my code:

<ListBox Name="ChatListBox" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Background="#00FFFFFF" BorderBrush="{x:Null}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
                            <ListBox.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <StackPanel Background="#00FFFFFF"/>
                                </ItemsPanelTemplate>
                            </ListBox.ItemsPanel>
                            <ListBox.ItemContainerStyle>
                                <Style TargetType="{x:Type ListBoxItem}">
                                    <Setter Property="Focusable" Value="False"/>
                                </Style>
                            </ListBox.ItemContainerStyle>
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <!--If the user sends a msg-->
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="9*"/>
                                            <ColumnDefinition Width="*"/>
                                        </Grid.ColumnDefinitions>
                                        <Border Grid.Column="0" Margin="0" BorderThickness="1" BorderBrush="#9f9f9f" Background="#c4df9b" CornerRadius="10">
                                            <TextBlock Name="MsgText" Background="#c4df9b" Foreground="Black" TextAlignment="Center" TextWrapping="Wrap" Margin="5" Text="{Binding text}" FontSize="14"/>
                                        </Border>
                                        <Image Grid.Column="1" Source="Images/user.png" Margin="5" Height="{Binding ElementName=MsgText, Path=ActualHeight}"/>
                                        <!--
                                        If the user receives a msg
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="9*"/>
                                        </Grid.ColumnDefinitions>
                                        <Image Grid.Column="0" Source="Images/user.png" Margin="5" Height="{Binding ElementName=MsgText, Path=ActualHeight}"/>
                                        <Border Grid.Column="1" Margin="0" BorderThickness="1" BorderBrush="#9f9f9f" Background="#c4df9b" CornerRadius="10">
                                            <TextBlock Name="MsgText" Background="#c4df9b" Foreground="Black" TextAlignment="Center" TextWrapping="Wrap" Margin="5" Text="{Binding text}" FontSize="14"/>
                                        </Border>-->
                                    </Grid>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>

Here is my c# code:

List<ChatItem> chatItem = new List<ChatItem>();
chatItem.Add(new ChatItem() { text = "Hello...", isFromUser = false });
        chatItem.Add(new ChatItem() { text = "hi!", isFromUser = true });
        chatItem.Add(new ChatItem() { text = "this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test, this is a test", isFromUser = false });
        ChatListBox.ItemsSource = chatItem;

This is how the ListBox is redered:

enter image description here

Is there any way of adding an IF statement at the ListBox with WPF? or how can i control which ListBox.ItemTemplate to add.

Upvotes: 3

Views: 2428

Answers (4)

Bruno Joaquim
Bruno Joaquim

Reputation: 1473

You could use DataTemplateSelector to select the Template according with your data, here's an example.

For this example, I'll use messages, a client message and a server message:

public abstract class Message
{
    public string Content { get; set; }

    public override string ToString()
    {
        return Content;
    }
}

public class ServerMessage : Message
{

}

public class ClientMessage : Message
{

}

In this way, I can check the type of the object and apply certain template.

Let's define our templates and selector:

XAML:

    <DataTemplate x:Key="clientTemplate" >
        <TextBlock Text="{Binding Content}"
                   Foreground="Red"/>
    </DataTemplate>

    <DataTemplate x:Key="serverClient">
        <TextBlock Text="{Binding Content}"
                   Foreground="Green"/>
    </DataTemplate>

    <local:MessageTemplateSelector x:Key="messageSelector" 
                                   ServerTemplate="{StaticResource serverClient}"
                                   ClientTemplate="{StaticResource clientTemplate}"/>

DataTemplateSelector

public class MessageTemplateSelector : DataTemplateSelector
{
    public DataTemplate ClientTemplate { get; set; }

    public DataTemplate ServerTemplate { get; set; }

    public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
    {
        if (item.GetType() == typeof(ClientMessage))
            return ClientTemplate;

        return ServerTemplate;
    }
}

First of all, as you can see I create two DataTemplate properties on my Selector, which I set it by XAML, doing this is easy to retrieve the DataTemplate, and finally I just compare the item received by parameter, which is the binded object(Message), and check its type and return the template.

And that is it, it works like expected.

enter image description here

UPDATE: Let's supposed that you want Template your items based on their types, just like my example, you could this the same thing above without using TemplateSelector, you could define the DataType of your Template:

<Window.Resources>
    <DataTemplate DataType="{x:Type local:ClientMessage}">
        <TextBlock Text="{Binding Content}"
                   Foreground="Red"/>
    </DataTemplate>

    <DataTemplate DataType="{x:Type  local:ServerMessage}">
        <TextBlock Text="{Binding Content}"
                   Foreground="Green"/>
    </DataTemplate>
</Window.Resources>

Doing so, the Template will be selected automatically according the type of the object.

Upvotes: 1

Vivek Saurav
Vivek Saurav

Reputation: 2275

Maybe this will give you an idea

Xaml

<Window x:Class="Q1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Q1"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <DataTemplate x:Key="intDataTemplate">
        <TextBox Text="{Binding Path=.}" Width="80"/>
    </DataTemplate>
    <DataTemplate x:Key="stringDataTemplate">
        <TextBlock Text="{Binding Path=.}"/>
    </DataTemplate>
    <local:MyDataTemplateSelector IntDataTemplate="{StaticResource intDataTemplate}"
                                  StringDataTemplate="{StaticResource stringDataTemplate}"
                                  x:Key="myDataTemplateSelector"/>
</Window.Resources>
<Grid>
    <ListBox x:Name="myListBox" ItemsSource="{Binding}"
             ItemTemplateSelector="{StaticResource myDataTemplateSelector}">

    </ListBox>
</Grid>

Code-Behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Q1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            List<System.Object> myList = new List<object>();
            myList.Add(1);
            myList.Add("Alpha");
            myList.Add(2);
            myList.Add("Beta");
            myList.Add(3);
            myList.Add("Gamma");
            myListBox.DataContext = myList;
        }
    }

}

DataTemplateSelector

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Q1
{
    public class MyDataTemplateSelector : System.Windows.Controls.DataTemplateSelector
    {
        public System.Windows.DataTemplate IntDataTemplate { get; set; }
        public System.Windows.DataTemplate StringDataTemplate { get; set; }
        public MyDataTemplateSelector()
        {
            IntDataTemplate = new System.Windows.DataTemplate();
            StringDataTemplate = new System.Windows.DataTemplate();
        }
        public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
        {
            if (item is Int32)
            {
                return IntDataTemplate;
            }
            else
            {
                return StringDataTemplate;
            }
        }
    }
}

Upvotes: 2

Mark Feldman
Mark Feldman

Reputation: 16119

Another option is to simply use a data trigger:

<DataTemplate x:Key="ToTemplate">
    ... etc ...
</DataTemplate>

<DataTemplate x:Key="FromTemplate">
    ... etc ...
</DataTemplate>

<Style TargetType="ListBoxItem">
    <Style.Triggers>
        <DataTrigger Binding="{Binding isFromUser}" Value="false">
            <Setter Property="Template" Value="{StaticResource ToTemplate}" />
        </DataTrigger>
        <DataTrigger Binding="{Binding isFromUser}" Value="true">
            <Setter Property="Template" Value="{StaticResource FromTemplate}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

Upvotes: 2

Benjamin Baumann
Benjamin Baumann

Reputation: 4065

If your messages are of the same class you could use itemsTemplateSelector as in http://codingbandit.com/blog/?p=8

if there are different classes you should just use the datatemplate datatype property as in Conditional List itemtemplate or datatemplate in WPF

Upvotes: 3

Related Questions