Search code examples
c#wpfxamlmvvmdatagrid

WPF datagrid where the header and number of columns is generated dynamically using MVVM


I am trying to create a generic DataGrid in my WPF application where the number of columns and the value of each of their headers is defined through a binding to the class property ResultsHeader, which is an array of strings.

Here is what I have so far :

<DataGrid Grid.Row="1" HorizontalAlignment="Left" ItemsSource="{Binding Results}" Margin="10" IsReadOnly="True" AutoGenerateColumns="False"
            Visibility="{Binding ShowResults, Converter={StaticResource BooleanToVisibilityConverter}}">
    <DataGrid.Columns>
        <DataGridTextColumn Width="auto" Binding="{Binding [0]}"
                            Header="{Binding ResultsHeader[0], RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
                            Visibility="{Binding ShowResultsColumns[0], Converter={StaticResource BooleanToVisibilityConverter},
                                         RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
        <DataGridTextColumn Width="auto" Binding="{Binding [1]}"
                            Header="{Binding ResultsHeader[1], RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
                            Visibility="{Binding ShowResultsColumns[1], Converter={StaticResource BooleanToVisibilityConverter},
                                         RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
        <DataGridTextColumn Width="auto" Binding="{Binding [2]}"
                            Header="{Binding ResultsHeader[2], RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
                            Visibility="{Binding ShowResultsColumns[2], Converter={StaticResource BooleanToVisibilityConverter},
                                         RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
        <DataGridTextColumn Width="auto" Binding="{Binding [3]}"
                            Header="{Binding ResultsHeader[3], RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
                            Visibility="{Binding ShowResultsColumns[3], Converter={StaticResource BooleanToVisibilityConverter},
                                         RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
    </DataGrid.Columns>
</DataGrid>

In which Results represents the content of the cells of the table, ShowResults is the boolean that controls whether or not the DataGrid is visible or collapsed, and ShowResultsColumns is an array of booleans that controls whether each of the individual columns is visible or collapsed. Here is how each of the variables and properties is coded within the ViewModel class :

private static readonly int maxNumberOfColumnsInResults = 4;

private ObservableCollection<string[]> _results;
public ObservableCollection<string[]> Results
{
    get { return _results; }
    set {
        _results = value;
        OnPropertyChanged(nameof(Results));
        OnPropertyChanged(nameof(ShowResults));
    }
}

public bool ShowResults
{
    get { return _results != null; }
}

private string[] _resultsHeader;
public string[] ResultsHeader
{
    get { return _resultsHeader; }
    set {
        _resultsHeader = value;
        OnPropertyChanged(nameof(ResultsHeader));
        OnPropertyChanged(nameof(ShowResultsColumns));
    }
}

public bool[] ShowResultsColumns
{
    get
    {
        return _resultsHeader == null
                    ? Enumerable.Repeat(false, maxNumberOfColumnsInResults).ToArray()
                    : Enumerable.Range(0, maxNumberOfColumnsInResults).Select(x => x < ResultsHeader.Length).ToArray();
    }
}

BooleanToVisibilityConverter and OnPropertyChanged seem to be correctly implemented based on their behaviors on other features of the application, and my variables are assigned the correct values. The binding works on Results and ShowResults fine, but not on ResultsHeader and ShowResultsColumns (all the columns are visible with an empty header regardless of the value of _resultsHeader). Is there something I am missing ? Should I change my approach ?

Thanks in advance.


Solution

  • Is there something I am missing ?

    Yes, the fact that a DataGridColumn has no DataContext that you can bind to.

    Should I change my approach ?

    You could use a binding proxy that captures the DataContext as explained here.