简体   繁体   中英

WPF - How to correctly show text in a TextBlock binded with a string list

For a future professional project, I need to evaluate the WPF capabilities.

In this context, I created a small test project, which contains 1 string tree, and 1 image grid. I want that my image grid shows all the jpeg images contained inside a given directory, and for each image, to show the extracted file name below the image, without its path and extension.

Actually, my demo works correctly according to my goal, except for one point: I added each formatted file name to show inside a List collection, which I tried to bind with a TextBlock shown on the bottom of each images. However this formatted name isn't visible, instead I see the complete file name, as if the TextBlock extracted it directly from the Image object.

I tried to resolve this issue by myself, following several tutorials, nothing worked for me. I cannot figure out what I'm doing wrong. Can someone explain to me?

Here is my xaml file content

<Window x:Class="VirtualTrees.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:VirtualTrees"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="myHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
            <Setter Property="Visibility" Value="Collapsed" />
        </Style>
        <DataTemplate x:Key="itImageCell">
            <WrapPanel>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="100"/>
                        <RowDefinition Height="20"/>
                    </Grid.RowDefinitions>
                    <Image Width="120" Stretch="Uniform" Source="{Binding}"/>
                    <TextBlock Grid.Row="1" Width="120" Text="{Binding}" TextTrimming="CharacterEllipsis"/>
                </Grid>
            </WrapPanel>
        </DataTemplate>
        <local:ListToStringConverter x:Key="ListToStringConverter" />
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="400"/>
            <ColumnDefinition Width="400*"/>
        </Grid.ColumnDefinitions>
        <ListView Margin="10" Name="lvStringTree">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
                    <GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
                    <GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" />
                </GridView>
            </ListView.View>
        </ListView>
        <Grid x:Name="grImages" Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>
            <ListView Grid.Row="1" Name="lvImages" ItemsSource="{Binding Path=m_ImageList}" ItemTemplate="{StaticResource itImageCell}">
                <ListView.Background>
                    <ImageBrush/>
                </ListView.Background>
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid Columns="3" />
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
            </ListView>
            <TextBlock Name="tbImageName" Text="{Binding Path=m_ImageNames, Converter={StaticResource ResourceKey=ListToStringConverter}}" DataContext="{StaticResource itImageCell}" />
        </Grid>
    </Grid>
</Window>

And my c# code

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace VirtualTrees
{
    [ValueConversion(typeof(List<string>), typeof(string))]
    public class ListToStringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (targetType != typeof(string))
                throw new InvalidOperationException("The target must be a string");

            return string.Join(", ", ((List<string>)value).ToArray());
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public class User
        {
            public string Name { get; set; }
            public int    Age  { get; set; }
            public string Mail { get; set; }
        }

        List<ImageSource> m_ImageList    = new List<ImageSource>();
        List<string>      m_ImageNames   = new List<string>();
        string            m_RegexPattern = @"\\([\w ]+).(?:jpg|png)$";

        public MainWindow()
        {
            InitializeComponent();

            PopulateStringTree();
            PopulateImageGrid();
        }

        public void PopulateStringTree()
        {
            List<User> vstItems = new List<User>();

            for (ulong i = 0; i < 100000; ++i)
            {
                vstItems.Add(new User() { Name = "John Doe",  Age = 42, Mail = "john@doe-family.com" });
                vstItems.Add(new User() { Name = "Jane Doe",  Age = 39, Mail = "jane@doe-family.com" });
                vstItems.Add(new User() { Name = "Sammy Doe", Age = 7,  Mail = "sammy.doe@gmail.com" });
            }

            lvStringTree.ItemsSource = vstItems;
        }

        public void PopulateImageGrid()
        {
            // get jpeg image file list from target dir
            string       moviePosterPath = @"W:\Labo\WPF\VirtualTrees\VirtualTrees\Resources\Images";
            List<string> fileNames       = new List<string>(System.IO.Directory.EnumerateFiles(moviePosterPath, "*.jpg"));

            // iterate through files
            foreach (string fileName in fileNames)
            {
                // load image and add it to image list
                m_ImageList.Add(new BitmapImage(new Uri(fileName)));
                Console.WriteLine("filename " + fileName);

                // extract image file name and add it to name list
                Match regexMatch = Regex.Match(fileName.Trim(), m_RegexPattern);
                m_ImageNames.Add(regexMatch.Groups[1].Value);
                Console.WriteLine("Movie Name: " + regexMatch.Groups[1].Value);
            }

            // bind data to image grid
            lvImages.ItemsSource = m_ImageList;
        }
    }
}

Your DataTemplate is the origin of the error. You have to check the binding of the TextBlock . You are binding to the DataContext which is a BitmapSource . The TextBlock implicitly calls BitmapSource.ToString() to get the string representation of the type. BitmapSource has ToString() overriden to return the full file path. To fix this, you need to use a IValueConverter .

Modified DataTemplate . The TextBlock binding now uses a converter to convert the BitmapSource to the filename:

<DataTemplate x:Key="itImageCell">
  <WrapPanel>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="100" />
        <RowDefinition Height="20" />
      </Grid.RowDefinitions>
      <Image Width="120"
             Stretch="Uniform"
             Source="{Binding}" />
      <TextBlock Grid.Row="1"
                 Width="120"
                 Text="{Binding ., Converter={StaticResource BitmapSourceToFilenameConverter}}"
                 TextTrimming="CharacterEllipsis" />
    </Grid>
  </WrapPanel>
</DataTemplate>

The IValueConverter for the TextBlock binding to convert the BitmapSource to filename:

[ValueConversion(typeof(BitmapSource), typeof(string))]
public class  BitmapSourceToFilenameConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    if (value is BitmapSource bitmapSource)
      return bitmapSource.UriSource.AbsolutePath;

    return Binding.DoNothing;
  }

  public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    throw new NotImplementedException();
  }
}

A small mistake I realized in your code:

You are first setting a binding on the ListView :

<ListView Name="lvImages" ItemsSource="{Binding Path=m_ImageList}" />

and then you override (remove) it

// bind data to image grid
lvImages.ItemsSource = m_ImageList;

This is not a binding (the comment is not true).

You should make m_ImageList a ObservableCollection<ImageSource> instead of List . The ObservableCollection will automatically update the ListView when items are added, moved or removed. Then remove this line from your MainWindow class: lvImages.ItemsSource = m_ImageList;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM