Search code examples
wpfsilverlightdata-bindingivalueconverter

Binding ObservableCollection<> to a TextBox


I have data comming back from web service in the form of a ObservableCollection<string> I want to bind the collection to a read-only TextBox so that the user can select and copy the data to the clipboard.

To get the collection bound to the Text property of the TextBox I created IValueConverter which converts the collection to a text string. This seems to work except that it only works once, it is as if the binding does not recognize subsequent changes to the Observable collection. Here is a simple application that reproduces the problem, just to confirm the binding is working correctly I also bind to a `ListBox'

Is this because the Text binding simple does not handle the change events of the collection?

One option would of course be for me to handle the collection changes and propogate those to a Text property that the TextBox is bound to, which is fine, but I would like to understand why what seemed to me to be an obvious solutions is not working as expected.

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>

Code Behind

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

Solution

  • Is this because the Text binding simple does not handle the change events of the collection?

    Indeed. A binding updates only when its source property changes. If you change the TextLines property by setting a whole new ObservableCollection and implement INotifyPropertyChanged, your binding will work as expected. Adding new elements to the collection will have meaning only if it's bound to a property like ItemsControl.ItemsSource that listens to the collection changes.

    One option would of course be for me to handle the collection changes and propogate those to a Text property that the TextBox is bound to, which is fine.

    That would be another solution.