'Property MyObject[] display in DataGrid

I have a DataGrid. The DataGridgets filled with MyObject and displays its properties automatically. For most properties this works fine.

As an example, MyObject looks like this:

public class Transaction
{
    public string Reference { get; set; }
    public double Amount { get; set; }
    public MainCategory ThisPropertyWorksFine { get; set; }
    public TransactionTag[] ThisPropertyDoesnt { get; set; }
}

Notice that MainCategory has an override .ToString(). It gets displayed properly in the DataGrid:

public override string ToString()
{
    return this.Name;
}

TransactionTag also has an override .ToString() method but the DataGridonly shows it's type:

A DataGrid with two columns where the second column bound to TransactionTag objects displays "TransactionTag[] Array" for each row instead of the names of the objects.

The data has been added as such:

List<Transaction> TransactionList = fillMyList();
this.ImportPreview.AutoGenerateColumns = true;
this.ImportPreview.ItemsSource = TransactionList;

How can I make it, so that the DataGrid shows the array content, for example separated by ,?



Solution 1:[1]

How can I make it, so that the DataGrid shows the array content, for example separated by ,?

You could create a custom collection type that overrides ToString():

public class FormattableCollection<T> : ReadOnlyCollection<T>
{
    public FormattableCollection(IList<T> list)
        : base(list) { }

    public override string ToString() =>
        string.Join(",", this);
}

...and change the type of your property:

public FormattableCollection<TransactionTag> BindToThisOne { get; }
...
BindToThisOne = new FormattableCollection<TransactionTag>(ThisPropertyDoesnt);

Solution 2:[2]

The ThisPropertyDoesnt property is of type TransactionTag[] which is an array of TransactionTag objects. You overrode the ToString method of TransactionTag, but not the array type, which you cannot change, see How to override ToString() in [] array? for reference.

You can create a custom value converter that concatenates the result of ToString() of a collection of objects using a predefined separator. The following converter exposes the separator as a property, which is useful to reuse a single instance with the same separator. You could alternatively pass it in as parameter.

public class ConcatenateToStringConverter : IValueConverter
{
   public string Separator { get; set; }

   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      return !(value is IEnumerable<object> objects) || Separator is null
         ? Binding.DoNothing
         : string.Join(Separator, objects);
   }

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

Without auto-generating columns, you would specify the converter in the Binding of the column.

<DataGrid ItemsSource="{Binding Transactions}" AutoGenerateColumns="False">
   <DataGrid.Resources>
      <local:ConcatenateToStringConverter x:Key="ConcatenateStringConverter" Separator=", "/>
   </DataGrid.Resources>
   <!-- ...other columns. -->
   <DataGrid.Columns>
      <DataGridTextColumn Header="Transaction Tags" Binding="{Binding ThisPropertyDoesnt, Converter={StaticResource ConcatenateStringConverter}}"/>
   </DataGrid.Columns>
</DataGrid>

With auto-generating columns, you would have to add a handler for the AutoGeneratingColumn event and adapt column generation. You would identify the target column, get its binding and add the converter.

<DataGrid ItemsSource="{Binding Transactions}" AutoGenerateColumns="True" AutoGeneratingColumn="OnAutoGeneratingColumn"/>
private void OnAutoGeneratingColumn(object? sender, DataGridAutoGeneratingColumnEventArgs e)
{
   if ( /* ...identify the target column here. */)
   {
      var dataGridTextColumn = (DataGridTextColumn)e.Column;
      var binding = (Binding)dataGridTextColumn.Binding;

      // It is favorable to reuse the same converter instance if you can.
      binding.Converter = new ConcatenateToStringConverter { Separator = ", " };
   }
}

Alternatively, you could create a separate model type that implements IEnumerable<TransactionTag> and override ToString there, but that is up to you and your requirements. For display only purposes, value converters are useful.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mm8
Solution 2