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

Posted

技术标签:

【中文标题】如何正确改善 ItemsControl 加载并避免冻结【英文标题】:How to properly improve ItemsControl loading and avoid freezing 【发布时间】:2017-11-02 07:04:41 【问题描述】:

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

    假设我无法缩短加载项目的时间; 我无法使用虚拟化,因为我需要一次查看所有项目; 视图不等待后台操作完成,所有工作都在 UI 线程上完成。

我已经找到了解决方法,但我对此不满意,使用 BackgroundWorker 和睡眠:

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 提供了类似的方法。我不喜欢这种方法,因为:

    ViewModel 正在做视图应该做的事情; 我使用任意计时器,而项目可能需要或多或少的时间来渲染,具体取决于计算机。

我认为最好的方法是重写 ItemsControl 并告诉 UI 线程在添加项目后进行更新,但我没有设法做到这一点。有什么线索或想法吗?

【问题讨论】:

你不能使用async,但是你可以使用BackgroundWorker。你不觉得这里有什么矛盾吗? 或者你可以使用Loaded事件。 @Dennis 是的,你是对的,我将编辑我的问题。我只是想说视图没有等待后台操作完成。 @PatrikKučera 是的,我想是的,但我不知道应该在哪里以及如何处理被覆盖的 ItemsControl 中项目的 Loaded 事件。 @Max 如果您在 MainWindow 中加载项目,您可以使用 MainWindow.Loaded 并简单地隐藏该窗口,直到所有内容都加载完毕 【参考方案1】:

使用 CollectionView.DeferRefresh() 获取一次更新?

https://blogs.msdn.microsoft.com/matt/2008/08/28/collectionview-deferrefresh-my-new-best-friend/

【讨论】:

感谢您的回答,但我不认为 DeferRefresh 可以成为解决方案,因为我希望我的窗口更频繁地刷新(每次呈现项目时),就像在我当前的解决方法中一样。 问题是你用 Do.Work 启动一个线程,在里面你用 Sleep(50) 停止它,然后在渲染优先级中调用 gui 线程上的委托以在集合中添加一个项目;但是您在 item_arrange 中使用另一个 Sleep(20) 暂停 gui 线程,然后返回线程....这会导致大量上下文滑动并阻塞 gui。相信我 - 我用成千上万的位图填充 CBR 程序的 gui 而不会冻结 需要明确的是,ArrangeOverride 中的 Sleep(20) 不是我在实际代码中拥有的,这些项目非常复杂,我不想在这里发布我的所有代码。他们只是需要时间来渲染。我对 Sleep(50) 不满意,这就是我问这个问题的原因。而且我不明白如何使用 DeferRefresh 来帮助我。如果我想显示位图,我知道我可以轻松地在后台加载位图,这里的问题是我想显示复杂的 WPF 控件 好吧,我看不出复杂的 wpf 控件是什么意思,任何 gui 都包含数千个以毫秒为单位呈现的 xaml 元素...是否编写了复杂的用户控件?也许问题出在其他地方?

以上是关于如何正确改善 ItemsControl 加载并避免冻结的主要内容,如果未能解决你的问题,请参考以下文章

WPF: WrapPanel 容器的模板数据绑定(ItemsControl)

ItemsControl 组合框 selecteditem C# WPF MVVM

在 Ionic 中更改 HTML 时避免整页重新加载

开发 QFutureWatcher 以改善图像加载时间的问题

如何改善 UITableview 中的加载图像?

markdown 如何加载Web字体以避免性能问题并加快页面加载速度