سؤال

I used the following post to implement a datagrid bound to a list of dynamic objects

Binding DynamicObject to a DataGrid with automatic column generation?

The ITypedList method GetItemProperties works fine, a grid is displayed with all the columns I described.

I use a custom PropertyDescriptor and override the GetValue and SetValue methods as described in the above post, I also implement the TryGetMember and TrySetMember methods in the dynamic objects.

so basically I have a ComplexObject:DynamicCobject with a field Dictionary and a ComplexObjectCollection implementing ITypedList and IList.

This all works fine except when I bind the itemsSource of the DataGrid to the collection, the cells will show the SimpleObject type name and I actually want to implement a template to show the property Value of the SimpleObject in a text block.

I've used all sorts of methods to try and get the underlying SimpleObject but nothing works and I always get the ComplexObject for the row. I am using autogenerated columns and this always seems to produce a text column, this may be the problem but why cant I still get the underlying SimpleObject from somewhere in the cell properties?

Below would be my ideal solution but this does not work.

<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="DefaultNodeTempate">
            <ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, 
                              Path=Content}">
                <ContentControl.Resources>
                        <DataTemplate DataType="local:SimpleObjectType">
                            <TextBlock Text="{Binding Value}" />
                        </DataTemplate>
                </ContentControl.Resources>
            </ContentControl>
        </DataTemplate>
    </Grid.Resources>
    <DataGrid ItemsSource="{Binding ElementName=mainWin, Path=DynamicObjects}">
        <DataGrid.Resources>
            <Style TargetType="DataGridCell">
                <Setter Property="ContentTemplate" Value="{StaticResource DefaultNodeTempate}" />
            </Style>
        </DataGrid.Resources>
    </DataGrid>
</Grid>

Any suggestions would be much appreciated.

Thanks

Kieran

هل كانت مفيدة؟

المحلول

So I found the solution was to do some work in the code behind.

In the AutoGeneratingColumn event create a DataTemplate with a content control and a custom template selector (I create the selector in Xaml and found it as a resource).

Create a binding for the ContentProperty of the ContentControl with e.PropertyName as the path

Create a new DataGridTemplateColumn and set the new columns CellTemplate to your new DataTemplate

replace e.Column with your new column and hey presto the cells datacontext bind with the dynamic property for that column.

If anyone has any refinement to this please feel free to share your thoughts.

Thanks

EDIT: As requested some sample code for my solution

Custom template selector:

public class CustomDataTemplateSelector : DataTemplateSelector
{
    public List<DataTemplate> Templates { get; set; }

    public CustomDataTemplateSelector()
        : base()
    {
        this.Templates = new List<DataTemplate>();
    }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        DataTemplate template = null;
        if (item != null)
        {
            template = this.Templates.FirstOrDefault(t => t.DataType is Type ? (t.DataType as Type) == item.GetType() : t.DataType.ToString() == item.GetType().ToString());
        }

        if (template == null)
        {
            template = base.SelectTemplate(item, container);
        }

        return template;
    }
}

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="ParentControl">
        <Grid.Resources>
            <local:CustomDataTemplateSelector x:Key="MyTemplateSelector" >
                <local:CustomDataTemplateSelector.Templates>
                    <DataTemplate DataType="{x:Type local:MyCellObject}" >
                        <TextBox Text="{Binding MyStringValue}" IsReadOnly="{Binding IsReadOnly}" />
                    </DataTemplate>
                </local:CustomDataTemplateSelector.Templates>
            </local:CustomDataTemplateSelector>
        </Grid.Resources>
        <DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="True" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" >
        </DataGrid>
    </Grid>
</Window>

Code behind:

private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    // Get template selector
    CustomDataTemplateSelector selector = ParentControl.FindResource("MyTemplateSelector") as CustomDataTemplateSelector;

    // Create wrapping content control
    FrameworkElementFactory view = new FrameworkElementFactory(typeof(ContentControl));

    // set template selector
    view.SetValue(ContentControl.ContentTemplateSelectorProperty, selector);

    // bind to the property name
    view.SetBinding(ContentControl.ContentProperty, new Binding(e.PropertyName));

    // create the datatemplate
    DataTemplate template = new DataTemplate { VisualTree = view };

    // create the new column
    DataGridTemplateColumn newColumn = new DataGridTemplateColumn { CellTemplate = template };

    // set the columns and hey presto we have bound data
    e.Column = newColumn;
}

There may be a better way to create the data template, I have read recently that Microsoft suggest using a XamlReader but this is how I did it back then. Also I haven't tested this on a dynamic class but I'm sure it should work either way.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top