开发者

Showing a progress bar that updates while creating a lot of controls

开发者 https://www.devze.com 2023-04-01 01:55 出处:网络
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 exter

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 t开发者_Go百科o. 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.


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

0

精彩评论

暂无评论...
验证码 换一张
取 消