将List<string>绑定到TextBox

4

在进行了20多年的Windows编程和两天的WPF后,我感觉自己什么也不懂 :-)

我的第一个WPF程序非常简单:您从资源管理器中拖放一些文件,并将它们的名称显示在TextBox控件中。 (对于ListBox,它可以正常工作,但这不是我想要的。当然,在Drop事件中手动添加行也可以 - 但我想学习绑定方式..)

所以我写了一个转换器,但不知何故它没有被使用(断点不会被命中),也没有显示任何内容。

这应该是一个小问题,或者我完全偏离了轨道。找到了许多类似的示例,我从中拼凑出了这个,但仍然无法使其正常工作。

(我可能不需要ConvertBack,但还是写下来了..)

这是转换器类:

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

namespace WpTest02
{
  public class ListToTextConverter : IValueConverter
  {
      public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            StringBuilder sb = new StringBuilder();
            foreach (string s in (List<string>)value) sb.AppendLine(s);
            return sb.ToString();
        }

      public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string[] lines = ((string)value).Split(new string[] { @"\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            return lines.ToList<String>();
        }
   }

}

MainWindow.xaml,我怀疑绑定问题出在这里:

<Window x:Class="WpTest02.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpTest02"
        Title="MainWindow" Height="350" Width="525"
        >
    <Window.Resources>
        <local:ListToTextConverter x:Key="converter1" />
    </Window.Resources>

    <Grid >
        <TextBox Name="tb_files" Margin="50,20,0,0"  AllowDrop="True" 
                 PreviewDragOver="tb_files_PreviewDragOver" Drop="tb_files_Drop" 
                 Text="{Binding Path=fileNames, Converter={StaticResource converter1} }"
                 />
    </Grid>
</Window>

Codebehind文件只需绑定数据属性和拖放代码,就可以正常工作。

using System; 
//etc ..

namespace WpTest02
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            fileNames = new List<string>();
        }


        public List<string> fileNames { get; set; }

        private void tb_files_Drop(object sender, DragEventArgs e)
        {
            var files = ((DataObject)e.Data).GetFileDropList();
            foreach (string s in files) fileNames.Add(s);

            // EDIT: this doesn't help ? Wrong!                     
            // EDIT: this is actually necessary! :                     
            tb_files.GetBindingExpression(TextBox.TextProperty).UpdateTarget();

            // this obviosly would work:
            //foreach (string s in files) tb_files.Text += s + "\r\n";
        }

        private void tb_files_PreviewDragOver(object sender, DragEventArgs e)
        {
            e.Handled = true;
        }

    }
}

注意:我已经编辑了最后一段代码,强调了UpdateTarget调用的重要性。

3个回答

4
要使Binding起作用,您需要将Window的DataContext分配给属性所在的实例,而在您的情况下,该实例是Window类本身。

因此,在构造函数中设置DataContext,它应该可以正常工作:

public MainWindow()
{
    InitializeComponent();
    fileNames = new List<string>();
    DataContext = this;
}

或者

您需要使用ElementName在绑定中明确解析XAML中的绑定:

<Window x:Name="myWindow">
  ....
  <TextBox Text="{Binding Path=fileNames, ElementName=myWindow,
                          Converter={StaticResource converter1}}"/>

为了让XAML方法生效,您需要在XAML加载之前即在InitializeComponent被调用之前初始化列表。
fileNames = new List<string>();
InitializeComponent();

谢谢提供这个替代方案。还有很多需要学习的地方。但是,仅仅将它添加到XAML中就会在转换器中引发空对象引用错误。(程序启动时该值为null)。我可以捕获它,但我想知道不同行为的原因是什么?我怀疑现在XAML被更早地调用了? - TaW
我已经更新了你的问题的答案。看看是否有帮助。 - Rohit Vats

3

必须设置文本框的DataContext以绑定数据,如下所示:

    public MainWindow()
    {
        InitializeComponent();
        fileNames = new List<string>();
        this.tb_files.DataContext = this;
    }

谢谢。实际上,这行代码在原始程序中已经存在。我发布的是一个简化版本。但现在我有一个可工作的版本,我将能够追踪原始代码中的问题。 - TaW

1

以下是通用模式,应该对您有效。如有任何问题,请随时与我联系。祝好运! ~Justin

<Window xmlns:vm="clr-namespace:YourProject.YourViewModelNamespace"
        xmlns:vc="clr-namespace:YourProject.YourValueConverterNamespace">
    <Window.Resources>
        <vc:YourValueConverter x:key="YourValueConverter" />
    </Window.Resources>
    <Window.DataContext>
        <vm:YourViewViewModel />
    </Window.DataContext>
    <TextBox Text="{Binding MyItems, Converter={StaticResource YourValueConverter}}"/>
</Window>

public class YourViewViewModel : ViewModelBase
{
    ObservableCollection<string> _myItems;
    ObservableCollection<string> MyItems
    {
        get { return _gameProfileListItems; }
        set { _gameProfileListItems = value; OnPropertyChanged("MyItems"); }
    }
    
    public void SetMyItems()
    {
        //    go and get your data here, transfer it to an observable collection
        //    and then assign it to this.GameProfileListItems (i would recommend writing a .ToObservableCollection() extension method for IEnumerable)
        this.MyItems = SomeManagerOrUtil.GetYourData().ToObservableCollection();
    }
}

public class YourView : Window
{
    YourViewViewModel ViewModel
    {
        { get return this.DataContext as YourViewViewModel; }
    }

    public void YourView()
    {
        InitializeComponent();

        InitializeViewModel();
    }

    void InitializeViewModel()
    {
        this.ViewModel.SetMyItems();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接