0
votes

I have a DataGrid bound to an observable list of objects. If I also have an ItemsControl bound to that list the sorting performance (by clicking the DataGrid header) is very bad (in the order of a few seconds for the sample below). When the ItemsControl is not bound to the same list, then sorting is instant.

Here is some sample code that exhibits this behaviour

namespace LargeDataGridViewTest
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainPresenter();
        }
    }

    public class MainPresenter : INotifyPropertyChanged
    {
        private readonly ObservableCollection<Item> _items = new ObservableCollection<Item>();
        public IEnumerable<Item> Items { get { return _items; } }

        public MainPresenter()
        {
            for (var i = 0; i < 10000; i++)
                _items.Add(new Item());
        }
    }

    public class Item : INotifyPropertyChanged
    {
        public int Random { get; private set; }
        private static readonly Random Rand = new Random();

        public Item()
        {
            Random = Rand.Next(0, 1000000);
        }
    }
}

And the corresponding XAML

<Window.Resources>
    <DataTemplate DataType="{x:Type LargeDataGridViewTest:MainPresenter}">
        <DockPanel>
            <DataGrid ItemsSource="{Binding Items}"/>
            <!--ListBox ItemsSource="{Binding Items}"/-->
            <ItemsControl ItemsSource="{Binding Items}"/>
        </DockPanel>
    </DataTemplate>
</Window.Resources>

<ContentPresenter Content="{Binding}"/>

If instead of the ItemsControl I use the ListBox, the sorting performance is fine. If I use the ListBox but access it's underlying ItemsControl by, e.g., changing the ItemsPanelTemplate, the performance is bad again.

If I take a shallow copy of the list (referencing the same items) and bind the ItemsControl to that instead, the performance is fine again.

Running both the ItemsControl binding that is slow and the fast ListBox binding through EQATEC profiler shows no difference in performance other than the top level app time.

Does anyone have any idea what is happening here?

EDIT

Part of the answer appears to be that ItemsControls aren't virtualized and hence have to draw all their items rather than just the visible ones. In this case, why are all the ItemsControl items redrawn when the DataGrid is sorted (even if the ItemsControl binding mode is OneTime)? And how can I stop this having an impact of the performance of the DataGrid sort?

2

2 Answers

0
votes

I suspect it is because both the DataGrid and ListBox virtualize their items by default, while an ItemsControl does not.

This means that the DataGrid and ListBox only create UI objects that are visible on the screen, while the ItemsControl will create all 10,000 UI objects.

To fix the performance, virtualize your ItemsControl. Here's the basic pieces needed, although check out the linked question for more details.

<ItemsControl
    VirtualizingStackPanel.IsVirtualizing="True" <!-- needed -->
    ScrollViewer.CanContentScroll="True" <!-- needed -->
    ... >
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel /> <!-- needed -->
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer>  <!-- needed -->
                <ItemsPresenter  />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>
0
votes

virtualization is the key!

ItemsControls are not virtualized by default and when the data grid sorts the Items collection the Default Collection View gets sorted and same sort applies to the other ItemsControl as well. But that ItemsControl has no virtualization implemented and hence it not only sorts the items but also renders them on its items container. ListBox performs better as it has virtualization implemented in it by default.

For this the quick fix would be simulate the ListBox to look like a ItemsControl. You do that by get rid of the selection colors of the list box.

   <ListBox ItemsSource="{StaticResource MyData}" DisplayMemberPath="ID">
       <ListBox.ItemContainerStyle>
            <Style TargetType="{x:Type ListBoxItem}">
                <Style.Resources>
                    <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
                                     Color="Transparent"/>
                </Style.Resources>
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Foreground" Value="Black"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>