8

I have tried everything and got nowhere so I'm hoping someone can give me the aha moment. I simply cannot get the binding to pull the data in the datagrid successfully.

I have a DataTable that contains multiple columns with of MyDataType

public class MyData
{
    string nameData {get;set;}
    bool showData {get;set;}
}

MyDataType has 2 properties (A string, a boolean) I have created a test DataTable

DataTable GetDummyData()
{
    DataTable dt = new DataTable("Foo");
    dt.Columns.Add(new DataColumn("AnotherColumn", typeof(MyData)));
    dt.Rows.Add(new MyData("Row1C1", true));
    dt.Rows.Add(new MyData("Row2C1", false));
    dt.AcceptChanges();
    return dt;
}

I have a WPF DataGrid which I want to show my DataTable. But all I want to do is to change how each cell is rendered to show [TextBlock][Button] per cell with values bound to the MyData object and this is where I'm having a tonne of trouble.

My XAML looks like this

<Window.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="MyDataTemplate" DataType="MyData">
            <StackPanel Orientation="Horizontal" >
                <Button Background="Green" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,0,0" Content="{Binding Path=nameData}"></Button>
                <TextBlock Background="Green" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding Path=nameData}"></TextBlock>
            </StackPanel>
        </DataTemplate>
    </ResourceDictionary>
</Window.Resources>
<Grid>
    <dg:DataGrid Grid.Row="1" ItemsSource="{Binding}" AutoGenerateColumns="True"
                 x:Name="dataGrid1" SelectionMode="Single" CanUserAddRows="False"
                 CanUserSortColumns="true" CanUserDeleteRows="False" AlternatingRowBackground="AliceBlue"
                 AutoGeneratingColumn="dataGrid1_AutoGeneratingColumn" />
</Grid>

Now all I do once loaded is to attempt to bind the DataTable to the WPF DataGrid

dt = GetDummyData();
dataGrid1.ItemsSource = dt.DefaultView;

The TextBlock and Button show up, but they don't bind, which leaves them blank. Could anyone let me know if they have any idea how to fix this. This should be simple, thats what Microsoft leads us to believe. I have set the Column.CellTemplate during the AutoGenerating event and still get no binding.

Please help!!!

3
  • Did you try setting the datatable from GetDummyData as the DataGrid.DataContext? Commented Apr 20, 2010 at 17:58
  • Already did and still the same thing. Nothing changes on the UIElements as they appear to be unbound. I have tried to use the AutoGeneratingColumn Event to set my own Column.CellTemplate and still nothing. Any other ideas? Thanks Thanks Commented Apr 20, 2010 at 18:44
  • 1
    Try this: C# Read Excel and Show in WPF DataGrid Commented Jun 14, 2012 at 14:54

3 Answers 3

16

Edit: Updated to reflect the input of Aran Mulholland (see comment)

Apparently the DataGrid is passing the entire DataRowView to each cell. That's why the binding doesn't work. Your DataTemplate expects the DataContext to be of type MyData, but instead it is of type DataRowView. My proposed (somewhat hack-ish) workaround to get the DataContext you want is to create a custom DataGridTemplateColumn that will extract the necessary item from the DataRowView. The code is below:

<Window x:Class="DataGridTemplateColumnSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <ResourceDictionary>
            <DataTemplate x:Key="MyDataTemplate" DataType="DataRowView">
                <StackPanel Orientation="Horizontal">
                    <Button Background="Green" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,0,0" Content="{Binding Path=nameData}"></Button>
                    <TextBlock Background="Green" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding Path=nameData}"></TextBlock>
                </StackPanel>
            </DataTemplate>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <dg:DataGrid Grid.Row="1" AutoGenerateColumns="True" x:Name="dataGrid1" SelectionMode="Single" 
                     CanUserAddRows="False" CanUserSortColumns="true" CanUserDeleteRows="False" 
                     AlternatingRowBackground="AliceBlue"  AutoGeneratingColumn="dataGrid1_AutoGeneratingColumn"
                     ItemsSource="{Binding}" VirtualizingStackPanel.VirtualizationMode="Standard" />
    </Grid>
</Window>

using System.Data;
using System.Windows;
using Microsoft.Windows.Controls;

namespace DataGridTemplateColumnSample
{
    public partial class Window1
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = GetDummyData().DefaultView;
        }

        private static DataTable GetDummyData()
        {
            var dt = new DataTable("Foo");
            dt.Columns.Add(new DataColumn("OneColumn", typeof(MyData)));
            dt.Columns.Add(new DataColumn("AnotherColumn", typeof(MyData)));
            dt.Rows.Add(new MyData("Row1C1", true), new MyData("Row1C2", true));
            dt.Rows.Add(new MyData("Row2C1", false), new MyData("Row2C2", true));
            dt.AcceptChanges();
            return dt;
        }

        private void dataGrid1_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            var column = new DataRowColumn(e.PropertyName);
            column.Header = e.Column.Header;
            column.CellTemplate = (DataTemplate)Resources["MyDataTemplate"];
            e.Column = column;
        }
    }

    public class DataRowColumn : DataGridTemplateColumn
    {
        public DataRowColumn(string column) { ColumnName = column; }
        public string ColumnName { get; private set; }
        protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
        {
            var row = (DataRowView) dataItem;
            var item = row[ColumnName];
            cell.DataContext = item;
            var element = base.GenerateElement(cell, item);
            return element;
        }
    }

    public class MyData
    {
        public MyData(string name, bool data) { nameData = name; showData = data; }
        public string nameData { get; set; }
        public bool showData { get; set; }
    }
}

Note: This approach only appears to work with container virtualization off or in Standard mode. If the VirtualizationMode is set to Recycling the template is not applied.

Sign up to request clarification or add additional context in comments.

3 Comments

the datagrid passes the row to each column to generate the cell, this is the standard behaviour, the binding on the column defines the cells content. I think your issue is that you are doing everything on code. do you have dynamic amounts of columns? if not just set up your columns and their bindings in xaml.
I agree. If this were my project I would try to avoid doing things this way. Unfortunately project requirements (e.g. dynamic columns) sometimes don't leave you much room to do things the "clean" way.
Oh WOW. AWESOME. It works..Thanks. That answers the question 100%. I had figured out that it was the Row being passed but couldn't figure out how to get to the item in that view to bind to. Thanks alot. Unfortunately I don't see how you would avoid doing things this way without knowing the data in advance. Which kinda defeats the objective of having a flexible datagrid that works with non stock columns. Defining your own DataTemplateColumn for instances where the datatype is non-stock columntype, I would have thought would work right out the box. I can dream on, I guess.
8

After finding this thread and having trouble with the code shown here, I ran across this thread on MSDN, and it works much better! No virtualization problems at all so far as I've seen.

http://social.msdn.microsoft.com/Forums/en/wpf/thread/8b2e94b7-3c44-4642-8acc-851de5285062

Code:

private void dataGrid1_AutoGeneratingColumn(object sender, Microsoft.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
{
    if (e.PropertyType == typeof(MyData))
    {
        MyDataGridTemplateColumn col = new MyDataGridTemplateColumn();
        col.ColumnName = e.PropertyName;  // so it knows from which column to get MyData
        col.CellTemplate = (DataTemplate)FindResource("MyDataTemplate");
        e.Column = col;
        e.Column.Header = e.PropertyName;
    }
}

public class MyDataGridTemplateColumn : DataGridTemplateColumn
{
    public string ColumnName
    {
        get;
        set;
    }

    protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        // The DataGridTemplateColumn uses ContentPresenter with your DataTemplate.
        ContentPresenter cp = (ContentPresenter)base.GenerateElement(cell, dataItem);
        // Reset the Binding to the specific column. The default binding is to the DataRowView.
        BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
        return cp;
    }
}

Comments

0

I used the code from this thread and it looks a good solution for me. But, a question appeared - how to use the RowFilter on DataView with Custom classes in Cells?

(dataView as DataView).RowFilter = rowFilter;

1 Comment

Solved by filtering on the Source of the DataView.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.