I have a DataGrid
. The DataGrid
gets 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 DataGrid
only shows it's type:
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 object
s 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.