C# WPF DataGrid - Processing a Dictionary inside a class into column components in the datagrid

1.6k views Asked by At

I am trying to use WPF's Datagrid to display the content of an object. Usually, this is very simple when you are trying to display something when the class is like this :

class Employee
{
   public string Name { get; set; }
   public string Phone { get; set; }
   public bool Active { get; set; }
}

However, let say you have something more complex like this :

class Employee
{
   public Employee()
   {
     SecurityAccesses = new public Dictionary<string, bool>();
   }
   public string Name { get; set; }
   public string Phone { get; set; }
   public bool Active { get; set; }
   public Dictionary<string, bool> SecurityAccesses { get; }
}

If you assign this to ItemSource, DataGrid will not know what to do with SecurityAccesses and will only display a column named SecurityAccesses and the cell will show as a collection.

What I would like to do is make the Datagrid aware that it should get all the keys of the dictionary to display the column names and of course the values to be displayed as a checkbox inside the datagrid.

I could do this with some code behind but I am trying to use XAML as much as possible so is there a way with behaviors that this could be done. Your recommendations would be really appreciated.

3

There are 3 answers

4
JBrooks On

Your DataGrid can be bound to an observable collection employees. Then your employees DataGrid can contain a DataGridTemplateColumn that contains a (child) DataGrid bound to the SecurityAccesses property of each employee. The data context of the child DataGrid will be the single employee bound to the parent DataGrid's row. Something like:

    <DataGrid ItemsSource="{Binding Employees}" ... >
      <DataGrid.Columns>
        <DataGridTextColumn Header="Access" Binding="{Binding Path=Key}" />
        <!-- more Employee columns here -->
        <DataGridTemplateColumn Header="SecurityAccesses">
          <DataGridTemplateColumn.CellTemplate>
               <DataTemplate>
                 <DataGrid AutoGenerateColumns="False" CanUserAddRows="False" 
    CanUserDeleteRows="False" ItemsSource="{Binding SecurityAccesses}">
                    <DataGrid.Columns>
         <DataGridTextColumn Header="Access" Binding="{Binding Path=Key}" />
                         <ComboBox Margin="4" ItemsSource="{Binding Value,
                    NotifyOnTargetUpdated=True,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
        ...
3
ReeganLourduraj On

Could you try the following way in one of your Datagrid Column

<DataGrid.Columns>
     <DataGridTextColumn Header="Key" Binding="{Binding Path=[Key]}" />
      <DataGridTextColumn Header="Value" Binding="{Binding Path=[Value]}" />
</DataGrid.Columns>
1
tessierp On

Just to follow up with those who tried to help, I found an example of someone who tried to do exactly what I wanted :

http://blogs.msmvps.com/deborahk/populating-a-datagrid-with-dynamic-columns-in-a-silverlight-application-using-mvvm/

I reused almost everything specified in this example except for the part that is used to compose the list of columns. Instead of creating a separate list of strings from where the name of columns is specified, I reused my models and implemented a converter that is used to get a list of strings from my models. Here is the xaml code :

    <DataGrid ItemsSource="{Binding LeftLegPartsAnomalies}"
          AutoGenerateColumns="False"
          IsReadOnly="{Binding IsFormCanBeAccessed, Converter={StaticResource NegateBooleanConverter}}">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding LegPartName}" Header="Name" />
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.HeaderStyle>
                <Style TargetType="DataGridColumnHeader">
                    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                    <Setter Property="Margin" Value="0" />
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
                                    AncestorType={x:Type UserControl}}, 
                                    Path=DataContext.LeftLegPartsAnomalies, 
                                    Converter={StaticResource LegPartsAnomaliesToListAnomaliesNameConverter}}">
                                        <ItemsControl.ItemsPanel>
                                        <ItemsPanelTemplate>
                                            <StackPanel Orientation="Horizontal" />
                                        </ItemsPanelTemplate>
                                    </ItemsControl.ItemsPanel>
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Border Width="70">
                                                <TextBlock Text="{Binding}" TextAlignment="Center"/>
                                            </Border>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </DataGridTemplateColumn.HeaderStyle>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ItemsControl ItemsSource="{Binding Anomalies}">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel Orientation="Horizontal"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Border Width="70">
                                    <CheckBox Margin="20,0,0,0" IsChecked="{Binding Spotted}" HorizontalAlignment="Center" />
                                </Border>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

The "magic" to get my list of string is done in LegPartsAnomaliesToListAnomaliesNameConverter :

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var listLegParts = value as ObservableCollection<LegPartAnomaly>;

        if (listLegParts == null || listLegParts.Count == 0)
            return null;

        List<string> convertedList = new List<string>();

        foreach (var legPart in listLegParts)
        {
            foreach (var anomaly in legPart.Anomalies)
            {
                if(convertedList.Contains(anomaly.Name))
                {
                    continue;
                }

                convertedList.Add(anomaly.Name);
            }
        }

        return convertedList;
    }

From my observable collection, LeftLegPartsAnomalies, I get all the anomalies defined inside and extract the names of every anomaly (once, no duplicates allowed). This is the converter's job!

By the way, I know this has nothing to do with security access. I was using the security access example not to get into the fine details of what I was doing but the principal is the same.

Thanks for your help!