Binding ObservableCollection<> to a TextBox

16,395

Solution 1

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.

Solution 2

A slightly more elegant way to achieve that is to use MultiBinding on the Text property and bind to the Collection's Count property. This will update the binding every time the collection's Count changes and update the Text according to a MultiValueConverter you define.

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

And the converter:

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

In my use case, I don't allow the TextBox to update its source (hence the ´Mode="OneWay"´), but if need be the Converter's ConvertBack method would handle that.

Share:
16,395
Chris Taylor
Author by

Chris Taylor

I am here to look at questions being asked to help jog my memory and of course to get a feel for the things I should be looking into to update my skills. And who knows, maybe I even get to make a positive contribution...

Updated on July 25, 2022

Comments

  • Chris Taylor
    Chris Taylor almost 2 years

    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();
        }
      }
    }
    
  • Chris Taylor
    Chris Taylor over 13 years
    Thanks Julien, I was hoping that I was missing something and this would just work.
  • Chris Taylor
    Chris Taylor over 13 years
    thank you. In the actual application the update is occuring in the View Model, and the View Model has no knowledge of the view and which controls are bound to the properties. I therefore cannot directly apply this approach. Sorry my simple reproduction of the problem did not show this side of the design.
  • TalentTuner
    TalentTuner over 13 years
    normally i do it a way that i raise a event from my vm and handel on my view its kind of hack and creates a binding between my view and ViewModel BUT sometime its not posible to 100% complint with MVVM and i can tell you one classic example like handling DataGrid cell level operations. BUT in your case there is a possibility not to break the MVVM , if you want i can show you how to do it.