如何正确改进ItemsControl加载并避免冻结

时间:2017-06-01 08:28:17

标签: c# wpf xaml animation itemscontrol

我想知道改善ItemsControl加载的正确方法是什么。我想显示加载非常繁重的项目(由于复杂的XAML),我不希望我的WPF应用程序冻结。我希望我的项目一旦呈现就会显示出来,并且能够同时与我窗口的其他元素进行交互(即使它有点慢)。以下是一些假设:

  1. 假设我无法改善项目加载的时间;
  2. 我无法使用虚拟化,因为我需要立即查看所有项目;
  3. 视图不等待后台操作完成,所有工作都在UI线程上完成。
  4. 我已经找到了解决方法,但我对它感到不满意,使用BackgroundWorker和睡眠:

    enter image description here

    MainWindow.xaml

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
            xmlns:local="clr-namespace:WpfApplication1">
        <DockPanel>
            <Button DockPanel.Dock="Bottom" Content="Click me" Click="OnButtonClick"/>
    
            <ItemsControl ItemsSource="{Binding Items}">
                <!-- Items begin to be loaded once the ItemsControl is loaded -->
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Loaded">
                        <i:InvokeCommandAction Command="{Binding LoadCommand}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border Width="25" Height="25" Background="Red" Margin="5">
                            <local:HeavyItem Content="{Binding}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </DockPanel>
    </Window>
    

    ViewModel.cs

    public class ViewModel
    {
        public ICommand LoadCommand { get; protected set; }
    
        public ObservableCollection<int> Items { get; protected set; }
    
        public ViewModel()
        {
            Items = new ObservableCollection<int>();
            LoadCommand = new DelegateCommand(AsyncLoad);
            //Load();
        }
    
        protected void Load()
        {
            for (int i = 0; i < 250; i++)
            {
                Items.Add(i);
            }
        }
    
        protected void AsyncLoad()
        {
            var bk = new BackgroundWorker();
            bk.DoWork += (s, e) =>
            {
                for (int i = 0; i < 250; i++)
                {
                    // Sleep 50ms to let the UI thread breeze
                    Thread.Sleep(50);
                    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render, (Action)(() =>
                    {
                        Items.Add(i);
                    }));
                }
            };
            bk.RunWorkerAsync();
        }
    }
    

    HeavyItem.cs(虚假控件模拟沉重的视觉效果)

    public class HeavyItem : ContentControl
    {
        protected override Size ArrangeOverride(Size finalSize)
        {
            Thread.Sleep(20);
            return base.ArrangeOverride(finalSize);
        }
    }
    

    This question提供了类似的方法。我不喜欢这种方法,因为:

    1. ViewModel正在执行视图应该执行的操作;
    2. 我使用任意计时器,而该项目可能需要更多或更少的时间来渲染,具体取决于计算机。
    3. 我认为最好的方法是覆盖ItemsControl并告诉UI线程在添加项目后更新,但我没有设法做到这一点。任何线索或想法?

1 个答案:

答案 0 :(得分:0)