3

The problem I have is my listcollectionview is taking 3-4 seconds to update. I think I have the virtualization configured correctly; however, please point out if it is incorrect. I have my itemssource bound to a ICollectionView.

If i set the loop to 3000+ in BindingViewModel UI takes a long time to launch even though it takes less than a second to build the ViewModels.

I don't know how to attach files so I'll post all the sample code that reproduces this problem:

BigList.Xaml: `

    <Style x:Key="textBlockBaseStyle" TargetType="TextBlock">
        <Setter Property="Foreground" Value="Blue"/>
    </Style>

    <Style x:Key="headerButtonStyle" TargetType="Button">
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="Foreground" Value="Blue"/>
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="BorderBrush" Value="Transparent"/>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="DarkBlue"/>
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style x:Key="borderBaseStyle" TargetType="Border">
        <Setter Property="BorderBrush" Value="Blue"/>
    </Style>

    <!--these two styles let us cascadingly style on the page without having to specify styles
        apparently styles don't cascade into itemscontrols... in the items control we must reference via key -->

    <Style TargetType="TextBlock" BasedOn="{StaticResource textBlockBaseStyle}"/>
    <Style TargetType="Border" BasedOn="{StaticResource borderBaseStyle}"/>

    <!-- hover styles -->
    <Style x:Key="onmouseover" TargetType="{x:Type Border}" BasedOn="{StaticResource borderBaseStyle}">
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="DarkBlue"/>
            </Trigger>
        </Style.Triggers>
    </Style>

</UserControl.Resources>
<Grid>
    <StackPanel Width="390">
    <!-- Header-->
    <Border  BorderThickness="1,1,1,1">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="55"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="70"/>
            </Grid.ColumnDefinitions>

            <Button Command="{Binding SortFirstNameCommand}" Style="{StaticResource headerButtonStyle}" Grid.Column="1" >
                <TextBlock   TextAlignment="Left" Text="First"/>
            </Button>
            <Button Style="{StaticResource headerButtonStyle}" Grid.Column="2" >
                <TextBlock TextAlignment="Left"  Text="Last"/>
            </Button>
            <Button Style="{StaticResource headerButtonStyle}"  Grid.Column="3" >
                <TextBlock TextAlignment="Left"  Text="Activity"/>
            </Button>
            <Button Style="{StaticResource headerButtonStyle}"  Grid.Column="4">
                <TextBlock TextAlignment="Left"  Text="Last Activity"/>
            </Button>
        </Grid>
    </Border>
    <Border BorderThickness="1,0,1,1">
        <ItemsControl VirtualizingStackPanel.IsVirtualizing="True"  VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding UsersAvailForEmail}" MaxHeight="400">
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Disabled" 
                                                          VerticalScrollBarVisibility="Auto" IsDeferredScrollingEnabled="True" Padding="{TemplateBinding Padding}">
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Style="{StaticResource onmouseover}" BorderThickness="0,0,1,1">
                        <CheckBox Margin="20,5" IsChecked="{Binding IsSelected}">
                            <CheckBox.Content>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Width="20"/>
                                    <TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="1" Text="{Binding FirstName}"/>
                                    <TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="2" Text="{Binding LastName}"/>
                                    <TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="3" Text="{Binding Action}"/>
                                    <TextBlock Width="60" Style="{StaticResource textBlockBaseStyle}" Grid.Column="4" Text="{Binding ActionId}"/>
                                </StackPanel>
                            </CheckBox.Content>
                        </CheckBox>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

    </Border>
</StackPanel>


</Grid>

`

BindingViewModel.cs:

using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Data;

namespace LargeItemsCollection
{
    public class BindingViewModel : INotifyPropertyChanged
    {
        ObservableCollection<EmailPersonViewModel> _usersAvail = new ObservableCollection<EmailPersonViewModel>();
        ListCollectionView _lvc = null;

        public BindingViewModel()
        {

            for (int i = 0; i < 4000; i++)
            {
                _usersAvail.Add( new EmailPersonViewModel { FirstName = "f" +i.ToString() , LastName= "l" + i.ToString(), Action= "a" + i.ToString(), ActionId="ai" + i.ToString(), IsSelected=true });
            }

             _lvc = new ListCollectionView(_usersAvail);

        }

        public ICollectionView UsersAvailForEmail
        {
            get { return _lvc; }
        }


        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        public virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
    }

EmailPersonViewModel.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LargeItemsCollection
{
    public class EmailPersonViewModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Action { get; set; }
        public string ActionId { get; set; }

        public bool IsSelected { get; set; }

        public EmailPersonViewModel()
        {

        }

    }
}

MainWindow.xaml:

<Window x:Class="LargeItemsCollection.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:LargeItemsCollection"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:BigList/>
    </Grid>
</Window>

MainWindow.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace LargeItemsCollection
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new BindingViewModel();
        }
    }
}
Dave Hanson
  • 495
  • 4
  • 11

3 Answers3

3

All righty per this post:

Virtualizing a WPF ItemsControl

I didn't have the Scrollviewer.CanScroll='true' in WPF virtualizationstackpanel doesn't seem to work without it.

Thanks all who posted solutions to help me along the way. You guys didn't have the solution so I didn't mark it as correct; however, I gave you guys upvotes because you both helped along the way. Here's the final XAML so you can see what the itemscontrol needs to look like:

  <ItemsControl ScrollViewer.CanContentScroll="True"  VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding UsersAvailForEmail}" MaxHeight="400">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <VirtualizingStackPanel />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                            <ItemsControl.Template>
                    <ControlTemplate>
                        <ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Disabled" 
                                                              VerticalScrollBarVisibility="Auto" IsDeferredScrollingEnabled="True" Padding="{TemplateBinding Padding}">
                            <ItemsPresenter/>
                        </ScrollViewer>
                    </ControlTemplate>
                </ItemsControl.Template> 
                <ItemsControl.ItemTemplate>
                    <DataTemplate>

                        <Border Style="{StaticResource onmouseover}" BorderThickness="0,0,1,1">
                            <CheckBox Margin="20,5" IsChecked="{Binding IsSelected}">
                                <CheckBox.Content>
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Width="20"/>
                                        <TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="1" Text="{Binding FirstName}"/>
                                        <TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="2" Text="{Binding LastName}"/>
                                        <TextBlock Width="70" Style="{StaticResource textBlockBaseStyle}" Grid.Column="3" Text="{Binding Action}"/>
                                        <TextBlock Width="60" Style="{StaticResource textBlockBaseStyle}" Grid.Column="4" Text="{Binding ActionId}"/>
                                    </StackPanel>
                                </CheckBox.Content>
                            </CheckBox>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
Community
  • 1
  • 1
Dave Hanson
  • 495
  • 4
  • 11
2

The virtualizing stack panel virtualizes creation of objects in the visual tree, not the objects in the ItemsSource. If it takes 4 seconds to create the collection of view model objects that the control is bound to, it's going to take 4 seconds to display the control irrespective of whether or not it uses virtualization.

Since you're sorting the collection, you have to instantiate all of the items in the collection before the control can display any of them. However long it takes to construct all 4,000 objects is a fixed cost here.

An easy way to test this is to create a command that builds the collection of objects and a second command that displays the items control bound to the collection. Then you can watch in the UI and see how long it takes to do each of those things separately.

If this is in fact the problem, you can focus on speeding up object creation (e.g. by using lazy evaluation on properties that are time-consuming to access, assuming they're not used by the sort), or possibly populate the collection in the background.

Robert Rossney
  • 87,288
  • 24
  • 136
  • 211
  • Thanks for the reply. I put a timer on my Object creation and it was less than a second (no db access mostly refrence passing in the EmailPersonViewModel). I reproduced the problem in a sample project and posted it above... I'm going to try tweaking the UI virtualization settings and see what happens. – Dave Hanson Jan 01 '11 at 15:02
1

I guess it's due to the sorting approach that you've taken. Under the hood it uses reflection and this is quite a bottleneck. Consider using CustomSort property of the ListCollectionView class. More links in this answer.

Hope this helps.

Community
  • 1
  • 1
Anvaka
  • 14,938
  • 2
  • 41
  • 52
  • I added in the CustomSort and it cut the sorting time down; however the Grid described above still takes 5 seconds to appear. I think it has to do with the object construction or my use of a grid in the datatemplate. If I ctrl+alt+break during the delay in the callstack all I get is [external code] – Dave Hanson Dec 30 '10 at 15:58