Search code examples
c#wpflistviewdata-binding

WPF ListView can not show custom user components


Recently I am trying to make a user chat program, which requires the use WPF ListView to show chat messages one by one. To make the maintenance easier, I decided to use custom components as a single chat message. However, when I try to bind my custom user components to the ListView, it will leave blank and show nothing.
I tried to check if I had bound the list correctly, but I could not find a problem. I also tried to search the google, but it seemed that there is no similar questions.
Here is my ListView code:

<ListView x:Name="listView_chat" ItemsSource="{Binding}" Margin="10,10,10,115">
    <ListView.ItemTemplate>
        <DataTemplate DataType="{x:Type local:ChatTextMessage}">
            <local:ChatTextMessage />
        </DataTemplate>
    </ListView.ItemTemplate>
    <ListView.View>
        <GridView>
            <GridView.ColumnHeaderContainerStyle>
                <Style TargetType="{x:Type GridViewColumnHeader}">
                    <Setter Property="Visibility" Value="Collapsed"/>
                </Style>
            </GridView.ColumnHeaderContainerStyle>
        </GridView>
    </ListView.View>
</ListView>

And here is my binding code:

private ObservableCollection<ChatTextMessage> messages { get; set; } = new ObservableCollection<ChatTextMessage>();
public PrivateChatPage()
{
    InitializeComponent();
    this.listView_chat.ItemsSource = messages;
    messages.Add(new ChatTextMessage("UserName", "User message", new BitmapImage()));
    this.listView_chat.Items.Refresh();
}

Just if you also want my custom components, here is my custom components:

<UserControl x:Class="Client_Window.ChatTextMessage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Client_Window"
             mc:Ignorable="d" 
             Height="Auto" Width="Auto">
    <Grid Background="#FF9C9090">
        <Image x:Name="image_userimage" Margin="10,10,390,90" HorizontalAlignment="Left" Width="50" Height="50" VerticalAlignment="Top"/>
        <Label x:Name="label_username" Content="UserName" HorizontalAlignment="Left" Height="25" Margin="70,10,0,0" VerticalAlignment="Top" Width="150" Background="Transparent" FontSize="13"/>
        <TextBlock x:Name="textBlock_user_message" Margin="70,40,10,0" TextWrapping="Wrap" MaxHeight="300" Text="User message" VerticalAlignment="Top" FontSize="15"/>
    </Grid>
</UserControl>

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;
using System.Xml.Linq;

namespace Client_Window
{
    /// <summary>
    /// ChatTextMessage.xaml 的互動邏輯
    /// </summary>
    public partial class ChatTextMessage : UserControl
    {
        public ChatTextMessage()
        {
            InitializeComponent();
        }
        public ChatTextMessage(string Username, string UserMessage, ImageSource UserImage)
        {
            InitializeComponent();
            this.Username = Username;
            this.UserMessage = UserMessage;
            this.UserImage = UserImage;
        }
        public string Username
        {
            get
            {
                return this.label_username.Content as string;
            }
            set
            {
                this.label_username.Content = value;
            }
        }
        public string UserMessage
        {
            get
            {
                return this.textBlock_user_message.Text;
            }
            set
            {
                this.textBlock_user_message.Text = value;
            }
        }
        public ImageSource UserImage
        {
            get
            {
                return this.image_userimage.Source;
            }
            set
            {
                this.image_userimage.Source = value;
            }
        }
    }
}

I wonder why it can not show my custom components correctly, and where I did wrong.


Solution

  • You have more problems:

    1. You are using the ListView wrong: either use it as list view or grid view.
    2. Your UserControl adds all child elements to a Grid without any layout proper arrangement. If your control is displayed all elements are rendered on top of each other: the TextBlock will overlay and hide the label and the image. Adding margins is not the solution. You must arrange children in a Grid using columns and rows. This way you can even create a responsive layout.
    3. Your UserControl won't display any data: WPF will only call the default constructor and not any overloads. You must always initialize custom controls with external data via data binding.
    4. You must not add controls to the ObservableCollection.
    5. Adding controls to the ListView and defining the same control as DataTemplate will not work. This is not how data templates work. You usually define a collection of data models. Those models are rendered by defining the DataTemplate. This way you can separate data and data presentation. See: Data Templating Overview.
    6. Calling this.listView_chat.Items.Refresh(); after modifying an ObservableCollection is always redundant. It will only negatively impact the rendering performance.

    You have to introduce 3 parts: the data model to hold the data (ChatMessage), the item container template (ChatMessageItem) tah describes how the data is rendered, and the data view (ListBox) that wires data model and data template together:

    ChatMessage.cs

    public class ChatMessage : INotifyPropertyChanged
    {
      public event PropertyChangedEventHandler? PropertyChanged;
    
      private string username;
      public int Username
      {
        get => this.username;
        set 
        { 
          this.username = value;
          OnPropertyChanged();
        }
      }
    
      private string textMessage;
      public string TextMessage
      {
        get => this.textMessage;
        set
        {
          this.textMessage = value;
          OnPropertyChanged();
        }
      }
    
      private BitmapImage icon;
      public string Icon
      {
        get => this.icon;
        set
        {
          this.icon = value;
          OnPropertyChanged();
        }
      }
    
      public ChatMessage(string username, string textMessage, BitmapImage icon)
      {
        this.Username = username;
        this.TextMessage = textMessage;
        this.Icon = icon;
      }
    
      protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    

    ChatMessageItem.xaml

    <UserControl x:Name="Root" 
                 Background="#FF9C9090">
      <Grid>
        <Grid.Columns>
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="*" />
        </Grid.Columns>
    
        <Image Grid.Column="0" 
               Source="{Binding ElementName=Root, Path=ProfileImage}"            
               HorizontalAlignment="Left" 
               Width="50" 
               Height="50" 
               VerticalAlignment="Top" />
    
        <TextBlock Grid.Column="1" 
                   Text="{Binding ElementName=Root, Path=Username}" 
                   Height="25" 
                   Width="150" 
                   FontSize="13" />
    
        <TextBlock Grid.Column="2" 
                   Text="{Binding ElementName=Root, Path=Message}" 
                   TextWrapping="Wrap" 
                   MaxHeight="300" 
                   FontSize="15" />
      </Grid>
    </UserControl>
    

    ChatMessageItem.xaml.cs

    partial class ChatMessageItem : UserControl
    {
      public string Username
      {
        get => (string)GetValue(UsernameProperty);
        set => SetValue(UsernameProperty, value);
      }
    
      public static readonly DependencyProperty UsernameProperty = DependencyProperty.Register(
        "Username",
        typeof(string),
        typeof(ChatMessageItem),
        new FrameworkPropertyMetadata(default));
    
      public string Message
      {
        get => (string)GetValue(MessageProperty);
        set => SetValue(MessageProperty, value);
      }
    
      public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof(string),
        typeof(ChatMessageItem),
        new FrameworkPropertyMetadata(default));
    
      public BitmapImage ProfileImage
      {
        get => (BitmapImage)GetValue(ProfileImageProperty);
        set => SetValue(ProfileImageProperty, value);
      }
    
      public static readonly DependencyProperty ProfileImageProperty = DependencyProperty.Register(
        "ProfileImage",
        typeof(BitmapImage),
        typeof(ChatMessageItem),
        new FrameworkPropertyMetadata(default));
    
      public ChatTextMessageItem()
      {
        InitializeComponent();
      }
    }
    

    MainWindow.xaml.cs

    partial class MainWindow : Window
    {
      public ObservableCollection<ChatMessage> ChatMessages { get; }
    
      public ChatTextMessageItem()
      {
        this.ChatMessages = new ObservableCollection<string> 
        {
          new ChatMessage("User1", "Hello there!", new BitmapImage()),
          new ChatMessage("User2", "Hi there!", new BitmapImage()),
        };
    
        InitializeComponent();
      }
    }
    

    MainWindow.xaml

    <Window x:Name="Root">
      <ListBox ItemsSource="{Binding ElementName="Root", Path=ChatMessages, Mode=OneTime}">
        <ListBox.ItemTemplate>
          <DataTemplate DataType="{x:Type local:ChatMessage}">
            <local:ChatTextMessage Username="{Binding Username}"
                                   Message={Binding TextMessage}"
                                   ProfileImage="{Binding Icon}" />
          </DataTemplate>
        </ListBox.ItemTemplate>
      </ListView>
    </Window>