Binding Objects to a WPF datagrid with unknown-at-compile-time columns









up vote
0
down vote

favorite













Note: Problem is identical to Bind Objects with generic list to wpf datagrid (unanswered) which asks for a solution involving CellTemplates and doesn't include any workarounds. I'm open to any solution, and I have a working (but non-ideal) solution.




The setup is that I have a List of Objects (Persons) that each contain a List of DataObjects.



class Person : List<DataObject>

public string id get; set;

class DataObject

public string columnName get; set;
public string value get; set;



The columnNames are based on User Input, but every Person has the same columnNames in their List of DataObjects (i.e. they all have a firstName column, lastName column, dateOfBirth column, all with different values).



I'd like to show these values in a DataGrid format so that the values can be edited by the user.



What I'm currently doing is using a Grid (editGrid) and adding child TextBlocks to it for each column header, and then looping through the items to add TextBoxes for each cell. This works for small numbers, but when I have 1000s of People the program lags because of the sheer number of TextBoxes and TextBlocks being created.



List<People> items;
List<string> columnHeaders;
Grid editGrid;

// Generate column headers
editGrid.RowDefinitions.Add(new RowDefinition() Height = GridLength.Auto );
var columns = items.SelectMany(o => o.Select(a => a.columnName)).Distinct(StringComparer.OrdinalIgnoreCase);
foreach (string text in columns)

TextBlock headerText = new TextBlock();
headerText.Text = text;
Grid.SetColumn(headerText, editGrid.ColumnDefinitions.Count());
editGrid.ColumnDefinitions.Add(new ColumnDefinition() Width = GridLength.Auto );

columnHeaders.Add(text);
editGrid.Children.Add(headerText);


// Create rows
foreach (var item in items)

foreach (var dataObject in item)

var columnNum = columnHeaders.IndexOf(dataObject.columnName);
if (columnNum != -1)

TextBox valueBox = new TextBox();
Binding bind = new Binding();
bind.Source = dataObject;
bind.Mode = BindingMode.TwoWay;
bind.Path = new PropertyPath("value");
BindingOperations.SetBinding(valueBox , TextBox.TextProperty, bind);

Grid.SetColumn(valueBox, columnNum);
Grid.SetRow(valueBox, editGrid.RowDefinitions.Count);
editGrid.Children.Add(valueBox);


editGrid.RowDefinitions.Add(new RowDefinition() Height = GridLength.Auto );










share|improve this question

























    up vote
    0
    down vote

    favorite













    Note: Problem is identical to Bind Objects with generic list to wpf datagrid (unanswered) which asks for a solution involving CellTemplates and doesn't include any workarounds. I'm open to any solution, and I have a working (but non-ideal) solution.




    The setup is that I have a List of Objects (Persons) that each contain a List of DataObjects.



    class Person : List<DataObject>

    public string id get; set;

    class DataObject

    public string columnName get; set;
    public string value get; set;



    The columnNames are based on User Input, but every Person has the same columnNames in their List of DataObjects (i.e. they all have a firstName column, lastName column, dateOfBirth column, all with different values).



    I'd like to show these values in a DataGrid format so that the values can be edited by the user.



    What I'm currently doing is using a Grid (editGrid) and adding child TextBlocks to it for each column header, and then looping through the items to add TextBoxes for each cell. This works for small numbers, but when I have 1000s of People the program lags because of the sheer number of TextBoxes and TextBlocks being created.



    List<People> items;
    List<string> columnHeaders;
    Grid editGrid;

    // Generate column headers
    editGrid.RowDefinitions.Add(new RowDefinition() Height = GridLength.Auto );
    var columns = items.SelectMany(o => o.Select(a => a.columnName)).Distinct(StringComparer.OrdinalIgnoreCase);
    foreach (string text in columns)

    TextBlock headerText = new TextBlock();
    headerText.Text = text;
    Grid.SetColumn(headerText, editGrid.ColumnDefinitions.Count());
    editGrid.ColumnDefinitions.Add(new ColumnDefinition() Width = GridLength.Auto );

    columnHeaders.Add(text);
    editGrid.Children.Add(headerText);


    // Create rows
    foreach (var item in items)

    foreach (var dataObject in item)

    var columnNum = columnHeaders.IndexOf(dataObject.columnName);
    if (columnNum != -1)

    TextBox valueBox = new TextBox();
    Binding bind = new Binding();
    bind.Source = dataObject;
    bind.Mode = BindingMode.TwoWay;
    bind.Path = new PropertyPath("value");
    BindingOperations.SetBinding(valueBox , TextBox.TextProperty, bind);

    Grid.SetColumn(valueBox, columnNum);
    Grid.SetRow(valueBox, editGrid.RowDefinitions.Count);
    editGrid.Children.Add(valueBox);


    editGrid.RowDefinitions.Add(new RowDefinition() Height = GridLength.Auto );










    share|improve this question























      up vote
      0
      down vote

      favorite









      up vote
      0
      down vote

      favorite












      Note: Problem is identical to Bind Objects with generic list to wpf datagrid (unanswered) which asks for a solution involving CellTemplates and doesn't include any workarounds. I'm open to any solution, and I have a working (but non-ideal) solution.




      The setup is that I have a List of Objects (Persons) that each contain a List of DataObjects.



      class Person : List<DataObject>

      public string id get; set;

      class DataObject

      public string columnName get; set;
      public string value get; set;



      The columnNames are based on User Input, but every Person has the same columnNames in their List of DataObjects (i.e. they all have a firstName column, lastName column, dateOfBirth column, all with different values).



      I'd like to show these values in a DataGrid format so that the values can be edited by the user.



      What I'm currently doing is using a Grid (editGrid) and adding child TextBlocks to it for each column header, and then looping through the items to add TextBoxes for each cell. This works for small numbers, but when I have 1000s of People the program lags because of the sheer number of TextBoxes and TextBlocks being created.



      List<People> items;
      List<string> columnHeaders;
      Grid editGrid;

      // Generate column headers
      editGrid.RowDefinitions.Add(new RowDefinition() Height = GridLength.Auto );
      var columns = items.SelectMany(o => o.Select(a => a.columnName)).Distinct(StringComparer.OrdinalIgnoreCase);
      foreach (string text in columns)

      TextBlock headerText = new TextBlock();
      headerText.Text = text;
      Grid.SetColumn(headerText, editGrid.ColumnDefinitions.Count());
      editGrid.ColumnDefinitions.Add(new ColumnDefinition() Width = GridLength.Auto );

      columnHeaders.Add(text);
      editGrid.Children.Add(headerText);


      // Create rows
      foreach (var item in items)

      foreach (var dataObject in item)

      var columnNum = columnHeaders.IndexOf(dataObject.columnName);
      if (columnNum != -1)

      TextBox valueBox = new TextBox();
      Binding bind = new Binding();
      bind.Source = dataObject;
      bind.Mode = BindingMode.TwoWay;
      bind.Path = new PropertyPath("value");
      BindingOperations.SetBinding(valueBox , TextBox.TextProperty, bind);

      Grid.SetColumn(valueBox, columnNum);
      Grid.SetRow(valueBox, editGrid.RowDefinitions.Count);
      editGrid.Children.Add(valueBox);


      editGrid.RowDefinitions.Add(new RowDefinition() Height = GridLength.Auto );










      share|improve this question














      Note: Problem is identical to Bind Objects with generic list to wpf datagrid (unanswered) which asks for a solution involving CellTemplates and doesn't include any workarounds. I'm open to any solution, and I have a working (but non-ideal) solution.




      The setup is that I have a List of Objects (Persons) that each contain a List of DataObjects.



      class Person : List<DataObject>

      public string id get; set;

      class DataObject

      public string columnName get; set;
      public string value get; set;



      The columnNames are based on User Input, but every Person has the same columnNames in their List of DataObjects (i.e. they all have a firstName column, lastName column, dateOfBirth column, all with different values).



      I'd like to show these values in a DataGrid format so that the values can be edited by the user.



      What I'm currently doing is using a Grid (editGrid) and adding child TextBlocks to it for each column header, and then looping through the items to add TextBoxes for each cell. This works for small numbers, but when I have 1000s of People the program lags because of the sheer number of TextBoxes and TextBlocks being created.



      List<People> items;
      List<string> columnHeaders;
      Grid editGrid;

      // Generate column headers
      editGrid.RowDefinitions.Add(new RowDefinition() Height = GridLength.Auto );
      var columns = items.SelectMany(o => o.Select(a => a.columnName)).Distinct(StringComparer.OrdinalIgnoreCase);
      foreach (string text in columns)

      TextBlock headerText = new TextBlock();
      headerText.Text = text;
      Grid.SetColumn(headerText, editGrid.ColumnDefinitions.Count());
      editGrid.ColumnDefinitions.Add(new ColumnDefinition() Width = GridLength.Auto );

      columnHeaders.Add(text);
      editGrid.Children.Add(headerText);


      // Create rows
      foreach (var item in items)

      foreach (var dataObject in item)

      var columnNum = columnHeaders.IndexOf(dataObject.columnName);
      if (columnNum != -1)

      TextBox valueBox = new TextBox();
      Binding bind = new Binding();
      bind.Source = dataObject;
      bind.Mode = BindingMode.TwoWay;
      bind.Path = new PropertyPath("value");
      BindingOperations.SetBinding(valueBox , TextBox.TextProperty, bind);

      Grid.SetColumn(valueBox, columnNum);
      Grid.SetRow(valueBox, editGrid.RowDefinitions.Count);
      editGrid.Children.Add(valueBox);


      editGrid.RowDefinitions.Add(new RowDefinition() Height = GridLength.Auto );







      c# wpf datagrid






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 8 at 19:55









      Daniel W

      31




      31






















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          1
          down vote



          accepted










          Have you considered creating a DataGrid effect by using an ItemsControl? Something like:



          <ItemsControl ItemsSource="Binding" Name="IC1" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <VirtualizingStackPanel />
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.Template>
          <ControlTemplate>
          <Border
          BorderThickness="TemplateBinding Border.BorderThickness"
          Padding="TemplateBinding Control.Padding"
          BorderBrush="TemplateBinding Border.BorderBrush"
          Background="TemplateBinding Panel.Background"
          SnapsToDevicePixels="True">
          <ScrollViewer
          Padding="TemplateBinding Control.Padding"
          Focusable="False">
          <ItemsPresenter
          SnapsToDevicePixels="TemplateBinding UIElement.SnapsToDevicePixels" />
          </ScrollViewer>
          </Border>
          </ControlTemplate>
          </ItemsControl.Template>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <Grid>
          <Grid.ColumnDefinitions>
          <ColumnDefinition Width="1*" />
          <ColumnDefinition Width="9*" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
          <RowDefinition />
          <RowDefinition />
          </Grid.RowDefinitions>
          <TextBlock Text="ID" Grid.Column="0" Grid.Row="0"/>
          <TextBlock Grid.Column="0" Grid.Row="1" Text="Binding Path=id"/>
          <ItemsControl Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" ItemsSource="Binding" >
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <StackPanel>
          <TextBlock Text="Binding Path=columnName" Width="100" />
          <TextBox Text="Binding Path=value" Width="100" />
          </StackPanel>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>
          </Grid>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>


          I populated with some test data (sorry, but in VB):



          Property PersonList As New ObservableCollection(Of Person)

          Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
          For x As Integer = 1 To 500
          Dim P1 As New Person With .id = "SE" & x
          P1.Add(New DataObject With .columnName = "First Name", .value = "Simon")
          P1.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P1.Add(New DataObject With .columnName = "DOB", .value = "03/03/1980")
          Dim P2 As New Person With .id = "RE" & x
          P2.Add(New DataObject With .columnName = "First Name", .value = "Ruth")
          P2.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P2.Add(New DataObject With .columnName = "DOB", .value = "11/02/1979")
          PersonList.Add(P1)
          PersonList.Add(P2)
          Next
          IC1.DataContext = PersonList
          End Sub


          That's 1,000 rows, but because the control uses virtulisation, there is no lag.



          EDIT



          No idea if the is the best way, but I would suggest adding an Int width property to your DataObject class and binding the width of the TextBlocks & TextBoxes in the second ItemsControl to this.



          Then using the below code snippet to calculate the maximum width required (kudos to WPF equivalent to TextRenderer):



          public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)

          FormattedText ft = new FormattedText(text,
          CultureInfo.CurrentCulture,
          FlowDirection.LeftToRight,
          new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
          fontSize,
          Brushes.Black);
          return new Size(ft.Width, ft.Height);



          (I used the font details for the Window, but you can, of course, specify different details if you are styling the text boxes.)



          I then wrote this little bit to go through the data and set the widths (sorry, back to VB):



           If PersonList.Count > 0 Then
          Dim MaxLengths(PersonList(0).Count - 1) As Integer
          For i As Integer = 0 To MaxLengths.Count - 1
          MaxLengths(i) = 70 'Set Minimum width to accomodate Headers
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          Dim BoxSize As Size = MeasureTextSize(P(i).value, FontFamily, FontStyle, FontWeight, FontStretch, FontSize)
          If BoxSize.Width > MaxLengths(i) Then MaxLengths(i) = BoxSize.Width + 6 'to allow for padding
          Next
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          P(i).width = MaxLengths(i)
          Next
          Next
          End If





          share|improve this answer






















          • Thanks for this, it solves the lag entirely! I did have some issues though: it would put a column name above every entry instead of just one header, but that should be a relatively easy fix; I can't have the columns sized to the longest entry like a datagrid (width:auto makes the columns stop lining up between rows); and it assumes that the List<DataObjects> in each Person are ordered the same, so I'll have to sort them before loading them into the control. Do you have any ideas for the width issue?
            – Daniel W
            Nov 9 at 16:12










          • @DanielW - Yeah, the column headers I added as I misread your original post and thought the column name was specific to each person - just rip out the unnecessary controls from the DataTemplates. For the Width issue, I have addressed in an edit.
            – Simon Evans
            Nov 9 at 19:32










          Your Answer






          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "1"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader:
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          ,
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );













           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53215220%2fbinding-objects-to-a-wpf-datagrid-with-unknown-at-compile-time-columns%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          1
          down vote



          accepted










          Have you considered creating a DataGrid effect by using an ItemsControl? Something like:



          <ItemsControl ItemsSource="Binding" Name="IC1" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <VirtualizingStackPanel />
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.Template>
          <ControlTemplate>
          <Border
          BorderThickness="TemplateBinding Border.BorderThickness"
          Padding="TemplateBinding Control.Padding"
          BorderBrush="TemplateBinding Border.BorderBrush"
          Background="TemplateBinding Panel.Background"
          SnapsToDevicePixels="True">
          <ScrollViewer
          Padding="TemplateBinding Control.Padding"
          Focusable="False">
          <ItemsPresenter
          SnapsToDevicePixels="TemplateBinding UIElement.SnapsToDevicePixels" />
          </ScrollViewer>
          </Border>
          </ControlTemplate>
          </ItemsControl.Template>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <Grid>
          <Grid.ColumnDefinitions>
          <ColumnDefinition Width="1*" />
          <ColumnDefinition Width="9*" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
          <RowDefinition />
          <RowDefinition />
          </Grid.RowDefinitions>
          <TextBlock Text="ID" Grid.Column="0" Grid.Row="0"/>
          <TextBlock Grid.Column="0" Grid.Row="1" Text="Binding Path=id"/>
          <ItemsControl Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" ItemsSource="Binding" >
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <StackPanel>
          <TextBlock Text="Binding Path=columnName" Width="100" />
          <TextBox Text="Binding Path=value" Width="100" />
          </StackPanel>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>
          </Grid>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>


          I populated with some test data (sorry, but in VB):



          Property PersonList As New ObservableCollection(Of Person)

          Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
          For x As Integer = 1 To 500
          Dim P1 As New Person With .id = "SE" & x
          P1.Add(New DataObject With .columnName = "First Name", .value = "Simon")
          P1.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P1.Add(New DataObject With .columnName = "DOB", .value = "03/03/1980")
          Dim P2 As New Person With .id = "RE" & x
          P2.Add(New DataObject With .columnName = "First Name", .value = "Ruth")
          P2.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P2.Add(New DataObject With .columnName = "DOB", .value = "11/02/1979")
          PersonList.Add(P1)
          PersonList.Add(P2)
          Next
          IC1.DataContext = PersonList
          End Sub


          That's 1,000 rows, but because the control uses virtulisation, there is no lag.



          EDIT



          No idea if the is the best way, but I would suggest adding an Int width property to your DataObject class and binding the width of the TextBlocks & TextBoxes in the second ItemsControl to this.



          Then using the below code snippet to calculate the maximum width required (kudos to WPF equivalent to TextRenderer):



          public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)

          FormattedText ft = new FormattedText(text,
          CultureInfo.CurrentCulture,
          FlowDirection.LeftToRight,
          new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
          fontSize,
          Brushes.Black);
          return new Size(ft.Width, ft.Height);



          (I used the font details for the Window, but you can, of course, specify different details if you are styling the text boxes.)



          I then wrote this little bit to go through the data and set the widths (sorry, back to VB):



           If PersonList.Count > 0 Then
          Dim MaxLengths(PersonList(0).Count - 1) As Integer
          For i As Integer = 0 To MaxLengths.Count - 1
          MaxLengths(i) = 70 'Set Minimum width to accomodate Headers
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          Dim BoxSize As Size = MeasureTextSize(P(i).value, FontFamily, FontStyle, FontWeight, FontStretch, FontSize)
          If BoxSize.Width > MaxLengths(i) Then MaxLengths(i) = BoxSize.Width + 6 'to allow for padding
          Next
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          P(i).width = MaxLengths(i)
          Next
          Next
          End If





          share|improve this answer






















          • Thanks for this, it solves the lag entirely! I did have some issues though: it would put a column name above every entry instead of just one header, but that should be a relatively easy fix; I can't have the columns sized to the longest entry like a datagrid (width:auto makes the columns stop lining up between rows); and it assumes that the List<DataObjects> in each Person are ordered the same, so I'll have to sort them before loading them into the control. Do you have any ideas for the width issue?
            – Daniel W
            Nov 9 at 16:12










          • @DanielW - Yeah, the column headers I added as I misread your original post and thought the column name was specific to each person - just rip out the unnecessary controls from the DataTemplates. For the Width issue, I have addressed in an edit.
            – Simon Evans
            Nov 9 at 19:32














          up vote
          1
          down vote



          accepted










          Have you considered creating a DataGrid effect by using an ItemsControl? Something like:



          <ItemsControl ItemsSource="Binding" Name="IC1" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <VirtualizingStackPanel />
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.Template>
          <ControlTemplate>
          <Border
          BorderThickness="TemplateBinding Border.BorderThickness"
          Padding="TemplateBinding Control.Padding"
          BorderBrush="TemplateBinding Border.BorderBrush"
          Background="TemplateBinding Panel.Background"
          SnapsToDevicePixels="True">
          <ScrollViewer
          Padding="TemplateBinding Control.Padding"
          Focusable="False">
          <ItemsPresenter
          SnapsToDevicePixels="TemplateBinding UIElement.SnapsToDevicePixels" />
          </ScrollViewer>
          </Border>
          </ControlTemplate>
          </ItemsControl.Template>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <Grid>
          <Grid.ColumnDefinitions>
          <ColumnDefinition Width="1*" />
          <ColumnDefinition Width="9*" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
          <RowDefinition />
          <RowDefinition />
          </Grid.RowDefinitions>
          <TextBlock Text="ID" Grid.Column="0" Grid.Row="0"/>
          <TextBlock Grid.Column="0" Grid.Row="1" Text="Binding Path=id"/>
          <ItemsControl Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" ItemsSource="Binding" >
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <StackPanel>
          <TextBlock Text="Binding Path=columnName" Width="100" />
          <TextBox Text="Binding Path=value" Width="100" />
          </StackPanel>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>
          </Grid>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>


          I populated with some test data (sorry, but in VB):



          Property PersonList As New ObservableCollection(Of Person)

          Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
          For x As Integer = 1 To 500
          Dim P1 As New Person With .id = "SE" & x
          P1.Add(New DataObject With .columnName = "First Name", .value = "Simon")
          P1.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P1.Add(New DataObject With .columnName = "DOB", .value = "03/03/1980")
          Dim P2 As New Person With .id = "RE" & x
          P2.Add(New DataObject With .columnName = "First Name", .value = "Ruth")
          P2.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P2.Add(New DataObject With .columnName = "DOB", .value = "11/02/1979")
          PersonList.Add(P1)
          PersonList.Add(P2)
          Next
          IC1.DataContext = PersonList
          End Sub


          That's 1,000 rows, but because the control uses virtulisation, there is no lag.



          EDIT



          No idea if the is the best way, but I would suggest adding an Int width property to your DataObject class and binding the width of the TextBlocks & TextBoxes in the second ItemsControl to this.



          Then using the below code snippet to calculate the maximum width required (kudos to WPF equivalent to TextRenderer):



          public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)

          FormattedText ft = new FormattedText(text,
          CultureInfo.CurrentCulture,
          FlowDirection.LeftToRight,
          new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
          fontSize,
          Brushes.Black);
          return new Size(ft.Width, ft.Height);



          (I used the font details for the Window, but you can, of course, specify different details if you are styling the text boxes.)



          I then wrote this little bit to go through the data and set the widths (sorry, back to VB):



           If PersonList.Count > 0 Then
          Dim MaxLengths(PersonList(0).Count - 1) As Integer
          For i As Integer = 0 To MaxLengths.Count - 1
          MaxLengths(i) = 70 'Set Minimum width to accomodate Headers
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          Dim BoxSize As Size = MeasureTextSize(P(i).value, FontFamily, FontStyle, FontWeight, FontStretch, FontSize)
          If BoxSize.Width > MaxLengths(i) Then MaxLengths(i) = BoxSize.Width + 6 'to allow for padding
          Next
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          P(i).width = MaxLengths(i)
          Next
          Next
          End If





          share|improve this answer






















          • Thanks for this, it solves the lag entirely! I did have some issues though: it would put a column name above every entry instead of just one header, but that should be a relatively easy fix; I can't have the columns sized to the longest entry like a datagrid (width:auto makes the columns stop lining up between rows); and it assumes that the List<DataObjects> in each Person are ordered the same, so I'll have to sort them before loading them into the control. Do you have any ideas for the width issue?
            – Daniel W
            Nov 9 at 16:12










          • @DanielW - Yeah, the column headers I added as I misread your original post and thought the column name was specific to each person - just rip out the unnecessary controls from the DataTemplates. For the Width issue, I have addressed in an edit.
            – Simon Evans
            Nov 9 at 19:32












          up vote
          1
          down vote



          accepted







          up vote
          1
          down vote



          accepted






          Have you considered creating a DataGrid effect by using an ItemsControl? Something like:



          <ItemsControl ItemsSource="Binding" Name="IC1" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <VirtualizingStackPanel />
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.Template>
          <ControlTemplate>
          <Border
          BorderThickness="TemplateBinding Border.BorderThickness"
          Padding="TemplateBinding Control.Padding"
          BorderBrush="TemplateBinding Border.BorderBrush"
          Background="TemplateBinding Panel.Background"
          SnapsToDevicePixels="True">
          <ScrollViewer
          Padding="TemplateBinding Control.Padding"
          Focusable="False">
          <ItemsPresenter
          SnapsToDevicePixels="TemplateBinding UIElement.SnapsToDevicePixels" />
          </ScrollViewer>
          </Border>
          </ControlTemplate>
          </ItemsControl.Template>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <Grid>
          <Grid.ColumnDefinitions>
          <ColumnDefinition Width="1*" />
          <ColumnDefinition Width="9*" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
          <RowDefinition />
          <RowDefinition />
          </Grid.RowDefinitions>
          <TextBlock Text="ID" Grid.Column="0" Grid.Row="0"/>
          <TextBlock Grid.Column="0" Grid.Row="1" Text="Binding Path=id"/>
          <ItemsControl Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" ItemsSource="Binding" >
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <StackPanel>
          <TextBlock Text="Binding Path=columnName" Width="100" />
          <TextBox Text="Binding Path=value" Width="100" />
          </StackPanel>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>
          </Grid>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>


          I populated with some test data (sorry, but in VB):



          Property PersonList As New ObservableCollection(Of Person)

          Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
          For x As Integer = 1 To 500
          Dim P1 As New Person With .id = "SE" & x
          P1.Add(New DataObject With .columnName = "First Name", .value = "Simon")
          P1.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P1.Add(New DataObject With .columnName = "DOB", .value = "03/03/1980")
          Dim P2 As New Person With .id = "RE" & x
          P2.Add(New DataObject With .columnName = "First Name", .value = "Ruth")
          P2.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P2.Add(New DataObject With .columnName = "DOB", .value = "11/02/1979")
          PersonList.Add(P1)
          PersonList.Add(P2)
          Next
          IC1.DataContext = PersonList
          End Sub


          That's 1,000 rows, but because the control uses virtulisation, there is no lag.



          EDIT



          No idea if the is the best way, but I would suggest adding an Int width property to your DataObject class and binding the width of the TextBlocks & TextBoxes in the second ItemsControl to this.



          Then using the below code snippet to calculate the maximum width required (kudos to WPF equivalent to TextRenderer):



          public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)

          FormattedText ft = new FormattedText(text,
          CultureInfo.CurrentCulture,
          FlowDirection.LeftToRight,
          new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
          fontSize,
          Brushes.Black);
          return new Size(ft.Width, ft.Height);



          (I used the font details for the Window, but you can, of course, specify different details if you are styling the text boxes.)



          I then wrote this little bit to go through the data and set the widths (sorry, back to VB):



           If PersonList.Count > 0 Then
          Dim MaxLengths(PersonList(0).Count - 1) As Integer
          For i As Integer = 0 To MaxLengths.Count - 1
          MaxLengths(i) = 70 'Set Minimum width to accomodate Headers
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          Dim BoxSize As Size = MeasureTextSize(P(i).value, FontFamily, FontStyle, FontWeight, FontStretch, FontSize)
          If BoxSize.Width > MaxLengths(i) Then MaxLengths(i) = BoxSize.Width + 6 'to allow for padding
          Next
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          P(i).width = MaxLengths(i)
          Next
          Next
          End If





          share|improve this answer














          Have you considered creating a DataGrid effect by using an ItemsControl? Something like:



          <ItemsControl ItemsSource="Binding" Name="IC1" VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <VirtualizingStackPanel />
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.Template>
          <ControlTemplate>
          <Border
          BorderThickness="TemplateBinding Border.BorderThickness"
          Padding="TemplateBinding Control.Padding"
          BorderBrush="TemplateBinding Border.BorderBrush"
          Background="TemplateBinding Panel.Background"
          SnapsToDevicePixels="True">
          <ScrollViewer
          Padding="TemplateBinding Control.Padding"
          Focusable="False">
          <ItemsPresenter
          SnapsToDevicePixels="TemplateBinding UIElement.SnapsToDevicePixels" />
          </ScrollViewer>
          </Border>
          </ControlTemplate>
          </ItemsControl.Template>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <Grid>
          <Grid.ColumnDefinitions>
          <ColumnDefinition Width="1*" />
          <ColumnDefinition Width="9*" />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
          <RowDefinition />
          <RowDefinition />
          </Grid.RowDefinitions>
          <TextBlock Text="ID" Grid.Column="0" Grid.Row="0"/>
          <TextBlock Grid.Column="0" Grid.Row="1" Text="Binding Path=id"/>
          <ItemsControl Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" ItemsSource="Binding" >
          <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.ItemTemplate>
          <DataTemplate>
          <StackPanel>
          <TextBlock Text="Binding Path=columnName" Width="100" />
          <TextBox Text="Binding Path=value" Width="100" />
          </StackPanel>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>
          </Grid>
          </DataTemplate>
          </ItemsControl.ItemTemplate>
          </ItemsControl>


          I populated with some test data (sorry, but in VB):



          Property PersonList As New ObservableCollection(Of Person)

          Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
          For x As Integer = 1 To 500
          Dim P1 As New Person With .id = "SE" & x
          P1.Add(New DataObject With .columnName = "First Name", .value = "Simon")
          P1.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P1.Add(New DataObject With .columnName = "DOB", .value = "03/03/1980")
          Dim P2 As New Person With .id = "RE" & x
          P2.Add(New DataObject With .columnName = "First Name", .value = "Ruth")
          P2.Add(New DataObject With .columnName = "Last Name", .value = "Evans")
          P2.Add(New DataObject With .columnName = "DOB", .value = "11/02/1979")
          PersonList.Add(P1)
          PersonList.Add(P2)
          Next
          IC1.DataContext = PersonList
          End Sub


          That's 1,000 rows, but because the control uses virtulisation, there is no lag.



          EDIT



          No idea if the is the best way, but I would suggest adding an Int width property to your DataObject class and binding the width of the TextBlocks & TextBoxes in the second ItemsControl to this.



          Then using the below code snippet to calculate the maximum width required (kudos to WPF equivalent to TextRenderer):



          public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)

          FormattedText ft = new FormattedText(text,
          CultureInfo.CurrentCulture,
          FlowDirection.LeftToRight,
          new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
          fontSize,
          Brushes.Black);
          return new Size(ft.Width, ft.Height);



          (I used the font details for the Window, but you can, of course, specify different details if you are styling the text boxes.)



          I then wrote this little bit to go through the data and set the widths (sorry, back to VB):



           If PersonList.Count > 0 Then
          Dim MaxLengths(PersonList(0).Count - 1) As Integer
          For i As Integer = 0 To MaxLengths.Count - 1
          MaxLengths(i) = 70 'Set Minimum width to accomodate Headers
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          Dim BoxSize As Size = MeasureTextSize(P(i).value, FontFamily, FontStyle, FontWeight, FontStretch, FontSize)
          If BoxSize.Width > MaxLengths(i) Then MaxLengths(i) = BoxSize.Width + 6 'to allow for padding
          Next
          Next
          For Each P As Person In PersonList
          For i As Integer = 0 To P.Count - 1
          P(i).width = MaxLengths(i)
          Next
          Next
          End If






          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 9 at 19:25

























          answered Nov 8 at 23:08









          Simon Evans

          11617




          11617











          • Thanks for this, it solves the lag entirely! I did have some issues though: it would put a column name above every entry instead of just one header, but that should be a relatively easy fix; I can't have the columns sized to the longest entry like a datagrid (width:auto makes the columns stop lining up between rows); and it assumes that the List<DataObjects> in each Person are ordered the same, so I'll have to sort them before loading them into the control. Do you have any ideas for the width issue?
            – Daniel W
            Nov 9 at 16:12










          • @DanielW - Yeah, the column headers I added as I misread your original post and thought the column name was specific to each person - just rip out the unnecessary controls from the DataTemplates. For the Width issue, I have addressed in an edit.
            – Simon Evans
            Nov 9 at 19:32
















          • Thanks for this, it solves the lag entirely! I did have some issues though: it would put a column name above every entry instead of just one header, but that should be a relatively easy fix; I can't have the columns sized to the longest entry like a datagrid (width:auto makes the columns stop lining up between rows); and it assumes that the List<DataObjects> in each Person are ordered the same, so I'll have to sort them before loading them into the control. Do you have any ideas for the width issue?
            – Daniel W
            Nov 9 at 16:12










          • @DanielW - Yeah, the column headers I added as I misread your original post and thought the column name was specific to each person - just rip out the unnecessary controls from the DataTemplates. For the Width issue, I have addressed in an edit.
            – Simon Evans
            Nov 9 at 19:32















          Thanks for this, it solves the lag entirely! I did have some issues though: it would put a column name above every entry instead of just one header, but that should be a relatively easy fix; I can't have the columns sized to the longest entry like a datagrid (width:auto makes the columns stop lining up between rows); and it assumes that the List<DataObjects> in each Person are ordered the same, so I'll have to sort them before loading them into the control. Do you have any ideas for the width issue?
          – Daniel W
          Nov 9 at 16:12




          Thanks for this, it solves the lag entirely! I did have some issues though: it would put a column name above every entry instead of just one header, but that should be a relatively easy fix; I can't have the columns sized to the longest entry like a datagrid (width:auto makes the columns stop lining up between rows); and it assumes that the List<DataObjects> in each Person are ordered the same, so I'll have to sort them before loading them into the control. Do you have any ideas for the width issue?
          – Daniel W
          Nov 9 at 16:12












          @DanielW - Yeah, the column headers I added as I misread your original post and thought the column name was specific to each person - just rip out the unnecessary controls from the DataTemplates. For the Width issue, I have addressed in an edit.
          – Simon Evans
          Nov 9 at 19:32




          @DanielW - Yeah, the column headers I added as I misread your original post and thought the column name was specific to each person - just rip out the unnecessary controls from the DataTemplates. For the Width issue, I have addressed in an edit.
          – Simon Evans
          Nov 9 at 19:32

















           

          draft saved


          draft discarded















































           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53215220%2fbinding-objects-to-a-wpf-datagrid-with-unknown-at-compile-time-columns%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

          Edmonton

          Crossroads (UK TV series)