将ObservableCollection<>与TextBox绑定

9
我从Web服务中得到的数据是一个ObservableCollection<string>,我想将这个集合绑定到只读的TextBox上,以便用户可以选择并复制数据到剪贴板。
为了将集合绑定到TextBox的Text属性上,我创建了一个IValueConverter,将集合转换为文本字符串。这似乎有效,但它只能工作一次,就好像绑定不能识别ObservableCollection的后续更改。以下是一个简单的应用程序,重现了这个问题,仅仅为了确认绑定是否正确,我还绑定了`ListBox`。
这是因为Text绑定不能处理集合的更改事件吗?
当然,一个选项是我处理集合的更改并将其传播到TextBox绑定的Text属性,这很好,但我想知道为什么对我来说似乎显而易见的解决方案没有按预期工作。 XAML
<Window x:Class="WpfTextBoxBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfTextBoxBinding"
        Title="MainWindow" Height="331" Width="402">
  <StackPanel>
    <StackPanel.Resources>
      <local:EnumarableToTextConverter x:Key="EnumarableToTextConverter" />
    </StackPanel.Resources>
    <TextBox Text="{Binding TextLines, Mode=OneWay, Converter={StaticResource EnumarableToTextConverter}}" Height="100" />
    <ListBox ItemsSource="{Binding TextLines}" Height="100" />
    <Button Click="Button_Click" Content="Add Line" />
  </StackPanel >
</Window>

后台代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Globalization;

namespace WpfTextBoxBinding
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public ObservableCollection<string> TextLines {get;set;}

    public MainWindow()
    {
      DataContext = this;

      TextLines = new ObservableCollection<string>();

      // Add some initial data, this shows that the 
      // TextBox binding works the first time      
      TextLines.Add("First Line");

      InitializeComponent();      
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      TextLines.Add("Line :" + TextLines.Count);
    }
  }

  public class EnumarableToTextConverter : IValueConverter
  {
    public object Convert(
      object value, Type targetType, 
      object parameter, CultureInfo culture)
    {
      if (value is IEnumerable)
      {
        StringBuilder sb = new StringBuilder();
        foreach (var s in value as IEnumerable)
        {
          sb.AppendLine(s.ToString());
        }
        return sb.ToString();
      }
      return string.Empty;
    }

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

你可能需要触发一个属性更改事件来更新文本框。 - ChrisF
@ChrisF,谢谢。我希望通过绑定将更改通知传播到TextBox。 - Chris Taylor
3个回答

6

一种更加优雅的方法是在Text属性上使用MultiBinding,并绑定到集合的Count属性。这将在集合的Count更改时更新绑定,并根据您定义的MultiValueConverter更新Text。

<TextBox>
    <TextBox.Text>
        <MultiBinding Converter="{x:Static l:Converters.LogEntryCollectionToTextConverter}">
            <Binding Path="LogEntries" Mode="OneWay"/>
            <Binding Path="LogEntries.Count" Mode="OneWay" />
        </MultiBinding>
    </TextBox.Text>
</TextBox>

还有转换器:

public static class Converters
{
    public static LogEntryCollectionToTextConverter LogEntryCollectionToTextConverter = new LogEntryCollectionToTextConverter();
}

public class LogEntryCollectionToTextConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<LogEntry> logEntries = values[0] as ObservableCollection<LogEntry>;

        if (logEntries != null && logEntries.Count > 0)
            return logEntries.ToString();
        else
            return String.Empty;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

在我的使用情况下,我不允许TextBox更新其源(因此´Mode="OneWay"´),但如果需要,转换器的ConvertBack方法将处理它。


5
这是因为Text绑定只处理源属性的更改事件。如果您通过设置一个全新的ObservableCollection并实现INotifyPropertyChanged来更改TextLines属性,则绑定将按预期工作。仅当将其绑定到像ItemsControl.ItemsSource这样监听集合更改的属性时,向集合添加新元素才有意义。 另一种解决方案是手动处理集合更改并将其传播到文本框绑定的Text属性。

谢谢Julien,我希望我只是漏了什么,这样就可以工作了。 - Chris Taylor

0

更新下面的代码

  private void Button_Click(object sender, RoutedEventArgs e)
    {
        TextLines.Add("Line :" + TextLines.Count);
      BindingExpression be =  BindingOperations.GetBindingExpression(txtName, TextBox.TextProperty);
      be.UpdateTarget();
    } 

txtName是您的文本框名称。

MVVM方式

1- 在您的ViewModel中定义一个字符串类型的属性,并如下所示将此属性绑定到文本框文本属性,现在不需要ValueConverter。

public string TextLines {get;set;}

 <TextBox Text="{Binding TextLines, Mode=OneWay/> 

2- 我认为,你很可能是使用命令处理程序来处理按钮点击事件,比如你的命令是AddMoreLines。

所以在AddMoreLine命令处理程序中,在向你的ObservableCollection添加新对象后,创建一个StringBuilder并将你的Collection的所有内容附加到其中,并将字符串分配给步骤1中创建的属性。

3- 调用PropertyChanged处理程序。


谢谢。在实际应用中,更新是在视图模型中进行的,而视图模型对视图和绑定到属性的控件一无所知。因此,我不能直接应用这种方法。很抱歉,我的简单问题重现并没有展示出这种设计的方面。 - Chris Taylor
通常我这样做:从我的VM引发一个事件并在我的视图中处理,这有点像黑客,也会在视图和ViewModel之间创建绑定。但是有时候无法百分之百地遵循MVVM,我可以告诉你一个典型的例子,比如处理DataGrid单元格级别操作。但在您的情况下,如果您想要,有可能不违反MVVM,我可以向您展示如何实现。 - TalentTuner

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