5

I would like to create a custom DataGrid column (or a DataGridCell style), ideally in XAML, and use it multiple times without any code duplication. It should contain multiple different controls, each accessing a different property of the data object.

There seem to be two ways of doing this:

  • DataGridTemplateColumn with a custom CellTemplate (see an example here)
  • custom DataGridCell style (something like this or this)

Both of these solutions suffer from the same issue. When specifying the bindings for the internal controls, they always seem to be related to the whole DataGrid row rather than the cell.

DataGridTemplateColumn does not have a Binding attribute, so its CellTemplate is bound to the whole row, making it impossible to reuse the same template for multiple columns, each accessing a different property of the row object.

Similarly, I can access the content of DataGridTextColumn in a style that overrides the Template via {TemplateBinding Content} (see here), but I cannot find a way to access the content's properties, so I assume that is not possible.

Here is a dummy example of such DataGrid to illustrate my situation:

public class Name
{
    public string First { get; set; }
    public string Last { get; set; }

    public override string ToString() => $"{First} {Last}";
}

public class Team
{
    public Name First { get; set; }
    public Name Second { get; set; }
    public Name Third { get; set; }
}

public ObservableCollection<Team> DataList { get; } = new ObservableCollection<Team>();
<DataGrid ItemsSource="{Binding DataList}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="First" Binding="{Binding First}"/>
        <DataGridTextColumn Header="Second" Binding="{Binding Second}"/>
        <DataGridTextColumn Header="Third" Binding="{Binding Third}"/>
    </DataGrid.Columns>
</DataGrid>

Now, I would like to separate the First and Last properties of Name and render them using individual controls, both still inside of the same DataGrid cell. The motivation here could be to have First in black and Last in red, for example, or anything else that requires them to be in two separate controls.

Example of a DateTemplate (applied as CellTemplate on DataGridTemplateColumn):

<DataTemplate x:Key="NameTemplate">
    <StackPanel>
        <Label Content="{Binding First}"/>
        <Label Content="{Binding Last}"/>
    </StackPanel>
</DataTemplate>

Example of a DataGridCell style (applied as CellStyle on a DataGridTextColumn):

<Style x:Key="NameStyle" TargetType="{x:Type DataGridCell}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridCell}">
                <DockPanel>
                    <ContentPresenter Content="{Binding First}"/>
                    <ContentPresenter Content="{Binding Last}"/>
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Is there any way to make a code similar to either of the examples above work?

Of course, it is possible to copy-paste the same exact template/style for each column, each accessing the relevant row-level property of Name, but this is an awful solution that would be a nightmare to maintain and it just does not scale.

I have seen a handful of similar questions, but not a single one of them had a XAML solution that was not extremely complex. This just seems like something that should be pretty easy to do, but I am unable to find a straightforward solution anywhere. The most similar question I found was probably this, but it was never really answered in a way that would help my case.

Two possible solutions were to either build and apply the XAML code (template/style) at runtime or to add an attached property to the column, which would allow me to pass the properties separately. Both of those seem like terrible hacks to me and I would much prefer avoiding them. Another solution is to create a new control (as suggested here) derived from DataGridBoundColumn (such as this), but I had no luck with that either and it a fairly complicated solution compared to defining a DataTemplate.

PS: While finishing up the question and looking for the links to other questions and answers, I have stumbled upon the DataGridBoundTemplateColumn implementation, which pretty much works, but I would still prefer a more XAML-native solution using just a template/style rather than creating a new control that still requires the template on top of itself.

natiiix
  • 1,005
  • 1
  • 13
  • 21
  • Creating the templates [programmatically](https://stackoverflow.com/questions/44751373/combining-datatemplates-at-runtime/44757321#44757321) isn't a hack but a solution. There is no way to reuse a template but replace only the bindings in XAML. – mm8 Aug 17 '20 at 13:27
  • @mm8 Okay, but how do you actually develop that kind of code? Surely enough, you won't be able to hot-reload it, etc., which definitely makes it an ugly solution. It feels like using `eval` in JavaScript/Python - sure, you can do it and it works, but it feels pretty edgy to me. I assume you keep a copy of the code commented out somewhere in the XAML, so that you can develop it and update the code string, which needs to be re-escaped every time. Seems bothersome to me; definitely not perfectly clean. – natiiix Aug 17 '20 at 19:25
  • tell me when you get this working. A few weeks ago I invested like 3+ hours research and trying out different things. nothing worked. This should be easy, but wpf is never as easy as it should be. especially when MVVM is involved. My approaches were about adding an attached property as a binding target on the datagrid column. it didnt work, because gridviewcolumns / datagridcolumns do not exist in the visual tree or something like that. – Welcor Aug 18 '20 at 18:08
  • @Blechdose I linked an answer from a similar question, which contains an implementation of a `DataGridTemplateColumn` with a `Binding` property. That's the closest I've gotten to resolving this issue. I must agree that for such a supposedly convenient, easy-to-use, straight-forward, etc. framework, WPF keeps on constantly proving to be quite painful to use for me. Maybe I'm just trying to bend it by having a wrong mindset. – natiiix Aug 19 '20 at 02:58
  • this looks like a regular datatemplate with fixed unflexible bindings. – Welcor Aug 19 '20 at 16:08
  • @Blechdose What do you mean fixed? The point was to have them relative to the cell, not the entire row, which can be done using that code. Perhaps you had something different in mind then. I just needed to re-use it for displaying the same datatype the same way in multiple columns within a single `DataGrid`. – natiiix Aug 19 '20 at 20:37
  • ah, ok, then we want to have different things. I thought you wanted to bind different objects to a template column which is manipulated by a datatemplate/style. Aka to reuse a datatemple column without coping it every time. – Welcor Aug 20 '20 at 16:28
  • @Blechdose Well, I can't tell if that's the same or a different thing quite frankly. I just want one DataTemplate per data type, so that I can use it for every `DataGridTemplateColumn` containing a value of said data type. – natiiix Aug 20 '20 at 21:37

0 Answers0