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

发布于 2024-12-02 11:56:19 字数 3682 浏览 1 评论 0原文

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

所以我想在执行此代码时显示一个进度条。对于程序的其他部分,我使用后台工作人员来报告进度。但是,由于我必须创建稍后可交互的控件,因此我不知道如何在此处使用后台工作人员或其他线程解决方案。

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

对于我的其他代码,我使用一个装饰器,我将其布局在应用程序的繁忙部分上,我更喜欢一个可以继续使用它的解决方案,我也仍然更喜欢使用BackgroundWorker的解决方案,但我很确定这不是可能的。

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

在非 UI 线程中创建控件

使用后台工作程序在主线程上创建 WinForm

编辑:

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

Edit2:nvm,这并不完全正确: BackgroundWorker 仍然需要调用 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 线程上创建控件并在那里执行所有操作,同时仍然保持灵活性。

I've written a piece of code that creates a lot of controls and layouts them on a canvas to visualize a tree. Now this code can take a lot of time, especially since it sometimes has to query an external service to see if there are more child node.

So I would like to show a progress bar while this code is executing. For other parts of my program I use a background worker that reports progress. However since I have to create controls that are later interact-able I don't see how to use a background worker or other threading solution here.

Since this is WPF, I also can't call Application.DoEvents(). So my question is, how can I create a lot of controls while still being able to periodically update the visual part of the GUI?

For my other code I use an Adorner that I layout over the busy piece of my app, I would prefer a solution where I can keep using that, I would also still prefer a solution using BackgroundWorker, but I'm pretty sure that is not possible.

I've looked at other SO topics, but I can't find a good answer so far

Creating controls in a non-UI thread

Creating a WinForm on the main thread using a backgroundworker

Edit:

According to this MSDN article http://msdn.microsoft.com/en-us/magazine/cc163328.aspx the BackgroundWorker should automatically invoke asynchronously on the UI thread if required, but this is not the behaviour I'm seeing, since I still see a cross thread exception.

Edit2: nvm, that's not totally true: BackgroundWorker still needs to call Invoke?

Edit3: After some more reading and some tips, this is the solution I've come to. Anybody got any tips/hints?

        // 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;
        }   

In short: I use the progressChanged event of the background worker because this is always marshalled to the GUI thread. I pass it a tuple of a delegate and some state. This way I always create the control on the GUI thread and do all actions there, while still being flexible.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

遮云壑 2024-12-09 11:56:19

一般来说,我不经常使用BackgroundWorker,但我可以建议以下内容:

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

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

ProgressChanged 中的所有节点,只需用新值更新一些 ProgressBar

Generally I don't use BackgroundWorker often but I can suggest the following:

Logic for DoWork - its executed on non UI thread

  1. get count of nodes so you can report real progress
  2. begin building tree ( and call Invoke on UI Dispatcher so UI thread
    is adding nodes) and report progress to ReportProgress as (already
    added nodes)/(total count nodes) while enumerating through all nodes

in ProgressChanged simply update some ProgressBar with new value

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文