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 );
c# wpf datagrid
add a comment |
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 );
c# wpf datagrid
add a comment |
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 );
c# wpf datagrid
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
c# wpf datagrid
asked Nov 8 at 19:55
Daniel W
31
31
add a comment |
add a comment |
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
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
add a comment |
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
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
add a comment |
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
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
add a comment |
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
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
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
add a comment |
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
add a comment |
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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