Mark Bell
Mark Bell

Reputation: 29785

How can I stop the columns resizing on scroll in a WPF DataGrid with row virtualization turned on?

I have a WPF DataGrid, bound to a DataTable which may contain a variable number of columns. This DataTable can change its schema and data at runtime, whenever the user chooses a new data file to load into the app (in the example below, this is simulated by clicking the Load Data button).

I've set the column width to Auto so that columns automatically resize to fit their header text or the longest piece of row content in the column (whichever is larger). Here's the MainWindow.xaml from my example:

<Window x:Class="analog.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"
        xmlns:local="clr-namespace:analog"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="600">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Button x:Name="loadData" Grid.Row="0" Margin="0" Content="Load Data" Click="loadData_Click" VerticalAlignment="Top" />
        <DataGrid x:Name="dataGrid" Grid.Row="1" Margin="0" IsReadOnly="True" CanUserAddRows="False" ColumnWidth="Auto" SelectionUnit="Cell" />
    </Grid>
</Window>

And here's MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void loadData_Click(object sender, RoutedEventArgs e)
    {
        var data = new DataTable();
        data.Columns.Add("A", typeof(string));
        data.Columns.Add("B", typeof(string));
        data.Columns.Add("C", typeof(string));

        Enumerable.Range(1, 50).ToList().ForEach(i => {
            var row = data.NewRow();
            row["A"] = "aaa";
            row["B"] = "bbb";
            row["C"] = "ccc";
            data.Rows.Add(row);
        });

        var longRow = data.NewRow();
        longRow["A"] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        longRow["B"] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
        longRow["C"] = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
        data.Rows.Add(longRow);

        dataGrid.ItemsSource = data.DefaultView;
    }
}

This works perfectly, except that because the DataGrid rows are virtual (i.e. rendered on demand when the list is scrolled, as opposed to upfront), the columns can only automatically resize to the content which is currently being displayed.

However, when you then scroll down to the bottom of the list, the column widths suddenly and dramatically change as the longer cell content scrolls into view, making for a really horrible, disorientating user experience.

Turning off row virtualisation isn't an option (e.g. EnableRowVirtualization="False"), as I'm loading in some large data sets and performance is unusably bad without it.

I understand the limitation on sizing when using virtualisation and I'm quite happy with that behaviour. It's perfectly OK if the columns just stay at the widths they are set to when the grid is initially rendered, but I just can't find a way to do it!

I've tried some fairly horrible hacks, including this one where I loop over the columns after the initial render and programmatically set the widths in pixels to whatever size they are currently rendered at:

foreach (DataGridColumn column in dataGrid.Columns)
{
    column.Width = new DataGridLength(column.ActualWidth, DataGridLengthUnitType.Pixel);
}

I'm currently calling this manually, by putting it in a button click handler and clicking the button after the initial data has rendered—but this has no effect whatsoever, and the resize still happens when I get down to the longer values.

So, how can I stop the columns resizing when I scroll the DataGrid?

Upvotes: 3

Views: 1824

Answers (2)

rmojab63
rmojab63

Reputation: 3629

Another option (not exactly as a solution for the question, but similar situations, especially when EnableColumnVirtualization is true), is to simply set the MaxWidth property of columns. An example:

public MainWindow()
{
    InitializeComponent(); 

    DataTable tab = new DataTable();
    for (int i = 0; i < 1000; i++)
        tab.Columns.Add("A" + i.ToString());
    for (int i = 0; i < 1000; i++)
    {
        DataRow r = tab.NewRow();
        for (int j = 0; j < 1000; j++)
            r[j] = "something " + (i * i * j * j).ToString();
        tab.Rows.Add(r);
    }

    DataGrid dg = new DataGrid() { EnableColumnVirtualization = true, EnableRowVirtualization = true };
    this.Content = dg;
    dg.ItemsSource = tab.AsDataView();
    dg.AutoGeneratingColumn += Dg_AutoGeneratingColumn;
}

private void Dg_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    e.Column.MaxWidth = 100;
}

Upvotes: 1

mm8
mm8

Reputation: 169400

Setting the Width explicitly should work. Make sure that you do this once the DataGrid has been loaded.

Please refer to the following sample code which works for me.

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        List<DgItem> items = new List<DgItem>()
        {
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abc", B = "abc" },
            new DgItem() { A = "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc", B = "abc" }
        };
        dg.ItemsSource = items;
    }

    private void dg_Loaded(object sender, RoutedEventArgs e)
    {
        foreach (DataGridColumn column in dg.Columns)
        {
            column.Width = new DataGridLength(column.ActualWidth, DataGridLengthUnitType.Pixel);
        }
    }
}

MainWindow.xaml:

<DataGrid x:Name="dg" AutoGenerateColumns="False" Height="100"
                  Loaded="dg_Loaded">
    <DataGrid.Columns>
        <DataGridTextColumn Header="1" Binding="{Binding A}" />
        <DataGridTextColumn Header="2" Binding="{Binding B}" />
    </DataGrid.Columns>
</DataGrid>

enter image description here

Upvotes: 0

Related Questions