Which interface(s) does a class need to implement for DataGrid
to automatically discover its schema and generate columns?
Details
I'm trying to implement paging for DataGrid
. The underlying data source is a DataTable
. Binding DataGrid
directly to the DataTable
works fine and AutoGenerateColumns
does its job.
Due to large size of incoming data and slow DataGrid
response, I had to use row virtualization, which resulted in several (known) issues and I had to turn it off.
I'm now working on paging approach instead. Found a good implementation of PagingCollectionView and tried it. DataGrid
correctly generates rows for the current page, but not columns. Looks like it is not able to read the schema of the underlying collection. (Note that the code at the link does not use AutoGenerateColumns
and therefore does not suffer from the problem at hand).
I investigated further to see what special interfaces do DataTable
and DataView
implement to expose their schema. It appears (and I could be wrong) that ITypedList
is the interface that I need to implement. I went on to implement this and am now stuck GetItemProperties()
. There doesn't appear a way to construct PropertyDescriptor
s manually for each column of the DataTable
. There is TypeDesriptor
that can get all public properties of a given type, but that is obviously not going to work for DataTable
columns.
So, what do I need to do to make my CollectionView class schema become discoverable for the DataGrid
?
MCVE
Here is the minimum code that shows the problem with DataGrid:
XAML
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Select All" Click="SelectAll_Click" Grid.Row="0" />
<Button Content="Deselect All" Click="Deselect_Click" Grid.Row="1" />
<DataGrid x:Name="DG" AutoGenerateColumns="False" SelectionUnit="FullRow" Grid.Row="2">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ID}" />
<DataGridTextColumn Binding="{Binding Name}" />
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode =TwoWay}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</Window>
Code
using System.Data;
class MainWindow
{
DataTable DT = new DataTable();
MainWindow()
{
// This call is required by the designer.
InitializeComponent();
DT.Columns.Add(new DataColumn("ID", typeof(Int32)));
DT.Columns.Add(new DataColumn("Name", typeof(string)));
DT.Columns.Add(new DataColumn("IsSelected", typeof(bool)));
for (index = 1; index <= 10000; index++) {
var NR = DT.NewRow();
NR["ID"] = index;
NR["Name"] = System.Guid.NewGuid().ToString();
DT.Rows.Add(NR);
}
DG.ItemsSource = DT.DefaultView;
}
private void SelectAll_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < DT.Rows.Count; i++) {
DT.Rows[i]["IsSelected"] = true;
}
}
private void Deselect_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < DT.Rows.Count; i++) {
DT.Rows[i]["IsSelected"] = false;
}
}
}
Run the code and click Select All button. All the rows appear to be selected. Scroll down to the bottom of the DataGrid. Click Deselect All. Now scroll up slowly. You'll see that many of the rows are still selected. Go to your XAML and disable row virtualization for DataGrid. Selection will now work fine but it will take more time for the DataGrid to load.