WPF - 如何在与字符串列表绑定的 TextBlock 中正确显示文本

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

为了以后的专业项目,我需要评估一下WPF的能力。

在这种情况下,我创建了一个小测试项目,其中包含 1 个字符串树和 1 个图像网格。我希望我的图像网格显示给定目录中包含的所有 jpeg 图像,并且对于每个图像,在图像下方显示提取的文件名,不包含其路径和扩展名。

实际上,我的演示根据我的目标正确运行,除了一点:我添加了每个格式化文件名以显示在列表集合中,我试图将其与每个图像底部显示的 TextBlock 绑定。然而,这个格式化的名称是不可见的,而是我看到了完整的文件名,就好像 TextBlock 直接从 Image 对象中提取它一样。

我尝试自己解决这个问题,遵循了几个教程,但没有任何效果。我无法弄清楚我做错了什么。有人可以给我解释一下吗?

这是我的xaml文件内容

<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>

还有我的 C# 代码

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;
        }
    }
}

你的DataTemplate是错误的来源。您必须检查 TextBlock 的绑定。您正在绑定到 DataContext,这是一个 BitmapSourceTextBlock 隐式调用 BitmapSource.ToString() 以获取该类型的字符串表示形式。 BitmapSource 已将 ToString() 覆盖为 return 完整文件路径。要解决此问题,您需要使用 IValueConverter.

已修改 DataTemplateTextBlock 绑定现在使用转换器将 BitmapSource 转换为文件名:

<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>

TextBlock 绑定的 IValueConverter 将 BitmapSource 转换为文件名:

[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();
  }
}

我在你的代码中发现了一个小错误:

您首先在 ListView:

上设置绑定
<ListView Name="lvImages" ItemsSource="{Binding Path=m_ImageList}" />

然后您覆盖(删除)它

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

这不是绑定(评论不正确)。

您应该将 m_ImageList 设为 ObservableCollection<ImageSource> 而不是 List。添加、移动或删除项目时,ObservableCollection 将自动更新 ListView。然后从您的 MainWindow class 中删除此行:lvImages.ItemsSource = m_ImageList;