Using MVVM on a View that has a long running process - c#

I have an example here that replicates what I am trying to accomplish.
As the following code will show - I have ViewModel that updates a ObservableCollection Property bound to a View. Normally I would update the collection from a result retrieved from a Model, but hopefully this example will suffice.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Controls;
namespace MVVMWpf.ViewModel
{
public class ListViewModel
{
public ObservableCollection<int> SomeObjectCollection { get; set; }
public ListViewModel()
{
SomeObjectCollection = new ObservableCollection<int>();
}
public void Do()
{
for (int i = 1; i < 1000000; i++)
{
int i1 = i;
SomeObjectCollection.Add(i1);
}
}
}
}
Unfortunately, this blocks this UI. It will only update the View when loop runs to completion. The way I solved it breaks MVVM concepts. That's why I need your help. I did it this way.
public class ListViewModel
{
private delegate void LongRunningProcess();
public ObservableCollection<int> SomeObjectCollection { get; set; }
private ListBox listBox;
public ListViewModel(ListBox listBox)
{
this.listBox = listBox;
SomeObjectCollection = new ObservableCollection<int>();
}
public void Do()
{
Thread thread = new Thread(() =>
{
for (int i = 1; i < int.MaxValue; i++)
{
int i1 = i;
listBox.Dispatcher.Invoke(
new LongRunningProcess(() =>
SomeObjectCollection.Add(i1);
}});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
As you can see the ViewModel now knows about the listBox element from the UI. And looking at the MVVM diagram only the View should have a reference to the ViewModel through binding. How does one overcome this issue?
Thanks.

You need to let your loop unleash updates to the screen - some kind of DoEvents() will do:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Background,new Action(delegate { }));
}
Add it and call that from within your loop.
Using timer as another option, your code should look somthing like that:
private System.Timers.Timer operationsTimer = new System.Timers.Timer();
private int x;
in your ctor:
operationsTimer.Elapsed += new System.Timers.ElapsedEventHandler
(operationsTimer_Elapsed);
operationsTimer.Enabled = true;
in your timer elpased:
operationsTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
operationsTimer.Enabled = false;
//add item to collection code
x++;
if(x<100)
operationsTimer.Enabled = true;
}

Consider using a BackgroundWorker, an easy way of performing async tasks with functionality for reporting progress as well as a completed event. Best of all, you don't have to invoke anything on the dispatcher, as the BackgroundWorker's functions are synchronized to the UI thread.

Related

Asynchronous operations within a loop - how to keep control of execution?

Follow-on question to this one.
I am trying to generate and save a series of images. Rendering is done by Helix Toolkit, which I am told utilises the WPF composite render thread. This is causing problems because it executes asynchronously.
My original problem was that I couldn't save a given image because it hadn't yet been rendered at that time I was trying to save it. The above answer provides a workaround to this by putting the 'save' operation inside an Action which is called with low priority, thus ensuring that the rendering completes first.
This is fine for one image, but in my application I require multiple images. As it stands I cannot keep control of the sequence of events because they occur asynchronously. I am using a For loop which just continues regardless of the progress of rendering and saving the images. I need the images to be generated one by one, with enough time for rendering and saving before starting the next one.
I have tried putting delays in the loop but that causes its own problems. For instance an async await as commented in the code causes cross-threading issues because the data was created on a different thread from where the rendering is being done. I tried putting in a simple delay but then that just locks everything up - I think in part because the save operation I am waiting on has very low priority.
I cannot simply treat it as a batch of separate unrelated asynchronous tasks because I am using a single HelixViewport3D control in the GUI. The images have to be generated sequentially.
I did try a recursive method where SaveHelixPlotAsBitmap() calls DrawStuff() but that wasn't working out very well, and it doesn't seem a good approach.
I tried setting a flag ('busy') on each loop and waiting for it to be reset before continuing but that wasn't working - again, because of the asynchronous execution. Similarly I tried using a counter to keep the loop in step with the number of images that had been generated but ran into similar problems.
I seem to be going down a rabbit hole of threading and asynchronous operations that I don't want to be in.
How can I resolve this?
class Foo {
public List<Point3D> points;
public Color PointColor;
public Foo(Color col) { // constructor creates three arbitrary 3D points
points = new List<Point3D>() { new Point3D(0, 0, 0), new Point3D(1, 0, 0), new Point3D(0, 0, 1) };
PointColor = col;
}
}
public partial class MainWindow : Window
{
int i = -1; // counter
public MainWindow()
{
InitializeComponent();
}
private void Go_Click(object sender, RoutedEventArgs e) // STARTING POINT
{
// Create list of objects each with three 3D points...
List<Foo> bar = new List<Foo>(){ new Foo(Colors.Red), new Foo(Colors.Green), new Foo(Colors.Blue) };
foreach (Foo b in bar)
{
i++;
DrawStuff(b, SaveHelixPlotAsBitmap); // plot to helixViewport3D control ('points' = list of 3D points)
// This is fine the first time but then it runs away with itself because the rendering and image grabbing
// are asynchronous. I need to keep it sequential i.e.
// Render image 1 -> save image 1
// Render image 2 -> save image 2
// Etc.
}
}
private void DrawStuff(Foo thisFoo, Action renderingCompleted)
{
//await System.Threading.Tasks.Task.Run(() =>
//{
Point3DCollection dataList = new Point3DCollection();
PointsVisual3D cloudPoints = new PointsVisual3D { Color = thisFoo.PointColor, Size = 5.0f };
foreach (Point3D p in thisFoo.points)
{
dataList.Add(p);
}
cloudPoints.Points = dataList;
// Add geometry to helixPlot. It renders asynchronously in the WPF composite render thread...
helixViewport3D.Children.Add(cloudPoints);
helixViewport3D.CameraController.ZoomExtents();
// Save image (low priority means rendering finishes first, which is critical)..
Dispatcher.BeginInvoke(renderingCompleted, DispatcherPriority.ContextIdle);
//});
}
private void SaveHelixPlotAsBitmap()
{
Viewport3DHelper.SaveBitmap(helixViewport3D.Viewport, $#"E:\test{i}.png", null, 4, BitmapExporter.OutputFormat.Png);
}
}
Note These examples are just to prove a concept, there is work needed on the TaskCompletionSource to handle errors
Given this test window
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel x:Name="StackPanel"/>
</Grid>
</Window>
Here is an example of how to use events to know when the view is in the state that you want.
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp2
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DoWorkAsync();
}
private async Task DoWorkAsync()
{
for (int i = 0; i < 10; i++)
{
await RenderAndCapture();
}
}
private async Task RenderAndCapture()
{
await RenderAsync();
CaptureScreen();
}
private Task RenderAsync()
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
var panel = new TextBlock {Text = "NewBlock"};
panel.Loaded += OnPanelOnLoaded;
StackPanel.Children.Add(panel);
void OnPanelOnLoaded(object sender, RoutedEventArgs args)
{
panel.Loaded -= OnPanelOnLoaded;
taskCompletionSource.TrySetResult(null);
}
});
return taskCompletionSource.Task;
}
private void CaptureScreen()
{
// Capture Image
}
}
}
If you want to have your sync method called from outside you can implement a task queue.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp2
{
public class TaskQueue
{
private readonly SemaphoreSlim _semaphore;
public TaskQueue()
{
_semaphore = new SemaphoreSlim(1);
}
public async Task Enqueue(Func<Task> taskFactory)
{
await _semaphore.WaitAsync();
try
{
await taskFactory();
}
finally
{
_semaphore.Release();
}
}
}
public partial class MainWindow : Window
{
private readonly TaskQueue _taskQueue;
public MainWindow()
{
_taskQueue = new TaskQueue();
InitializeComponent();
DoWork();
}
private void DoWork()
{
for (int i = 0; i < 10; i++)
{
QueueRenderAndCapture();
}
}
private void QueueRenderAndCapture()
{
_taskQueue.Enqueue(() => RenderAndCapture());
}
private async Task RenderAndCapture()
{
await RenderAsync();
CaptureScreen();
}
private Task RenderAsync()
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
var panel = new TextBlock {Text = "NewBlock"};
panel.Loaded += OnPanelOnLoaded;
StackPanel.Children.Add(panel);
void OnPanelOnLoaded(object sender, RoutedEventArgs args)
{
panel.Loaded -= OnPanelOnLoaded;
taskCompletionSource.TrySetResult(null);
}
});
return taskCompletionSource.Task;
}
private void CaptureScreen()
{
// Capture Screenshot
}
}
}
This will make sure the UI is in the state required for each iteration
You will of course need to expand this so that you listen to the Loaded event of each point that you wish to render.
Edit:
As PointsVisual3D does not have the Loaded event you can complete the task by hooking onto the event you had previously used. Not ideal, but it should work.
private Task RenderAsync()
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
var panel = new TextBlock {Text = "NewBlock"};
StackPanel.Children.Add(panel);
Dispatcher.BeginInvoke(new Action(() =>
{
taskCompletionSource.TrySetResult(null);
}), DispatcherPriority.ContextIdle);
});
return taskCompletionSource.Task;
}
Solution below. This is my implementation of the code provided in Jason's answer. All credit to Jason for the important bits.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Go_Click(object sender, RoutedEventArgs e) // STARTING POINT
{
DoWorkAsync();
}
private async Task DoWorkAsync()
{
// Create list of objects each with three 3D points...
List<Foo> bar = new List<Foo>() { new Foo(Colors.Red), new Foo(Colors.Green), new Foo(Colors.Blue) };
int i = -1; // init counter
foreach (Foo b in bar)
{
i++;
await RenderAndCapture(b, i);
}
}
private async Task RenderAndCapture(Foo b, int i)
{
await RenderAsync(b);
SaveHelixPlotAsBitmap(i);
}
private Task RenderAsync(Foo b)
{
var taskCompletionSource = new TaskCompletionSource<object>();
Dispatcher.Invoke(() =>
{
DrawStuff(b);
Dispatcher.BeginInvoke(new Action(() =>
{
taskCompletionSource.TrySetResult(null);
}), DispatcherPriority.ContextIdle);
});
return taskCompletionSource.Task;
}
private void DrawStuff(Foo thisFoo)
{
Point3DCollection dataList = new Point3DCollection();
PointsVisual3D cloudPoints = new PointsVisual3D { Color = thisFoo.PointColor, Size = 5.0f };
foreach (Point3D p in thisFoo.points)
{
dataList.Add(p);
}
cloudPoints.Points = dataList;
// Add geometry to helixPlot. It renders asynchronously in the WPF composite render thread...
helixPlot.Children.Add(cloudPoints);
helixPlot.CameraController.ZoomExtents();
}
private void SaveHelixPlotAsBitmap(int i) // screenshot
{
Viewport3DHelper.SaveBitmap(helixPlot.Viewport, $#"E:\test{i}.png", null, 4, BitmapExporter.OutputFormat.Png);
}
}

Queue File Copy Operation using C# Queue<MethodInvoker>

I'm trying to create a file copy application. I have a BackgroudWorker doing the work and it works fine so far. How it works, I have a form, Source and Destination Folder fields and a Copy button. The Copy button triggers the Copy operation using the BackgroundWorker. The Progressbar gets updated etc. Now I need to implemet a queue type of operation. I need to add other Source and Destination Copy operation and add it to the queue. I tried using the following:
Queue<MethodInvoker> MyQueue = new Queue<MethodInvoker>();
MyQueue.Enqueue(new MethodInvoker(() =>CopyStuff(1)));
MyQueue.Enqueue(new MethodInvoker(() =>CopyStuff(2)));
MethodInvoker bb = MyQueue.Dequeue(); //(I Forgot this part)
bb();
bb = MyQueue.Dequeue();
bb();
The problem is, because it's a BackgroundWorker, it does not wait for the first operation to complete. Any suggestions on how to go about this?
After fixing my code it works, except for it running on the UI thread, locking controls.
Update 1:
This works, but runs on main thread, user can't use controls while it's running:
BlockingCollection<MethodInvoker> bCollection = new BlockingCollection<MethodInvoker>(boundedCapacity: 2);
Task producerThread = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 2; ++i)
{
bCollection.Add(CopyStuff);
}
bCollection.CompleteAdding();
});
foreach (MethodInvoker item in bCollection.GetConsumingEnumerable())
{
BeginInvoke(item);
}
Update 2:
Works in a Console App, but not a Windows Forms Application.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncCopy
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
public class AsyncCopy
{
private static Queue<Action> MyQueue = new Queue<Action>();
public async Task EnqueueAndCopy(Action[] actionList)
{
foreach (var action in actionList)
{
MyQueue.Enqueue(action);
}
while (MyQueue.TryDequeue(out var copyAction)) //Here's the problem on Windows Form Applivcation
{
//If the copyaction is itself async, await directly on the method here instead of running the action in a Task
await Task.Factory.StartNew(copyAction);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
var asyncCopy = new AsyncCopy();
//In a typical usage, this will call be awaited
//In your case, make this call from a async method and call it from the UI thread
asyncCopy.EnqueueAndCopy(
new Action[] {
() => CopyStuff (1),
() => CopyStuff (2)
}
);
//Loop to confirm the processing is asynchronous
for (int i = 0; i <= 20; i++)
{
Console.WriteLine($"{i}");
Thread.Sleep(300);
}
}
//Copy process simulation
static void CopyStuff(int i)
{
Console.WriteLine($"Copying {i}");
Thread.Sleep(3000);
Console.WriteLine($"Copied {i}");
}
}
}
If you need to Queue the Copy process asynchronously as the other copies are processing, I would recommend a producer consumer pattern. Refer https://www.dotnetcurry.com/patterns-practices/1407/producer-consumer-pattern-dotnet-csharp
But a simple async await would work in your case as well
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace stackoverflow {
class Program {
static void Main (string[] args) {
var asyncCopy = new AsyncCopy ();
//In a typical usage, this will call be awaited
//In your case, make this call from a async method and call it from the UI thread
asyncCopy.EnqueueAndCopy (
new Action[] {
() => CopyStuff (1),
() => CopyStuff (2)
}
);
//Loop to confirm the processing is asynchronous
for(int i=0; i <= 20; i++)
{
Console.WriteLine($"{i}");
Thread.Sleep(300);
}
}
//Copy process simulation
static void CopyStuff (int i) {
Console.WriteLine ($"Copying {i}");
Thread.Sleep(3000);
Console.WriteLine ($"Copied {i}");
}
}
public class AsyncCopy {
private static Queue<Action> MyQueue = new Queue<Action> ();
public async Task EnqueueAndCopy (Action[] actionList) {
foreach (var action in actionList) {
MyQueue.Enqueue (action);
}
while (MyQueue.TryDequeue (out var copyAction)) {
//If the copyaction is itself async, await directly on the method here instead of running the action in a Task
await Task.Factory.StartNew (copyAction);
}
}
}
}
Update
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async Task CopyAsync(IEnumerable<Action> actionList)
{
foreach (var action in actionList)
{
await Task.Factory.StartNew(action);
}
}
private async void button2_Click(object sender, EventArgs e)
{
await CopyAsync(
new Action[]
{
() => CopyStuff(1),
() => CopyStuff(2)
});
}
//Copy process simulation
static void CopyStuff(int i)
{
Thread.Sleep(3000);
MessageBox.Show(string.Format("File Copied {0}", i));
}
}
}

What is the proper method for using invoke to avoid threading errors?

I have been recently learning C# and have a problem I just cant seem to wrap my head around. Please forgive me if this is noobish as I am very new to C# but my question is about delegates and invoke.
I have read many many tutorials online and watched many video tutorials about this as well but I am still getting the same error in my code and I just dont seem to grasp the subtleties. As I understand it a delegate is a pointer to a function and can be used to invoke that function from say another thread to update a textbox. I understand creating a delegate, that much I think I am doing right but when I invoke the delegate from a threat I always get the error Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.
The callback appears to be functional as it is calling the function that it is designed to however it does not seem to do it on the correct thread which I thought was the whole point of doing it this way. I know there is the option to set that warning break to false but I would rather learn what I am doing wrong and how to code this type of method properly. I appreciate any help, suggestions or answers you can provide as I am not sure any more of the tutorials are getting me any closer at this point to understanding where I have gone wrong.
My code is very basic and as I am just trying to understand the most basic concepts of properly coding for multithreading. Below is my code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Media;
using System.Threading;
using System.Reflection;
namespace WindowsFormsApplication3
{
//declair delegate name(vars) CORRECT
public delegate void Textboxdelegate(Int64 MyVar);
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void button1_Click(object sender, EventArgs e)
{
runme();
}
public void runme()
{
textBox1.Text = "87";
textupdate(44);
int target = 0;
Textboxdelegate TD = new Textboxdelegate(textupdate);
Number number = new Number(target, TD);
TD(11);
Thread thread1 = new Thread(new ThreadStart(number.worker));
thread1.Start();
}
public void textupdate(Int64 cntr)
{
textBox1.Text += cntr.ToString();
}
}
class Number
{
int _target;
Textboxdelegate _callbackMethod;
public Number(int target, Textboxdelegate TDD)
{
this._target = target;
this._callbackMethod = TDD;
}
public void worker()
{
Int64 counter = 0;
byte[] lifeforms = new byte[2146435071];
for (long y = 1; y <= 2146; y++)
{
for (long X = 1; X <= 1000000; X++)
{
lifeforms[X * y] = 20;
}
counter += 1;
if(_callbackMethod != null)
{
_callbackMethod(counter);
}
}
MessageBox.Show("Done!");
}
}
}
This crude example should do it for what you want to do:
//public delegate void Textboxdelegate(Int64 MyVar);
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public Action<Int64> Textboxdelegate;
public void runme()
{
textBox1.Text = "87";
textupdate(44);
int target = 0;
Textboxdelegate = textupdate;
Number number = new Number(target, Textboxdelegate,this);
Textboxdelegate(11);
Thread thread1 = new Thread(new ThreadStart(number.worker));
thread1.Start();
}
public void textupdate(Int64 cntr)
{
textBox1.Text += cntr.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
runme();
}
}
class Number
{
int _target;
Action<Int64> _callbackMethod;
Form1 frm;
public Number(int target, Action<Int64> act,Form1 frm)
{
this._target = target;
this._callbackMethod = act;
this.frm = frm;
}
public void worker()
{
Int64 counter = 0;
byte[] lifeforms = new byte[214643507];
for (long y = 1; y <= 2146; y++)
{
for (long X = 1; X <= 100000; X++)
{
lifeforms[X * y] = 20;
}
counter += 1;
if (_callbackMethod != null)
{
if (frm.InvokeRequired)
{
frm.Invoke(_callbackMethod,new object[]{counter});
}
else
{
_callbackMethod(counter);
}
}
}
MessageBox.Show("Done!");
}
}
Controls have thread-afinity,meaning they can only be accessed from the thread that created them,and you where accessing them by another thread.Now the Invoke method of the Control class(the base of form and all its controls)will allow you to safelly access them(very summarized explanation).

Wait for Collection to be Updated

I need a certain bit of code to wait for an item to be added to a collection. The thread itself needs to wait until this completes before it continues. The item gets added to the collection from another thread (kicked off from elsewhere), and I have no way to signal the current thread that this has occurred.
A naive implementation might look something like (ignoring thread safety on the collection, etc. for now):
public static List<string> myList = new List<string>();
public void WaitingMethod(string id)
{
bool waiting = true;
while (waiting)
{
int index = myList.IndexOf(id);
if (index >= 0)
{
waiting = false;
}
}
//thread continues here
}
I believe this would block the thread and peg the processor.
What would be a more efficient way to achieve this?
Could you instead use an observable collection, and subscribe to CollectionChanged event?
namespace ConsoleApplication3
{
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
internal class Program
{
private static readonly ObservableCollection<string> MyList = new ObservableCollection<string>();
private static AutoResetEvent resetEvent = new AutoResetEvent(false);
private static void Main(string[] args)
{
Task.Factory.StartNew(
() =>
{
for (int i = 0; i < 10; i++)
{
string item = i.ToString("0000");
MyList.Add(item);
Console.WriteLine(item);
Thread.Sleep(1000);
}
});
MyList.CollectionChanged += (sender, eventArgs) =>
{ if (eventArgs.NewItems.Cast<string>().Any(a => a.Equals("0005"))) resetEvent.Set(); };
resetEvent.WaitOne();
}
}
}
Here is how you can also control the loop generating the items being added to the collection:
namespace ConsoleApplication3
{
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
internal class Program
{
#region Static Fields
private static readonly CancellationTokenSource Cts = new CancellationTokenSource();
private static readonly ObservableCollection<string> MyList = new ObservableCollection<string>();
private static readonly AutoResetEvent ResetEvent = new AutoResetEvent(false);
#endregion
#region Methods
private static void Main(string[] args)
{
Task task = Task.Factory.StartNew(
() =>
{
for (int i = 0; i < 10 && !Cts.IsCancellationRequested; i++)
{
string item = i.ToString("0000");
MyList.Add(item);
Console.WriteLine(item);
Thread.Sleep(100);
}
},
Cts.Token);
Task finish = task.ContinueWith(antecedent => { Console.WriteLine("Task finished. Status {0}", antecedent.Status); });
MyList.CollectionChanged += (sender, eventArgs) =>
{
if (eventArgs.NewItems.Cast<string>().Any(a => a.Equals("0005")))
{
Cts.Cancel();
ResetEvent.Set();
}
};
ResetEvent.WaitOne();
Task.WaitAll(finish);
}
#endregion
}
}
As the others have said, use ObservableCollection. But, you also need an AutoResetEvent:
In the class, during instantiation or program initialization, you register a Collection Changed event handler with the ObservableCollection. When this method is called, it calls the AutoResetEvent's Set method to signal that the collection was changed.
When your thread gets to the point that it needs to wait, you wait for the AutoResetEvent.
Your other thread does its thing and changes the collection. That calls the method you registered with the collection, which raises the AutoResetEvent. That signals your thread that's waiting to wake up.
This way, the thread that's waiting has no impact on the CPU.

Progress bar working but label is not updating

I have a library with an Interface.
Public Interface Progress
{
int ProgressValue{get;set;},
string ProgressText{get;set;},
}
Library has a method Create (dummy code):
Public Class TestLibrary
{
Progress _progress;
Public void Create()
{
foreach(var n in TestList)
{
// Do Something
_progress.ProgressValue = GetIndex(n);
_progress.ProgressText = "Updating..." + n;
}
}
}
I have a project that references this library and calls Create method. It even Implements Interface Progress.
Public Class TestProject : Progress
{
public int ProgressValue
{
get{return progressBar1.Value;}
set{progressBar1.Value = value;}
}
public int ProgressText
{
get{return label1.Text;}
set{label1.Text = value;}
}
}
Now when I run the application, Progress Bar behaves properly and shows the progress correctly, but the Text of label1 does not change at all. But it do change in the end of for loop and shows the last item in loop. Can anyone help me out in this?
Note: All these codes are written directly without testing as I don't have my application now with me. Sorry for any syntax errors.
It sounds like all your work is being done on the UI thread. Don't do that.
Instead, run the loop itself in a background thread, and use Control.Invoke or Control.BeginInvoke (probably in the Progress implementation) to marshal a call across to the UI thread just to update the UI. This will leave your UI responsive (and able to update labels etc) while it's still processing.
Used a Label instead of ProgressBar. You can try this code [using BackGroundWorker] -
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
private BackgroundWorker _worker;
BusinessClass _biz = new BusinessClass();
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
_worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
int junk = 0;
for (i = 0; i <= 199990000; i++)
{
int result = _biz.MyFunction(junk);
junk++;
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// note I can pass the business class result also and display the same in the LABEL
_worker.ReportProgress(percentComplete, result);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
}
else if (e.Error != null)
{
// Do something with the error
}
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
}
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
}
Posted the same a few days ago here
The code you posted uses one thread. That means every operation is excuted in sequence right after the previous one finished.
Since you can update GUI elements right away, I suppose the code to be run from main thread (a.k.a "GUI thread"). Blocking the GUI thread results in the GUI ("Graphical User Interface") not updating until there is some idle time for it.
Use following example to refresh your label every iteration so that it updates your UI.
label1.Text = i.ToString();
label1.Refresh();

Categories

Resources