Friday, May 2, 2014

DataGrid vertical scrolling issues

This post is for WPF 4.0 and 4.5

If you have a DataGrid that displays parent and child data or very tall rows and the DataGrid's scrolling area is not very high you may find yourself unable to view some of the data. This is because, by default, the DataGrid scrolling snaps to top level items. Look at the XAML and code example below. You are unable to see the last of the child rows (band one).


<Window x:Class="DataGridScrolling.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" SizeToContent="WidthAndHeight"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <DataGrid Name="DG" ItemsSource="{Binding B0}" AutoGenerateColumns="False" IsReadOnly="true" RowDetailsVisibilityMode="Visible" 
                  Width="200" Height="100">
            <DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    <DataGrid Name="RD" HeadersVisibility="None" AutoGenerateColumns="false" IsReadOnly="true" ItemsSource="{Binding B1}">
                        <DataGrid.Columns>
                            <DataGridTextColumn Width="20"/>
                            <DataGridTextColumn Binding="{Binding}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Text" Binding="{Binding Text}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>



using System;
using System.Windows;
using System.Collections.ObjectModel;

namespace DataGridScrolling
{
    public partial class MainWindow : Window
    {
        public class cText
        {
            public string Text { get; set; }
            public ObservableCollection<string> B1 { get; set;} 

            public cText(string s)
            {
                this.Text=s;
                this.B1 = new ObservableCollection<string>();
            }
        }

        private ObservableCollection<cText> _B0 = new ObservableCollection<cText>();
        public ObservableCollection<cText> B0 { get { return _B0; } }

        public MainWindow()
        {
            B0.Add(new cText("Band 0 Row 0"));
            B0.Add(new cText("Band 0 Row 1"));

            B0[0].B1.Add("Band 1 Row 0");
            B0[0].B1.Add("Band 1 Row 1");
            B0[0].B1.Add("Band 1 Row 2");
            B0[0].B1.Add("Band 1 Row 3");
            B0[0].B1.Add("Band 1 Row 4");

            B0[1].B1.Add("Band 1 Row 0");

            InitializeComponent();
        }
    }
}

If you run this project and try to scroll down to the lower children of the first row you will see how the grid snaps to the second parent row before showing all the children of the first row. There are several ways to change this behavior


By default we scroll directly from this...

...to this

If you are targeting Framework 4.5 there is a new dependency object on the DataGrid's internal VirtualizingPanel called ScrollUnit that can be set to Item (the default) or Pixel. If we modify the XAML a little we can see how it works.

<DataGrid Name="DG" ItemsSource="{Binding B0}" AutoGenerateColumns="False" IsReadOnly="true" RowDetailsVisibilityMode="Visible" Width="200" 
          Height="100" VirtualizingPanel.ScrollUnit="Pixel">

If you're targeting Framework 4.0 (perhaps you still need to support Windows XP) you have two options. The easiest one is to set the DataGrid's ScrollViewer.CanContentScroll to false. This has the same effect but turns off virtualization of the DataGrid.

<DataGrid Name="DG" ItemsSource="{Binding B0}" AutoGenerateColumns="False" IsReadOnly="true" RowDetailsVisibilityMode="Visible" Width="200" 
          Height="100" ScrollViewer.CanContentScroll="False">

If you have a lot of rows in your DataGrid this will impact performance. However, showing a DataGrid with a lot of rows in a small viewport is not going to provide the best user experience so hopefully your DataGrid is small. If so, this is an acceptable solution.

If you must have a virtualized DataGrid you will have to work a lot harder. I won't reproduce the solution in this blog but you can find it here.

Now we can scroll halfway and see all the child rows

4 comments:

  1. Funnily, your page has scrolling issues of its own. Some of the code samples overflow the content area, onto the space background, and become illegible.

    ReplyDelete
  2. This article resolved all my complaints with the default WPF datagrid behavior, thanks!

    The part that fixed it is hidden by the background color though, hehe, you may want to wrap that text.

    ReplyDelete