2

I am trying to bind a TextBlock to items in a ObservableCollection. TextBlock values should be generated up to elements from collection. Count of elements in collection is between 0 and 7 (if it helps). MyClass implemented INotifyPropertyChanged. It should be directly TextBlock, not ListBox. How can I do it? Thanks!
Update: The problem is that I don't know previously the number of elements in collection. I know that it is better to use ListBox or ListView in this case, but it's important to make it in TextBlock or Label

For example:

  1. ObservableCollection contains elements 0, 1, 2.
    TextBlock should contains following "Values: 0, 1, 2"

  2. ObservableCollection contains elements 0, 1.
    TextBlock should contains following "Values: 0, 1"

    <TextBlock>
           <Run Text="Values: "/>
           <Run Text="{Binding Values}" />                 
    </TextBlock>
    

ObservableCollection<int> values = new ObservableCollection<int>();
        public ObservableCollection<int> Values
        {
            get => values;
            set
            {
                values = value;
                OnPropertyChanged();
            }
        }
Mykyta Shvets
  • 137
  • 1
  • 12
  • Possible duplicate of [How to bind multiple values to a single WPF TextBlock?](https://stackoverflow.com/questions/2552853/how-to-bind-multiple-values-to-a-single-wpf-textblock) – MikeT Mar 20 '18 at 11:14
  • @MikeT I don't think it's a duplicate of provided link. The question is about binding several entries from ObservableCollection not several values from one single entry in collection. – Rekshino Mar 20 '18 at 11:28

3 Answers3

1

Use a Converter that concat those strings:

public class StringsCollectionConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return null;
        return string.Join("\n", value as ObservableCollection<string>);
    }

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

Xaml

<Window.Resources>
    <local:StringsCollectionConverter x:Key="StringsCollectionConverter"/>
</Window.Resources> 
<Grid>
    <TextBlock Text="{Binding TextBlockCollection, Converter={StaticResource StringsCollectionConverter}}"></TextBlock>
</Grid>
SamTh3D3v
  • 9,854
  • 3
  • 31
  • 47
  • The problem is, that if collection be changed, the Text will be not updated. – Rekshino Mar 20 '18 at 11:52
  • 1
    Yes It will, make sure to use TwoWay Binding, Implement INotifyPropertyChanged Interface and call OnPropertyChanged("TextBlockCollection"); after the collection Is Changed – SamTh3D3v Mar 20 '18 at 11:58
  • I mean that if the same collection have one more item. – Rekshino Mar 20 '18 at 11:59
  • I am not following ? – SamTh3D3v Mar 20 '18 at 12:00
  • Will Text be updated, if new items will be added to the collection? – Rekshino Mar 20 '18 at 12:10
  • Even if you call OnPropertyChanged("TextBlockCollection", if collection changed __and collection remains the same object__, the bound property will be not updated. – Rekshino Mar 20 '18 at 12:21
  • No, Calling OnPropertyChanged will force the TextBlock To rerender It self – SamTh3D3v Mar 20 '18 at 12:34
  • If you want to check that just try it out, add a button or something! `private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { TextBlockCollection[0] = "Updated"; OnPropertyChanged("TextBlockCollection"); }` And pls don't downvote unless you are sure that the answer is totally worthless ! – SamTh3D3v Mar 20 '18 at 12:37
  • 1
    You are right! My apologizes! I undo my down vote(but you need probably change you answer for it works) – Rekshino Mar 20 '18 at 13:03
  • Disadvantage is, that you have to put OnPropertyChanged("TextBlockCollection") everywhere you modify the collection. – Rekshino Mar 20 '18 at 13:48
  • I agree, after all this is a working solution, a fancier one would be to handle the CollectionChanged event! – SamTh3D3v Mar 20 '18 at 14:10
  • To make it more clear it would be good to paste an example with adding a value and calling OnPropertyChanged. – Rekshino Mar 20 '18 at 14:13
0

You have to bind to the collection using a converter.
The problem is to get value updated on collection changed(here I mean not setting Values to new collection, but adding/removing items to/from the collection).
To implement updating on add/remove you have to use MultiBinding with one of bindings to the ObservableCollection.Count, so if the count be changed, then the bound property will be updated.

<Window.Resources>
    <local:MultValConverter x:Key="multivalcnv"/>
</Window.Resources> 
<TextBlock>
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource multivalcnv}">
            <Binding Path="Values"/>
            <Binding Path="Values.Count"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

public class MultValConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length > 1 && values[0] is ICollection myCol)
        {
            var retVal = string.Empty;
            var firstelem = true;
            foreach (var item in myCol)
            {
                retVal += $"{(firstelem?string.Empty:", ")}{item}";
                firstelem = false;
            }

            return retVal;
        }
        else
            return Binding.DoNothing;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException("It's a one way converter.");
    }
}
Rekshino
  • 6,954
  • 2
  • 19
  • 44
0

create an additional string property, which will change every time when collection item[s] change:

public class Vm
{
    public Vm()
    { 
       // new collection assigned via property because property setter adds event handler
       Values = new ObservableCollection<int>();
    }

    ObservableCollection<int> values;
    public ObservableCollection<int> Values
    {
        get => values;
        set
        {
            if (values != null) values.CollectionChanged -= CollectionChangedHandler;
            values = value;
            if (values != null) values.CollectionChanged += CollectionChangedHandler;
            OnPropertyChanged();
        }
    }

    private void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
    {
        OnPropertyChanged("ValuesText");
    }

    public string ValuesText
    {
        get { return "Values: " + String.Join(", ", values);}
    }
}

and then bind to that property:

<TextBlock Text="{Binding ValuesText}"/>
ASh
  • 34,632
  • 9
  • 60
  • 82
  • It's definitely an option to handle all in ViewModel. The question is where does it better belong to (View or ViewModel)? – Rekshino Mar 20 '18 at 14:05
  • @Rekshino, VM. there is for example entire ReactiveUI framework, which uses such properties left and right: see [Output Properties](https://reactiveui.net/docs/handbook/view-models/), [another example](http://pasoft-sharereactiveui.readthedocs.io/en/stable/basics/to-property/) – ASh Mar 20 '18 at 16:04