显示在创建大量控件时更新的进度条

时间:2011-09-01 14:06:59

标签: c# wpf progress-bar multithreading

我编写了一段代码,可以在画布上创建大量控件并将它们布局,以显示树。现在这段代码可能需要花费很多时间,特别是因为它有时必须查询外部服务以查看是否有更多的子节点。

所以我想在执行此代码时显示进度条。对于我的程序的其他部分,我使用报告进度的后台工作程序。但是,由于我必须创建以后可以交互的控件,所以我不会在这里看到如何使用后台工作程序或其他线程解决方案。

由于这是WPF,我也无法调用Application.DoEvents()。所以我的问题是,如何在能够定期更新GUI的可视部分的同时创建大量控件?

对于我的其他代码,我使用的是我在应用程序繁忙部分布局的Adorner,我更喜欢可以继续使用的解决方案,我还是更喜欢使用BackgroundWorker的解决方案,但我很确定这是不可能的。

我看过其他SO主题,但到目前为止我找不到一个好的答案

Creating controls in a non-UI thread

Creating a WinForm on the main thread using a backgroundworker

编辑:

根据这篇MSDN文章http://msdn.microsoft.com/en-us/magazine/cc163328.aspx,如果需要,BackgroundWorker应该自动在UI线程上异步调用,但这不是我看到的行为,因为我仍然看到一个跨线程异常。

Edit2:nvm,这不完全正确:BackgroundWorker still needs to call Invoke?

Edit3:经过一些阅读和一些提示后,这就是我的解决方案。有人得到任何提示/提示吗?

        // Events for reporting progress
        public event WorkStarted OnWorkStarted;
        public event WorkStatusChanged OnWorkStatusChanged;
        public event WorkCompleted OnWorkCompleted;


        private BackgroundWorker worker;
        private delegate void GuiThreadWork(object state);
        private PopulatableControlFactory factory = new PopulatableControlFactory();
        public Canvas canvas;

        public void PerformLayout(TreeNode node)
        {
            OnWorkStarted(this, "Testing");

            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);            
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            worker.RunWorkerAsync(node);
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnWorkCompleted(this);
        }

        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            var workTuple = (Tuple<GuiThreadWork, TreeNode>)e.UserState;
            workTuple.First.Invoke(workTuple.Second); //Or begin invoke?

            if (OnWorkStatusChanged != null)
                OnWorkStatusChanged(this, e.ProgressPercentage);
        }

        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            TreeNode node = (TreeNode)e.Argument;            
            Thread.Sleep(1000);
            worker.ReportProgress(33, Tuple.New(Place(node), node));
            Thread.Sleep(1000);
            worker.ReportProgress(66, Tuple.New(Place(node.children[0]), node.children[0]));
            Thread.Sleep(1000);
            worker.ReportProgress(100, Tuple.New(Place(node.children[1]), node.children[1]));
        }

        private GuiThreadWork Place(TreeNode node)
        {            
            GuiThreadWork threadWork = delegate(object state)
            {
                PopulatableControl control = factory.GetControl((TreeNode)state);
                Canvas.SetLeft(control, 100);
                Canvas.SetTop(control, 100);
                canvas.Children.Add(control);
            };

            return threadWork;
        }   

简而言之:我使用后台工作程序的progressChanged事件,因为它总是被编组到GUI线程。我传递了一个代表和一些状态的元组。这样我总是在GUI线程上创建控件并在那里执行所有操作,同时仍然保持灵活性。

1 个答案:

答案 0 :(得分:0)

通常我不经常使用BackgroundWorker,但我可以建议如下:

DoWork的逻辑 - 它在非UI线程上执行

  1. 获取节点数,以便报告实际进度
  2. 开始构建树(并在Invoke上调用UI Dispatcher以便UI线程 正在添加节点)并将进度报告给ReportProgress(已经 添加节点)/(总计数节点),同时枚举所有节点
  3. ProgressChanged中的

    只需使用新值

    更新一些ProgressBar