【问题标题】:How to stop UI being locked when adding things to the UI向 UI 添加内容时如何阻止 UI 被锁定
【发布时间】:2015-01-12 17:06:14
【问题描述】:

我有 250 个视觉对象,我需要将它们添加到 WrapPanel。当我这样做时,UI 会锁定几秒钟,这不是我想要的。有没有办法阻止这种情况?

private void ButtonClick()
{
   Foreach(Visual item in ListOfVisuals)
   {
      WrapPAnel.Children.Add(item);
   }
}

据我所知,我无法创建新的 Task.Run() => ButtonClick 来执行此操作,因为任务无法访问 UI。

【问题讨论】:

  • 您是实现了ListOfVisuals 还是在访问它们时创建了新元素?即,您可以在将它们添加到包装面板之前优化创建吗?
  • 你应该分析一下到底是什么消耗了所有的时间。 250 个对象看起来并不多,我很惊讶它花了几秒钟。我怀疑枚举ListOfVisuals 相对耗时,或者您的布局(例如数据绑定或大量布局计算)导致速度变慢。

标签: c# wpf multithreading


【解决方案1】:

这里的实际问题是您添加的每个项目都会发出更改通知。

您需要将WrapPAnel.Children 数据绑定到实现AddRangeINotifyCollectionChanged 实例。目标应该是属性更改事件在整个集合中只引发一次。

显而易见的答案是,当然,只需编写一个迭代的方法 在输入集合上并为每个调用 Add 。但这真的 不是答案,因为问题实际上与 AddRange 无关—— 问题实际上是关于引发单个 CollectionChanged 事件 添加多个项目时

你是如何实现的?

    public void AddRange(IEnumerable<T> dataToAdd)

    {

        this.CheckReentrancy();



        //

        // We need the starting index later

        //

        int startingIndex = this.Count;



        //

        // Add the items directly to the inner collection



        //

        foreach (var data in dataToAdd)

        {

            this.Items.Add(data);

        }



        //

        // Now raise the changed events

        //

        this.OnPropertyChanged("Count");

        this.OnPropertyChanged("Item[]");



        //

        // We have to change our input of new items into an IList since that is what the

        // event args require.

        //

        var changedItems = new List<T>(dataToAdd);

        this.OnCollectionChanged(changedItems, startingIndex);

    }

来源: http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx

但是等等,你不能像那样绑定Wrappanel!您将需要使用ItemsControl,其ItemsPanelTemplate 设置为WrapPanel。看这个例子http://tech.pro/tutorial/830/wpf-tutorial-using-an-itemspanel

<ItemsControl>
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <Image Source="Images\Aquarium.jpg" Width="100"/>
  <Image Source="Images\Ascent.jpg" Width="50"/>
  <Image Source="Images\Autumn.jpg" Width="200"/>
  <Image Source="Images\Crystal.jpg" Width="75"/>
  <Image Source="Images\DaVinci.jpg" Width="125"/>
  <Image Source="Images\Follow.jpg" Width="100"/>
  <Image Source="Images\Friend.jpg" Width="50"/>
  <Image Source="Images\Home.jpg" Width="150"/>
  <Image Source="Images\Moon flower.jpg" Width="100"/>
</ItemsControl> 

【讨论】:

    【解决方案2】:

    问题更可能是因为每个Add 调用都会导致重新布局和重绘。

    winforms 中,解决方案是在添加多个控件时使用SuspendLayout/ResumeLayout,并且通常启用双缓冲以避免闪烁。

    您可以搜索它是如何在 wpf 中完成的(例如,here 是什么)。但我建议您简单地隐藏 (Visibility.Collapsed) 容器,直到您完成添加 250 个控件。显示一些进度指示器(矢量视觉动画或简单的gif)会很酷,比如

    http://www.sponlytix.com/Images/Others/Loading.gif

    【讨论】:

    • 不。折叠会导致重绘。
    • @Gusdor,是的,重绘一次。而不是250。那不是很漂亮吗?还是你的意思是别的?
    • 重绘的成本取决于您的场景图。一个大的隐式大小的图将是一个昂贵的调整大小。我用我对解决方案的想法给出了答案。
    • 屏幕上的内容并不重要,但您至少需要一次重绘(当然是在布局之后)。不可能阻止它。你提到调整大小,但我没有看到 OP 在哪里提到它。他的初始显示有问题。为了调整复杂布局的大小,应该应用不同的解决方案(使用某种虚拟化)。关于您的解决方案:数据绑定在 wpf 中是首选,所以它更好,但是对于现有的代码隐藏代码隐藏容器很便宜并且应该可以解决问题(强调 should,我真的不费心去测试它=P)。
    • 让我直截了当地说,您是在告诉我,对于 WPF 性能而言,屏幕上的内容并不重要,它应该可以工作,但您尚未对其进行测试。我建议您运行该测试。我甚至会将此解决方案标记为 hack。对不起:(
    【解决方案3】:

    试试这个:

    this.Dispatcher.BeginInvoke(new Action(delegate
    {
        foreach(Visual item in ListOfVisuals)
        {
            WrapPAnel.Children.Add(item);
        }
    }), DispatcherPriority.Background);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多