【问题标题】:Binding ObservableCollection<> to a TextBox将 ObservableCollection<> 绑定到 TextBox
【发布时间】:2011-05-20 04:27:36
【问题描述】:

我有来自网络服务以ObservableCollection&lt;string&gt; 形式返回的数据我想将集合绑定到只读TextBox,以便用户可以选择数据并将其复制到剪贴板。

为了将集合绑定到 TextBox 的 Text 属性,我创建了 IValueConverter,它将集合转换为文本字符串。这似乎有效,只是它只有效一次,就好像绑定无法识别对 Observable 集合的后续更改。这是一个重现问题的简单应用程序,只是为了确认绑定工作正常,我还绑定到一个“ListBox”

这是不是因为 Text binding simple 不处理集合的 change 事件?

我当然可以选择一个选项来处理集合更改并将这些更改传播到 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,谢谢。我希望更改通知将通过绑定传播到 TextBox。

标签: wpf silverlight data-binding ivalueconverter


【解决方案1】:

实现这一点的更优雅的方法是在 Text 属性上使用 MultiBinding 并绑定到 Collection 的 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 方法会处理它。

【讨论】:

    【解决方案2】:

    这是因为文本绑定 简单不处理更改 集合事件?

    确实如此。绑定仅在其源 property 更改时更新。如果您通过设置一个全新的ObservableCollection 来更改TextLines 属性并实现INotifyPropertyChanged,您的绑定将按预期工作。仅当将新元素绑定到诸如 ItemsControl.ItemsSource 之类的侦听集合更改的属性时,向集合添加新元素才有意义。

    一个选择当然是我 处理集合更改和 将它们传播到 Text 属性 TextBox 绑定到的,即 很好。

    那将是另一种解决方案。

    【讨论】:

    • 谢谢朱利安,我希望我遗漏了一些东西,这会起作用。
    【解决方案3】:

    更新以下代码

      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 Command Handler 中,在您的 OBservrableCollection 添加一个新对象后,创建一个 StringBuilder 并附加您的 Collection 的所有内容并将字符串分配给在步骤 1 中创建的属性。

    3- 调用 PropertyChanged 处理程序。

    【讨论】:

    • 谢谢。在实际应用中,更新发生在视图模型中,视图模型不知道视图以及哪些控件绑定到属性。因此,我不能直接应用这种方法。抱歉,我对问题的简单再现没有显示设计的这一面。
    • 通常我这样做的方式是从我的虚拟机引发一个事件并在我的视图上处理它的一种黑客攻击并在我的视图和 ViewModel 之间创建绑定但有时它不可能 100% 兼容MVVM 我可以告诉你一个经典的例子,比如处理 DataGrid 单元级别的操作。但是在您的情况下,有可能不破坏 MVVM ,如果您愿意,我可以向您展示如何做到这一点。
    猜你喜欢
    • 2011-04-10
    • 2013-09-07
    • 2019-02-24
    • 2013-03-03
    • 2012-05-26
    • 2018-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多