【问题标题】:Slow wpf ItemsControl refresh on ListCollectionViewListCollectionView 上的 wpf ItemsControl 刷新缓慢
【发布时间】:2010-12-30 15:31:06
【问题描述】:

我的问题是我的 listcollectionview 需要 3-4 秒才能更新。我认为我已经正确配置了虚拟化;但是,如果不正确,请指出。我将 itemssource 绑定到 ICollectionView。

如果我在 BindingViewModel UI 中将循环设置为 3000+,则启动需要很长时间,即使构建 ViewModel 只需不到一秒。

我不知道如何附加文件,所以我将发布所有重现此问题的示例代码:

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();
        }
    }
}

【问题讨论】:

    标签: wpf


    【解决方案1】:

    按照这篇文章说得对:

    Virtualizing a WPF ItemsControl

    我没有 WPF 中的 Scrollviewer.CanScroll='true' 虚拟化stackpanel 没有它似乎无法工作。

    感谢所有发布解决方案以帮助我的人。你们没有解决方案,所以我没有将其标记为正确;但是,我给你们投了赞成票,因为你们一路上都提供了帮助。这是最终的 XAML,因此您可以看到 itemscontrol 的外观:

      <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>
    

    【讨论】:

      【解决方案2】:

      虚拟化堆栈面板虚拟化可视树中对象的创建,而不是 ItemsSource 中的对象。如果创建控件绑定的视图模型对象的集合需要 4 秒,那么无论是否使用虚拟化,都需要 4 秒来显示控件。

      由于您正在对集合进行排序,因此您必须先实例化集合中的所有项目,然后控件才能显示其中的任何项目。构建所有 4,000 个对象所需的时间是固定成本。

      一个简单的测试方法是创建一个构建对象集合的命令和显示绑定到集合的项目控件的第二个命令。然后,您可以在 UI 中观看,看看分别执行这些操作需要多长时间。

      如果这确实是问题所在,您可以专注于加速对象创建(例如,通过对访问耗时的属性使用惰性求值,假设它们没有被排序使用),或者可能填充在后台收集。

      【讨论】:

      • 感谢您的回复。我在我的对象创建上设置了一个计时器,它不到一秒(没有数据库访问主要是在 EmailPersonViewModel 中传递的参考)。我在一个示例项目中重现了该问题并将其发布在上面...我将尝试调整 UI 虚拟化设置,看看会发生什么。
      【解决方案3】:

      我猜这是由于您采用的排序方法。在引擎盖下它使用反射,这是一个相当大的瓶颈。考虑使用ListCollectionView 类的CustomSort 属性。更多链接在此answer

      希望这会有所帮助。

      【讨论】:

      • 我在 CustomSort 中添加了它,它缩短了排序时间;但是上面描述的网格仍然需要 5 秒才能出现。我认为这与对象构造或我在数据模板中使用网格有关。如果我在调用堆栈的延迟期间按 ctrl+alt+break 我得到的只是[外部代码]
      猜你喜欢
      • 2020-12-06
      • 1970-01-01
      • 2016-06-04
      • 2013-12-21
      • 1970-01-01
      • 2019-01-21
      • 2011-02-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多