Linking WPF text block to list <string>

Does anyone know if there is an easy way to bind a text block to a list box. What I have done so far is create a list and link it to a list, and then I have a template inside the list that uses a single text block.

what I really would like to do is just bind the List to the text block and display all the lines.

There was a "Lines" property in Winforms that I could just throw in the List, but I don't see it in a WPF or TextBox textbox.

Any ideas?

Am I missing something simple?

Here's the code

<UserControl x:Class="QSTClient.Infrastructure.Library.Views.WorkItemLogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Width="500" Height="400">
<StackPanel>
    <ListView ItemsSource="{Binding Path=Logs}" >
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Log Message">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding}"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</StackPanel>

      

and the WorkItem class

public class WorkItem
{
    public string Name { get; set; }
    public string Description { get; set; }
    public string CurrentLog { get; private set; }
    public string CurrentStatus { get; private set; }
    public WorkItemStatus Status { get; set; }
    public ThreadSafeObservableCollection<string> Logs{get;private set;}

      

I am using Prism to create a control and put it in WindowRegion

        WorkItemLogView newView = container.Resolve<WorkItemLogView>();
        newView.DataContext = workItem;
        regionManager.Regions["ShellWindowRegion"].Add(newView);

      

thank

+10


source to share


4 answers


Convert your list to a single line with "\ r \ n" as a separator between them. and bind that to a TextBlock. Make sure the TextBlock is not constrained by its height, so that it can grow based on the number of lines. I would implement this as a Value to XAML Binding Converter that converts a list of strings to a single string with a new string appended between

<TextBlock Text="{Binding Path=Logs,Converter={StaticResource ListToStringConverter}}"/>

      



ListToStringConverter will look like this:

[ValueConversion(typeof(List<string>), typeof(string))]
public class ListToStringConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (targetType != typeof(string))
            throw new InvalidOperationException("The target must be a String");

        return String.Join(", ", ((List<string>)value).ToArray());
    }

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

      

+28


source


If you use the converter it works perfect the first time, but if one or more logs are logged, there is no update to your binding because the converter only works the first time. all controls that do not control items do not subscribe to the event changed in the list!

here is a little code for this scenario

using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace BindListToTextBlock
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    private WorkItem workItem;

    public MainWindow() {
      this.WorkItems = new ObservableCollection<WorkItem>();
      this.DataContext = this;
      this.InitializeComponent();
    }

    public class WorkItem
    {
      public WorkItem() {
        this.Logs = new ObservableCollection<string>();
      }

      public string Name { get; set; }
      public ObservableCollection<string> Logs { get; private set; }
    }

    public ObservableCollection<WorkItem> WorkItems { get; set; }

    private void Button_Click(object sender, RoutedEventArgs e) {
      this.workItem = new WorkItem() {Name = string.Format("new item at {0}", DateTime.Now)};
      this.workItem.Logs.Add("first log");
      this.WorkItems.Add(this.workItem);
    }

    private void Button_Click_1(object sender, RoutedEventArgs e) {
      if (this.workItem != null) {
        this.workItem.Logs.Add(string.Format("more log {0}", DateTime.Now));
      }
    }
  }
}

      

xaml

<Window x:Class="BindListToTextBlock.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:BindListToTextBlock="clr-namespace:BindListToTextBlock"
        Title="MainWindow"
        Height="350"
        Width="525">
  <Grid>
    <Grid.Resources>
      <BindListToTextBlock:ListToStringConverter x:Key="ListToStringConverter" />
    </Grid.Resources>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition />
    </Grid.RowDefinitions>
    <Button Grid.Row="0"
            Content="Add item..."
            Click="Button_Click" />
    <Button Grid.Row="1"
            Content="Add some log to last item"
            Click="Button_Click_1" />
    <ListView Grid.Row="2"
              ItemsSource="{Binding Path=WorkItems}">
      <ListView.View>
        <GridView>
          <GridViewColumn Header="Name">
            <GridViewColumn.CellTemplate>
              <DataTemplate>
                <TextBlock Text="{Binding Path=Name}" />
              </DataTemplate>
            </GridViewColumn.CellTemplate>
          </GridViewColumn>
          <GridViewColumn Header="Log Message">
            <GridViewColumn.CellTemplate>
              <DataTemplate>
                <TextBlock Text="{Binding Path=Logs, Converter={StaticResource ListToStringConverter}}" />
              </DataTemplate>
            </GridViewColumn.CellTemplate>
          </GridViewColumn>
        </GridView>
      </ListView.View>
    </ListView>
  </Grid>
</Window>

      

converter

using System;
using System.Collections;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;

namespace BindListToTextBlock
{
  public class ListToStringConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
      if (value is IEnumerable) {
        return string.Join(Environment.NewLine, ((IEnumerable)value).OfType<string>().ToArray());
      }
      return "no messages yet";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
      return DependencyProperty.UnsetValue;
    }
  }
}

      



EDIT

here is a quick solution for scrolling updates (this can be done with the attached property too)

public class CustomTextBlock : TextBlock, INotifyPropertyChanged
{
  public static readonly DependencyProperty ListToBindProperty =
    DependencyProperty.Register("ListToBind", typeof(IBindingList), typeof(CustomTextBlock), new PropertyMetadata(null, ListToBindPropertyChangedCallback));

  private static void ListToBindPropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e)
  {
    var customTextBlock = o as CustomTextBlock;
    if (customTextBlock != null && e.NewValue != e.OldValue) {
      var oldList = e.OldValue as IBindingList;
      if (oldList != null) {
        oldList.ListChanged -= customTextBlock.BindingListChanged;
      }
      var newList = e.NewValue as IBindingList;
      if (newList != null) {
        newList.ListChanged += customTextBlock.BindingListChanged;
      }
    }
  }

  private void BindingListChanged(object sender, ListChangedEventArgs e)
  {
    this.RaisePropertyChanged("ListToBind");
  }

  public IBindingList ListToBind
  {
    get { return (IBindingList)this.GetValue(ListToBindProperty); }
    set { this.SetValue(ListToBindProperty, value); }
  }

  private void RaisePropertyChanged(string propName)
  {
    var eh = this.PropertyChanged;
    if (eh != null) {
      eh(this, new PropertyChangedEventArgs(propName));
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
}

      

used here for CustomTextBlock

(not tested)

<TextBlock Text="{Binding Path=ListToBind, RelativeSource=Self, Converter={StaticResource ListToStringConverter}}"
           ListToBind={Binding Path=Logs} />

      

@Fueled hope this helps

+2


source


I shamelessly posted a link to my answer to a very similar question: Binding ObservableCollection <gt; in the TextBox .

Like punker76, if you bind your text to a collection, it will update when you set the collection, but not when the collection changes. This link demonstrates an alternative to punker76's solution (the trick is multi-connectivity with collection counting).

+1


source


For a concatenated set of objects:

    /// <summary>Convertisseur pour concatรฉner des objets.</summary>
[ValueConversion(typeof(IEnumerable<object>), typeof(object))]
public class ConvListToString : IValueConverter {
    /// <summary>Convertisseur pour le Get.</summary>
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        return String.Join(", ", ((IEnumerable<object>)value).ToArray());
    }
    /// <summary>Convertisseur inverse, pour le Set (Binding).</summary>
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        throw new NotImplementedException();
    }
}

      

Juste is thinking of bypassing the ToString () of your object.

0


source







All Articles