0

I have a datagrid in a view defined like this:

<DataGrid local:DataGridColumnsBehavior.BindableColumns="{Binding Path=Grid.DisplayedProperties, Converter={StaticResource DynamicColumnConverter}}"
          ItemsSource="{Binding Path=Grid.Items}" 
          AutoGenerateColumns="False"
          GridLinesVisibility="None" 
          SelectionUnit="CellOrRowHeader">
    <DataGrid.Resources>
        <DataTemplate x:Key="TypeIcon">
            <Image Height="16" Source="{Binding Path=Type, Converter={StaticResource TypeImageConverter}, Mode=OneWay}" />
        </DataTemplate>
        <DataTemplate DataType="dgv:InapplicablePropertyValueViewModel">
            <TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="DimGray"/>
        </DataTemplate>
        <DataTemplate DataType="dgv:ApplicablePropertyViewModel">
            <TextBlock Text="{Binding Path=Value}"/>
        </DataTemplate>
    </DataGrid.Resources>
</DataGrid>

The "DataGridColumnsBehavior.BindableColumns" is an implementation of an attached behavior which provides dynamic binding on the grid's columns, taken from this answer.

Because I didn't want my ViewModel to directly contain a collection of UI objects (DataGridColumns), I wrote this TypeConverter to generate the UI objects on demand from my ViewModel contents:

class DynamicColumnConverter : IValueConverter
{
    public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
    {
        var propertyDefinitions = (IEnumerable<IPropertyDefinition>) value;

        Debug.Assert(propertyDefinitions != null);

        var columns = new ObservableCollection<DataGridColumn>
        {
            new DataGridTemplateColumn {CellTemplate = Application.Current.TryFindResource("TypeIcon") as DataTemplate},
            new DataGridTextColumn {Header = "Name", Binding = new Binding(nameof(Item.Name))},
            new DataGridTextColumn {Header = "Type", Binding = new Binding(nameof(Item.ShortType))},
            new DataGridTextColumn {Header = "Full Name", Binding = new Binding(nameof(Item.FullName))}
        };

        foreach (var propertyDefinition in propertyDefinitions)
        {
            var column = new DataGridTextColumn
            {
                Header = propertyDefinition.Name,
                Binding = new Binding($@"Properties[{propertyDefinition.Name}]")
            };
            columns.Add(column);
        }

        return columns;
    }

    public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture)
    {
        return DependencyProperty.UnsetValue;
    }
}

The grid item view model exposes the following interface:

interface IGridItemViewModel
{
    public IReadOnlyDictionary<String, IPropertyViewModel> Properties { get; }
    public String Name {get; set;}
    public String FullName {get;}
    public String Type {get; set;}
    public String ShortType {get;}
}

So all the binding paths should be valid at runtime. I think.

The converters are referenced as Resources:

<Window.Resources>
    <local:DynamicColumnConverter x:Key="DynamicColumnConverter"/>    
    <local:TypeImageConverter x:Key="TypeImageConverter"/>
</Window.Resources>

All the columns show up in the resulting data grid, but the icons aren't appearing in the first column, and none of the cell values are appearing for any of the columns defined in the foreach(var propertyDefinition in propertyDefinitions) loop. The dynamic bindings to the Name, FullName and ShortType properties of the GridItemViewModel are working, so that's why I have some confidence in the correctness of the binding paths.

I'm an inexperienced WPF developer and I'm having trouble debugging the broken dynamic columns.

What Visual Studio debugging tools can I use to figure out what's wrong with the dynamic bindings? E.g. how can I inspect these dynamically-created bindings at runtime to see what they're doing (or failing to do)?

Is there anything obvious from the code which might be the source of the problem?

Community
  • 1
  • 1
Hydrargyrum
  • 3,378
  • 4
  • 27
  • 41

1 Answers1

1

Application.Current.TryFindResource("TypeIcon") as DataTemplate

The reason is the code written above. Application.Current.TryFindResource looking for the resource only inside the resources of Application instance. Your DataTemplate defined inside DataGrid.Rsoures. So, there is two solutions

  1. Somehow get resources from Grid (For example pass it as ConverterParameter)
  2. Move your DataTemplate defintion from Grid.Resources to Application.Resources
Ruben Vardanyan
  • 1,298
  • 9
  • 19
  • Thanks. Any idea about the broken Properties text columns? – Hydrargyrum Apr 28 '17 at 10:02
  • 1
    Look for the binding exception in the _Output_ window while running application with debug. Is there any? – Ruben Vardanyan Apr 28 '17 at 10:44
  • 1
    So, the issue with all the columns created in the foreach() was that I needed to bind to `"Properties[{propertyDefinition.Name}].Value"`, not `"Properties[{propertyDefinition.Name}]"`. Now I'm trying to apply some triggers to those dynamically-created columns and having trouble getting the bindings right. But I think that's a whole other question. – Hydrargyrum May 02 '17 at 11:01