2

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?

Allon Guralnek
  • 15,813
  • 6
  • 60
  • 93
  • 2
    You did use `x:Type` in the `DataType` property, right? – H.B. Sep 19 '12 at 19:36
  • Yes, I did. And I'm pretty sure correctly because after typing the type's name, ReSharper recognized it and suggested adding the required namespace to the XAML, which I did. – Allon Guralnek Sep 20 '12 at 17:48
  • The [important part](http://stackoverflow.com/a/8109401/546730) is the `x:Type`, you'd get an exception if you use it but mess up the type reference. – H.B. Sep 20 '12 at 19:02

2 Answers2

7

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>
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • s/Visual Tree/Logical Tree/ (also i think you might be missing the point of the question which might be about peculiar behavior of the GridView) – H.B. Sep 20 '12 at 00:29
  • Thanks, but you did indeed miss my point. I understand the usage of `DataTemplate` and `DataType`. I used the following data template but it didn't have any effect, all cells in the `GridView` that had that type as their bound value just displayed the `.ToString()` of the object instead of the `DataTemplate`. My question is: **why?** `` – Allon Guralnek Sep 20 '12 at 17:53
  • @AllonGuralnek How is your GridViewColumn's data being bound? If you are simply setting the `DisplayMemberBinding` to your class, it will by default draw with a `TextBox` that has the `Text` property bound to your object, which will render the `.ToString()` of your object. You will need to set the `GridViewColumn.CellTemplate` to your `DataTemplate` or something like a `ContentControl` with the `Content` bound to your object, kind of like in [this answer](http://stackoverflow.com/a/4725474/302677) – Rachel Sep 20 '12 at 18:07
  • (In case you did not understand the first part of my comment: You should replace every usage of "visual tree" with "logical tree", WPF does *not* allow you to add any non-visual object to the visual tree. (See also the naming difference: `Child`/`Children` v.s. `Content`/`Items`)) – H.B. Sep 20 '12 at 18:58
  • @Rachel: Thanks, your suggestion seemed like it should do the trick, but it didn't work. The template did not apply (and I made sure to completely remove the `DisplayMemberBinding` as it takes precedence over `CellTemplate`). Should the `DataTemplate` permeate even into the logical tree of `GridView`? – Allon Guralnek Sep 20 '12 at 20:58
  • @AllonGuralnek Yes it should. Would you be able to add your XAML to your question? – Rachel Sep 21 '12 at 12:12
1

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.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • This sounds similar to Rachel's second XAML snippet, but I don't understand why an attached property or subclass is needed? – Allon Guralnek Sep 20 '12 at 21:05
  • @AllonGuralnek: It's not needed but you could implement the mentioned logic in a property of the sub-class or the attached property, so that you can just write something like `Member="Details"` and it will actually display its associated data-template. After all your goal was to not repeat yourself. – H.B. Sep 21 '12 at 05:10
  • Ah, that would indeed be an elegant solution, though I have no idea how it implement it. In any case, as you can see from my last comment on Rachel's answer, using a `ContentPresenter` doesn't seem to help; The `DataTemplate` still isn't applied. – Allon Guralnek Sep 21 '12 at 11:01
  • @AllonGuralnek: Well, it should, and is for me. Could you construct a *self-contained* example and add it to the question? Can you confirm that the binding itself is [not faulty](http://blogs.msdn.com/b/wpfsldesigner/archive/2010/06/30/debugging-data-bindings-in-a-wpf-or-silverlight-application.aspx)? – H.B. Sep 21 '12 at 12:39
  • The binding was not faulty... it was the ViewModel that was faulty! Everything is working now, thanks for your help. – Allon Guralnek Sep 21 '12 at 20:33