Search code examples
c#.netxamlgriddatatemplate

Grid as DataTemplate with unknown number of columns


I develop my App UI with XAML and in Background I run a C# program.

To make it bit easier to explain: Lets say the program shall Display Paramters in a ListView/Grid.

  1. Column 1 is the Parameter Name (string)
  2. Column 2 is the Parameter Value (string or string [,])

The Parameter Value can be a single value (displayed as Label) or a vector/matrix (displayed as Grid inside this cell). I have no idea how I can also add a other grid here with unknown column number, the number will be different for each paramter.

All the Parameters are in a List and loaded with a ItemSource + DisplayMemberBinding dynamically.

So what I Need: - A Grid in a DataTemplate - A Grid with unknown number of rows

<DataTemplate x:Key="labelTemplate">
  <Label Content="{Binding Path=Value}" Width="Auto"></Label>
</DataTemplate>

<DataTemplate x:Key="table2DTemplate">
</DataTemplate>

<local:ParameterTemplateSelector 
  x:Key="parameterTemplateSelector" 
  LabelTemplate="{StaticResource labelTemplate}" 
  Table2DTemplate ="{StaticResource table2DTemplate}"/>

MY Datatempaltes + TemplateSelector:

<ListView ItemsSource="{Binding Parameters}" Margin="10,156,10.286,10.429" x:Name="listBox1" FontSize="8" Background="White" Foreground="Black" BorderBrush="#FF60EFBB">
  <ListView.View>
    <GridView>
            <GridViewColumn 
                Header="Name" 
                Width="Auto"
                DisplayMemberBinding="{Binding Name}" />
            <GridViewColumn 
                Header="Value" 
                Width="Auto"
                CellTemplateSelector="{StaticResource parameterTemplateSelector}" />
            <GridViewColumn 
                Header="Unit" 
                Width="Auto"
    </GridView>
  </ListView.View>
</ListView>

Solution

  • Here's what you want to do. The value converter is necessary because you can't bind an ItemsControl (or any of its subclasses like ListBox, ListView, etc.) to a two-dimensional array. So we convert the 2D array into a List<List<String>> and display that in nested ItemsControls. The converter is included below.

    This could be simplified if you're willing to use List<List<String>> throughout your code as a substitute for string[,], but that may not be practical.

    <DataTemplate x:Key="table2DTemplate">
        <ItemsControl 
            ItemsSource="{Binding Value, Converter={StaticResource ListOfListsFrom2DArray}}"
            >
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ItemsControl ItemsSource="{Binding}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Label Content="{Binding}" />
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <UniformGrid
                                    Rows="1"
                                    />
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                    </ItemsControl>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </DataTemplate>
    

    Converter. There's not much here but a LINQ expression I stole from another StackOverflow answer.

    public class ListOfListsFrom2DArray : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var values = value as string[,];
    
            //  https://stackoverflow.com/a/37458182/424129
            var result = values.Cast<string>()
                // Use overloaded 'Select' and calculate row index.
                .Select((x, i) => new { x, index = i / values.GetLength(1) })
                // Group on Row index
                .GroupBy(x => x.index)
                // Create List for each group.  
                .Select(x => x.Select(s => s.x).ToList())
                .ToList();
    
            return result;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }