Home > Back-end >  Property MyObject[] display in DataGrid
Property MyObject[] display in DataGrid

Time:03-14

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.

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

CodePudding user response:

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

CodePudding user response:

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.

  • Related