RHaguiuda
RHaguiuda

Reputation: 3259

WPF DataGrid dynamic columns based on object type and subtype

I'm building a WPF software to manage a stock of electronics components.

I have the following structure:

public class Part
{
    public string Manufacturer { get; set; }
    public string PartNumber { get; set; }
}
public class Resistor : Part
{
    public string Resistance { get; set;}
    public string Power { get; set;}
} 
public class Capacitor : Part
{
    public string Capacitance { get; set; }
    public string Voltage { get; set; }
}

Resistor and Capacitor are subtypes of Part.

I'm binding a DataGrid to an ObservableCollection<Part>, and using a ListCollectionView to add filtering and grouping functionality.

What I'm trying to accomplish is when I filter the ListCollectionView to get only the Resistor subtype, I want the DataGrid to update it's columns to show the properties of Resistor type and it's base class Part (so I would get the columns Manufacturer, PartNumber, Resistance and Power). At the same time, if I filter the ListCollectionView to get Capacitor subtype, the DataGrid should have the Capacitor class public properties and the Part public properties (Manufacturer, PartNumber, Capacitance and Voltage). Finally, If there's no filtering applied, the DataGrid would show only Part properties (Manufacturer and PartNumber).

I tried to use the AutoGenerateColumns=true but the DataGrid only shows Part properties, even if I filter the ListCollectionView to have only Resistors. I also tried to change the type of the ObservableCollection to dynamic and it didn't work either.

How can I change the DataGrid columns based on type of the object contained in the ObservableCollection?

Upvotes: 3

Views: 1159

Answers (2)

AQuirky
AQuirky

Reputation: 5236

Here is the solution using autogenerate. Simply implement the ITypedList interface on the observable collection...

public class Parts : ObservableCollection<Part>, ITypedList
{
    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
    {
        if(Count == 0)
        {
            return TypeDescriptor.GetProperties(typeof(Part));
        }
        else
        {
            PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this[0]);
            return pdc;
        }
    }

    public string GetListName(PropertyDescriptor[] listAccessors)
    {
        return "Parts";
    }
}

Upvotes: 1

AQuirky
AQuirky

Reputation: 5236

Here is one way to do this. Do not auto generate columns. Set up every column that is possible. Then bind the visibility of each column to a converter that determines whether the column is visible.

    <FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>
    <DataGrid x:Name="dataGrid" ItemsSource="{Binding PartCollection}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Manufacturer" Binding="{Binding Manufacturer}"/>
            <DataGridTextColumn Header="Part Number" Binding="{Binding PartNumber}" />
            <DataGridTextColumn Header="Power" Binding="{Binding Power}" Visibility="{Binding DataContext.PartCollection, Source={x:Reference dummyElement}, Converter={StaticResource ColumnVisibility}, ConverterParameter=Resistor}"/>
            <DataGridTextColumn Header="Resistance" Binding="{Binding Resistance}" Visibility="{Binding DataContext.PartCollection, Source={x:Reference dummyElement}, Converter={StaticResource ColumnVisibility}, ConverterParameter=Resistor}"/>
            <DataGridTextColumn Header="Capacitance" Binding="{Binding Capacitance}" Visibility="{Binding DataContext.PartCollection, Source={x:Reference dummyElement}, Converter={StaticResource ColumnVisibility}, ConverterParameter=Capacitor}"/>
            <DataGridTextColumn Header="Voltage" Binding="{Binding Voltage}" Visibility="{Binding DataContext.PartCollection, Source={x:Reference dummyElement}, Converter={StaticResource ColumnVisibility}, ConverterParameter=Capacitor}"/>
        </DataGrid.Columns>
    </DataGrid>

here is the static resource for the converter...

<Window.Resources>
    <local:ColumnVisibilityConverter x:Key="ColumnVisibility"/>
</Window.Resources>

here is the converter...

    public class ColumnVisibilityConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        ObservableCollection<Part> collection = value as ObservableCollection<Part>;
        string collectionType = parameter as string;
        if(collection != null && collectionType != null && collection.Count > 0)
        {
            switch(collectionType)
            {
                case "Resistor": return collection[0].GetType() == typeof(Resistor) ? Visibility.Visible : Visibility.Hidden;
                case "Capacitor": return collection[0].GetType() == typeof(Capacitor) ? Visibility.Visible : Visibility.Hidden;
                default: return Visibility.Hidden;
            }
        }
        return Visibility.Hidden;
    }

I struggled a bit with binding visibility to the data grid column. Found the answer here: Binding Visibility for DataGridColumn in WPF

Setting up the columns manually is best practice in my view. If you really want to autogenerate them, there is another way to do this. You can implement an ICustomTypeDescriptor on your collection to return the PropertyDescriptors for the properties of the derived type that is contained in the collection.

Upvotes: 4

Related Questions