Daniel Möller
Daniel Möller

Reputation: 86650

WPF - Reuse templates with different bindings

Why not close this question?

Please don't close the question: the suggested link doesn't contain an answer, because there is no Binding property in DataGridTemplateColumn and I can't find a way to bind data to it.

It seems to be possible to use Text={Binding} inside the data template and this is "half" of the answer


Original question

I'm sort of a newbie in WPF.

I have a DataGrid where I want to have specific templates for some columns, but all the templates are the same.

For instance

<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" CanUserAddRows="False">
    <DataGrid.Columns>
        <!-- first column is a standard column, ok-->
        <DataGridTextColumn Header="First Column" Binding="{Binding FirstColumn}"/>

        <!-- a few of the other columns use a custom template-->
        <DataGridTemplateColumn Header="Second Column">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding SecondColumn, UpdateSourceTrigger=PropertyChanged}"/
                    <OtherThings />
                    <OtherThings />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

        <More Columns Exactly like the above, with different bindings/>
        <!-- Column3 Binding={Binding Column3}-->
        <!-- Column4 Binding={Binding Column4}-->
    </DataGrid.Columns>
</DataGrid>

How can I create a template as a static resource in a way that I can set the binding of this template?

I tried to create a static resource like

<DataTemplate x:Key="ColumnTemplate">
    <TextBox Text={I have no idea what to put here}/>
    <OtherThings/>
    <OtherThings/>
<DataTemplate>

And use it like

<DataGrid>
    <DataGrid.Columns>
        <!-- Where does the Header go?, where does the binding go?-->
        <!-- binds to column 2-->
        <DataGridTemplateColumn CellTemplate="{StaticResource ColumnTemplate}">
        <!-- binds to column 3-->
        <DataGridTemplateColumn CellTemplate="{StaticResource ColumnTemplate}">
    </DataGrid.Columns>
</DataGrid>

But I really don't know how to make the binding correctly from the item to the textbox inside the template.

How can I achieve this?

Upvotes: 0

Views: 796

Answers (2)

Matt
Matt

Reputation: 93

For anyone coming here looking for the answer. This template is reusable and the TextBox.Text property is bound to the text of the column binding.

<DataTemplate x:Key="ColumnTemplate">
    <TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Text}"/>
    <OtherThings/>
    <OtherThings/>
<DataTemplate>

Upvotes: 0

Andy
Andy

Reputation: 12276

In principle, you can do this. Whether this is a good plan or not is arguable though.

You can change the cell template to surround whatever the column binds to with other markup. Which seems to be what you want.

There are some subtelties to this in that the datagrid will switch out content of a cell from a textblock to textbox if you allow editing in the datagrid. Which is inadvisable for anything but trivial requirements - but that's perhaps a different subject.

In the datagrid or a parent resources:

<Window.Resources>
    <Style x:Key="StringStyle" TargetType="{x:Type DataGridCell}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGridCell}">
                    <StackPanel>
                        <ContentControl>
                             <ContentPresenter/>
                        </ContentControl>
                        <TextBlock Text="A constant string"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

You can then apply that to a column:

    <DataGridTextColumn Header="Date" Binding="{Binding DT, StringFormat=\{0:dd:hh:mm:ss.FF\}}"
                        CellStyle="{StaticResource StringStyle}"

As I mentioned above. Weird things are likely to happen when you click on the cell to edit it. You'd probably also want code to focus on the textbox within the contentpresenter.

If you instead wanted to cut down on repeated code you could use xamlreader to build your column dynamically using a template.

If you just have maybe 3 columns are the same then this is likely overkill but it'd be largely copy paste.

I built a sample illustrating this:

https://social.technet.microsoft.com/wiki/contents/articles/28797.wpf-dynamic-xaml.aspx#Awkward_Bindings_Data

https://gallery.technet.microsoft.com/WPF-Dynamic-XAML-Awkward-41b0689f

The sample builds a moving selection of months data.

The relevent code is in mainwindow

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // Get the datagrid shell
        XElement xdg = GetXElement(@"pack://application:,,,/dg.txt");  
        XElement cols = xdg.Descendants().First();     // Column list
        // Get the column template
        XElement col = GetXElement(@"pack://application:,,,/col.txt");  

        DateTime mnth = DateTime.Now.AddMonths(-6);

        for (int i = 0; i < 6; i++)
        {
            DateTime dat = mnth.AddMonths(i);
            XElement el = new XElement(col);
            // Month in mmm format in header
            var mnthEl = el.Descendants("TextBlock")
                        .Single(x => x.Attribute("Text").Value.ToString() == "xxMMMxx");
            mnthEl.SetAttributeValue("Text", dat.ToString("MMM"));

            string monthNo = dat.AddMonths(-1).Month.ToString();
            // Month as index for the product
            var prodEl = el.Descendants("TextBlock")
                        .Single(x => x.Attribute("Text").Value == "{Binding MonthTotals[xxNumxx].Products}");
            prodEl.SetAttributeValue("Text",
                "{Binding MonthTotals[" + monthNo + "].Products}");
            // Month as index for the total
            var prodTot = el.Descendants("TextBlock")
                        .Single(x => x.Attribute("Text").Value == "{Binding MonthTotals[xxNumxx].Total}");
            prodTot.SetAttributeValue("Text",
                "{Binding MonthTotals[" + monthNo + "].Total}");
            cols.Add(el);
        }

        string dgString = xdg.ToString();
        ParserContext context = new ParserContext();
        context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
        context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
        DataGrid dg = (DataGrid)XamlReader.Parse(dgString, context);
        Root.Children.Add(dg);
    }
    private XElement GetXElement(string uri)
    {
        XDocument xmlDoc = new XDocument();
        var xmltxt = Application.GetContentStream(new Uri(uri));
        string elfull = new StreamReader(xmltxt.Stream).ReadToEnd();
        xmlDoc = XDocument.Parse(elfull);
        return xmlDoc.Root;
    }

The col.txt file contains all the "standard" markup for a column. This is then manipulated as xml to replace values.

The result is then turned into wpf ui objects using xamlreader.parse

And again.

Probably way over engineered for a requirement has just a couple of columns.

Upvotes: 1

Related Questions