How do you use a DataType-targeted DataTemplate to

2019-04-01 19:37发布

问题:

Before you answer: This question is about WPF's ListView control (with GridView). It is not about WPF's DataGrid control, ASP.NET's DataGrid or ListView controls, or WinForm's DataGridView or ListView controls. They all sound very similar and are often confused, but an answer for the wrong type of control is both unhelpful to me and more importantly a waste of the time you took to write the answer, which I believe should not be squandered.


I have a ListView control with GridView that has several columns bound to my view-model's properties. I can easily customize the visual appearance of the GridViewColumn's cells by specifying a CellTemplate (either inline or via a resource).

Now I have a particular property on my view-model; its type is an abstract base class and its value can be one of several derived types. Each derived type should have a different DataTemplate in the cell. Luckily, GridViewColumn has a CellTemplateSelector which does exactly what I want, but requires writing some plumbing code. But looking at the page for DataTemplateSelector it says:

Note that if you have objects of different types you can set the DataType property on the DataTemplate. If you do that then there is no need to create a DataTemplateSelector. [...] For more information, see Data Templating Overview.

Hurray! No need to write plumbing code. My types are different so this seems like a perfect fit. But alas, even after I defined a DataTemplate with a DataType that matches a specific derived type of one of the databound columns (bound using GridViewColumn's DisplayMemberBinding), it had no effect.

I simply want to display a different DataTemplate according to the runtime type of a certain column in my GridView. Are DataType-targeted DataTemplates simply incompatible with GridView? Is there a way to use them, or must I resort to specifying a CellTemplateSelector? Perhaps there is a way to specify multiple DataTemplates inside GridViewColumn's CellTemplate, so that the DataType property will have an effect?

回答1:

WPF allows you to insert objects such as ViewModels in the Logical Tree, while a DataTemplate can be used to tell WPF how to draw the specified object when drawing the Visual Tree.

An implicit DataTemplate is a DataTemplate that only has a DataType defined (no x:Key), and it will be used automatically whenever WPF tries to render an object of the specified type in the VisualTree.

So, you can use

<Window.Resources>

    <DataTemplate DataType="{x:Type local:ViewModelA}">
        <local:ViewA />
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:ViewModelB}">
        <local:ViewB />
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:ViewModelC}">
        <local:ViewC />
    </DataTemplate>

</Window.Resources>

to tell WPF to draw ViewModelA with ViewA, ViewModelB with ViewB, and ViewModelC with ViewC.

If you only want this applied to your GridView instead of to the entire Window, you can specify <GridView.Resources> (or <ListView.Resources>, I can't remember which one)

It should be noted that if you are binding your column using the DisplayMemberBinding, it will render as a TextBox with the Text value bound to your property, which means it will render YourViewModel.ToString() instead of trying to draw the ViewModel in the VisualTree using your DataTemplate.

To avoid that, simply set the CellTemplate to something like a ContentPresenter with the Content property bound to your ViewModel, and it will render your ViewModel using your implicit DataTemplates

<GridViewColumn Header="Some Header">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <ContentPresenter Content="{Binding YourViewModelProperty}" />
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>


回答2:

The implementation of DisplayMember* properties is crap, i have no idea why they thought that binding a TextBlock instead of a ContentPresenter would be a good idea.

I would recommend an attached property or a subclass with a respective property to override this. You just need to make it create a DataTemplate containing a ContentPresenter whose Content is bound to the targeted property, that will allow for implicit DataTemplating. This deferring DataTemplate then should be assigned as the CellTemplate of the column.