none
Bind DataTable to WPF DataGrid using DataGridTemplateColumn Programatically

    Question

  • I have searched for a clearly explained way to do this and have come up with nothing.
    Every example I've seen seems to skip the obvious question and explain how to do it at design time only.
    I have a DataTable that contains multiple columns of MyDataType.

    public class MyData 
    { 
        string MyBooleanData{get;set;} 
        boolean MyTextData{get;set;} 


    //Constructor takes values and initializes the properties.
    } 

    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)); 
            return dt; 
    }     

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

    I'm currently using the AutoGeneratingColumn Event of the Grid to set the DataGridTemplateColumn as follows. When I breakpoint here, the Propertytype is correct, so why is the data from the DataTable not binding to the display elements in the DataGrid?

     private void dataGrid1_AutoGeneratingColumn(object sender, Microsoft.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
     {
      if (e.PropertyType == typeof(MyData))
      {
      DataGridTemplateColumn col = new DataGridTemplateColumn();
      Binding binding = new Binding(e.PropertyName);
      col.Header = e.PropertyName;
      col.CellTemplate = (this.Resources["MyDataTemplate"] as DataTemplate);
      e.Column = col;
      } 
     }
    
     Here is my XAML 
    <Window x:Class="WpfApplication1.Window1"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit xmlns:sys="clr-namespace:System;assembly=mscorlib"
     xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
     xmlns:cm="clr-namespace:System.ComponentModel;assembly=WindowsBase" 
     xmlns:dtt="clr-namespace:System.Data;assembly=System.Data" 
     xmlns:local="clr-namespace:WpfApplication1"
     Title="Window1" Height="500" Width="600" >
     <Window.Resources>
     <ResourceDictionary>
      <local:ItemTemplateSelector x:Key="dataTemplateSelector"/>  
      <DataTemplate x:Key="MyDataTemplate" DataType="{x:Type local:MyData}">
      <StackPanel Orientation="Horizontal" >
       <CheckBox Name="chkHeader" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Center" Content="" IsChecked="{Binding Path=MyBooleanData}"></CheckBox>
       <Button x:Name="theButton" Background="Green" Width="100" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,0,0" Content="{Binding Path=MyTextData}"></Button>
       <TextBlock x:Name="theTextBlock" HorizontalAlignment="Center"
        VerticalAlignment="Center" Margin="5,0,0,0" 
        Text="{Binding Path=MyTextData}" ></TextBlock>   
      </StackPanel>
      </DataTemplate>  
     </ResourceDictionary> 
     </Window.Resources>
    
     <Grid> 
     <Grid.RowDefinitions>
      <RowDefinition Height="auto"/>
      <RowDefinition/>
     </Grid.RowDefinitions>
     <Button Grid.Row="0" Click="Button_Click" IsDefault="True"> Click To Bind Data</Button>
     <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}" 
       >
     </dg:DataGrid>
     </Grid>
    </Window> "
    "
    "
    
    All I want is to be able to Dynamically crate a templateColumn to show a TextBlock and a CheckBox in the same cell for each MyData object.have had no luck doing this, and have only got it to work correctly by declaring the column statically in the XAML.  Which is of no use to me since the number of Columns could vary at Runtime. Doing it dynamically, seems to use the Template but doesn't bind the Properties correctly according to my Datatemplate, so all the elements in the WPFDataGrid are blank.

    Please help..I'm tearing my hair out trying various things.
    • Edited by Coders_Unite Tuesday, April 20, 2010 8:42 PM spaces
    Tuesday, April 20, 2010 8:34 PM

Answers

  • Hi,

    Sorry for the late reply. The problem here is that each cell's data context is a DataRowView instead of the an MyData instance. So the question now is 'how to extract the correct MyData instance from the DataRowView'.

    In order to do that, we need to 1) know which column is being generated; 2) Change the default binding in the DataGridTemplateColumn so its content is bound to a specific data column in the DataRowView instead of the DataRowView itself.

    This leads to the following code:

    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;
      }
    }

    Since here your DataGrid is read-only, I just override the GenerateElement method; if there is editing template, then override GenerateEditingElement as well.

    Now in the AutoGeneratingColumn event handler, use MyDataGridTemplateColumn instead:

    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;
      }
    }

    Please let me know how it works for you.

    Regards,
    Jie
    MSDN Subscriber Support in Forum
    If you have any feedback on our support, please contact msdnmg@microsoft.com


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    If you have any feedback, please tell us.

    The All-In-One Code Framework Project
    My Blog (in Simplified Chinese)
    Friday, April 23, 2010 3:42 AM

All replies

  • Hi,

    Sorry for the late reply. The problem here is that each cell's data context is a DataRowView instead of the an MyData instance. So the question now is 'how to extract the correct MyData instance from the DataRowView'.

    In order to do that, we need to 1) know which column is being generated; 2) Change the default binding in the DataGridTemplateColumn so its content is bound to a specific data column in the DataRowView instead of the DataRowView itself.

    This leads to the following code:

    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;
      }
    }

    Since here your DataGrid is read-only, I just override the GenerateElement method; if there is editing template, then override GenerateEditingElement as well.

    Now in the AutoGeneratingColumn event handler, use MyDataGridTemplateColumn instead:

    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;
      }
    }

    Please let me know how it works for you.

    Regards,
    Jie
    MSDN Subscriber Support in Forum
    If you have any feedback on our support, please contact msdnmg@microsoft.com


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    If you have any feedback, please tell us.

    The All-In-One Code Framework Project
    My Blog (in Simplified Chinese)
    Friday, April 23, 2010 3:42 AM
  • Any updates?

    Thanks,
    Jie
    MSDN Subscriber Support in Forum
    If you have any feedback on our support, please contact msdnmg@microsoft.com


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    If you have any feedback, please tell us.

    The All-In-One Code Framework Project
    My Blog (in Simplified Chinese)
    Tuesday, April 27, 2010 10:01 AM
  • Jie:
    I have spent all day on a situation where I believe that I have to get the ContentPresenter of the datatemplate for a DataGridTemplateColumn.  I think I need this because I have to call UpdateTarget() on the TextProperty of the textbox that is in the datatemplate.  I would be thrilled if you could help!!!!

    Basically, in XAML,

    I have a DataGridTemplateColumn. It has a DataTemplate with a textbox. The textbox has a TWO-WAY converter on it.

    The problem is, when the Converter's CONVERT method gets called, it does some math and changes the value that the user types in. It stores it in the model and raises the NotifyPropertyChanged event. HOWEVER, the ConvertBack method does NOT get called. This is because in WPF, what keeps the CONVERTBACK from being called on the cycle back is that the PropertyChange event is ignored if it is raised by the binding source of a two way bind, if it is known that the binding is currently updating the source.

    Hence, I need to call UpdateTarget on the textproperty of the textbox in the cell...

    I can get my current item (called a routepoint).

    RoutePoint currentPoint = (

    RoutePoint) (pointsDataGrid.Items.CurrentItem);  // works

    ContentPresenter

     

    contentPresenter =

     

    WPFDataGridHelper.GetObjectOfTypeInVisualTree<ContentPresenter>(

    pointsDataGrid.ItemContainerGenerator.ContainerFromItem(currentPoint)); // looks like it works -- but is this what we want for a datagridtemplatecolumn?

     

    DataTemplate dt = (DataTemplate) (this.Resources["LatitudeCellTemplate"]);

     

    if (dt != null) // it is not null

    {

     

    TextBox tb = (TextBox) dt.FindName("latitudeText", contentPresenter); // this fails

     

    BindingExpression be = tb.GetBindingExpression(TextBox.TextProperty);

     

    if (be != null)

    {

    be.UpdateTarget();

    }

     

    Thursday, May 06, 2010 8:10 PM
  • Hi Jie,

    this is exactly what i need, but there is a problem.

    At the line   "e.Column = col;" i will get the error "Cannot convert source type MyDataGridTemplateColumn  to target type Microsoft.Windows.Controls.DataGridColumn.

    So what am i doing wrong??

    regard thomas


    • Edited by Thomas Jacke Wednesday, February 08, 2012 11:44 AM
    Wednesday, February 08, 2012 11:02 AM
  • Hello Jey_Wang,

    i used your code, and it works. But if i want do Edit the DataRow, i get bindin Exceptions.

    System.Windows.Data Error: 40 : BindingExpression path error: 'Input' property not found on 'object' ''DataRowView' (HashCode=64304030)'. BindingExpression:Path=Input; DataItem='DataRowView' (HashCode=64304030); target element is 'TextBlock' (Name='tbInput'); target property is 'Text' (type 'String')

    And i used only a copy of my normal template. How can i fixed it?

    if (e.PropertyType == typeof(DataField))
                {
                    DatafieldDataGridTemplateColumn col = new DatafieldDataGridTemplateColumn();
                    col.ColumnName = e.PropertyName;  // so it knows from which column to get MyData
                    col.CellTemplate = (DataTemplate)FindResource("DataFieldTemplate");
                    col.CellEditingTemplate = (DataTemplate)FindResource("DataFieldChangeTemplate");
                    e.Column = col;
                    e.Column.Header = e.PropertyName;
                }

    Wednesday, April 11, 2012 9:16 AM