How to update the control in Loaded handler? - c#

The usual routine, progress bar, user clicks the button, process runs, progress bar is updated. Classic.
However I would like to run the process the moment entire app starts, so I run it in Loaded handler for Window (the only window in my app).
And now, the progress bar is not updated. How to overcome this problem?
Tester for updating progress bar.
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Render, new Action(delegate() { ++Progress.Value; }));
}

Because you put all the work on the UI thread, which is used to update the Progressbar, so the Progressbar is not updated until the work is done. Put the work on a background thread, and update the Progressbar from the background thread using Dispatcher.BeginInvoke.
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}
If you are using .NET 2.0 (not using TPL), you can use ThreadPool,
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem( (o) =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}
Or using await-async (.NET 4.5), see, you can call DoWork() from any method that marked as async.
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
await DoWork();
}
async Task DoWork()
{
await Task.Run(async () =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
await Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}

Related

C# - WPF - Updating the UI from another class on another Thread

I have looked around the internet and found a nice solution which I am incorporating into my code below however it doesn't quite do exactly what I want, it works when just calling an update but I want to run a method in another class then let that method call the method that will report back to the UI and just pass some information so this mock up is just changing the button content before the operation is ran.
Using a Dispatcher I can get a UI control to update however I don't just wish to do that I want to perform some functions then have the UI Update.
So there maybe some theory I am not getting, I know the Invoke is a synchronous operation and breaking through the code it does run but the UI doesn't update.
MainWindow
Has a single button with content "CLICK ME"
Code Behind
public partial class MainWindow : Window
{
public static Button windowButton;
public MainWindow()
{
InitializeComponent();
windowButton = btnStart;
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
// Run a Process in another class on a different Thread
ProcessClass processClass = new ProcessClass();
Task processTask = new Task(() =>
{
processClass.DoSomething();
});
processTask.Start();
}
}
}
ProcessClass
class ProcessClass:MainWindow
{
public static void UpdateUI()
{
App.Current.Dispatcher.Invoke(delegate
{
windowButton.Content = "CHANGED CONTENT";
});
}
public void DoSomething()
{
UpdateUI();
int counter = 0;
for(int i = 1; i < 100; i++)
{
counter += i;
Thread.Sleep(100);
}
MessageBox.Show($"Task Completed, answer is {counter}");
}
}
Assuming that ProcessClass is your own code that you can update, change the signiture of DoDomething() to
public async Task DoSomething(IProgress<string> progress)
{
progress.Report("Begin DoSomething()");
var counter = 0;
for(var i = 1; i < 100; i++)
{
counter += i;
await Task.Delay(100).ConfigureAwait(false);
progress.Report($"DoSomething() - i = {i}");
}
progress.Report($"DoSomething() Completed, answer is {counter}");
}
Now your button click handler can be written
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
// usually you would update some other control such as a TextBlock
// for the feedback, rather than the button content
var progress = new Progress<string>(s => btnStart.Content = s);
ProcessClass processClass = new ProcessClass();
await processClass.DoSomething(progress).ConfigureAwait(false);
}

C# Using task and yield to keep UI informed of a running process

Is it bad practice to write code like this. What I want to accomplish is that a user can press a button on a control. The button starts some kind of analyzing process and for each item done it shows a result to the user.
private IEnumerable<int> AnalyzeItems() {
for(int i = 0; i < 1000; i++) {
Thread.Sleep(500);
yield return i;
}
}
private void PerformTask_Click(object sender, EventArgs e) {
Task.Run(() => {
foreach (var item in AnalyzeItems()) {
ResultLog.Invoke((Action)delegate() { ResultLog.Text += item.ToString(); });
}
});
}
why do not use Backgroundworker?
First setup the backgroundworker properties to:
WorkerReportsProgress = true
WorkerSupportsCancellation = true
This is the code:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
for (int i = 0; i < 1000; i++) {
Thread.Sleep(500);
if (backgroundWorker1.CancellationPending) {
e.Cancel = true;
break;
}
backgroundWorker1.ReportProgress(i / 10, "step " + i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
label1.Text = e.UserState.ToString();
progressBar1.Value = e.ProgressPercentage;
}
private void button1_Click(object sender, EventArgs e) {
cancelButton.Focus();
button1.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
private void cancelButton_Click(object sender, EventArgs e) {
backgroundWorker1.CancelAsync();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
button1.Enabled = true;
if (e.Error != null) {
MessageBox.Show(e.Error.Message, "Unexpected error");
}
if (e.Cancelled) {
MessageBox.Show("Process stopped by the user", "Cancelled");
}
label1.Text = "Press start";
progressBar1.Value = progressBar1.Minimum;
}
}
Is your approach bad practice? It depends.
If you don't expect your code inside Task.Run to throw any exceptions and you want to continue doing something else, then your code is ok. However, if you want to capture any possible exceptions and wait for the process to finish without freezing UI, then you might want to consider using async/await.
private async void PerformTask_Click(object sender, EventArgs e) {
try
{
await Task.Run(() => {
foreach (var item in AnalyzeItems()) {
ResultLog.Invoke((Action)delegate() { ResultLog.Text += item.ToString(); });
}
});
}
catch(Exception ex)
{
// handle...
}
}
Alternative approach would be to use IProgress<T>. This allows for easy separation of long running work and updating UI. Please note that you shouldn't call this method too often, because
This will put too much work on UI thread resulting in UI freeze.
If you pass any valuetype to IProgress<T>.Report method, then it gets copied. If you call this too often, you risk running garbage collector very often resulting in even bigger freezes.
All of this means that you should utilize IProgress only for truly long running work.
Now that we have it all out of the way, here is a sample of how you could notify users about progress of analyzed items:
private double _currentProgress;
public double CurrentProgress {
get => _currentProgress;
set
{
_currentProgress = value;
NotifyPropertyChanged();
}
}
private async void PerformTask_Click(object sender, EventArgs e)
{
var progress = new Progress<double>();
progress.ProgressChanged += (sender, p) => CurrentProgress = p;
await Task.Run(() => AnalyzeItems(Enumerable.Range(0, 5000).ToList(), progress));
}
private void AnalyzeItems(List<int> items, IProgress<double> progress)
{
for (int itemIndex = 0; itemIndex < items.Count; itemIndex++)
{
// Very long running CPU work.
// ...
progress.Report((double)itemIndex * 100 / items.Count);
}
}
If AnalyzeItems takes less than 100 ms for individual item, then you don't want to report after every finished item (see why above). You can decide how often you want to update status like this:
private void AnalyzeItems(List<int> items, IProgress<double> progress)
{
var lastReport = DateTime.UtcNow;
for (int itemIndex = 0; itemIndex < items.Count; itemIndex++)
{
// Very long running work.
Thread.Sleep(10);
// Tell the user what the current status is every 500 milliseconds.
if (DateTime.UtcNow - lastReport > TimeSpan.FromMilliseconds(500))
{
progress.Report((double)itemIndex * 100 / items.Count);
lastReport = DateTime.UtcNow;
}
}
}
If you have really a lot of very fast iterations, you may want to consider changing DateTime.Now to something else.

totally confused thread safe call to chart control

I'm trying to workout how to call chart1.Series[0].Points.AddXY(1, 4); from another thread.
I have been trying to adapt the examples which all show setting a text property using a delegate but I can't get them to work with the chart control.
could someone please help me?
delegate void SetTextCallback(string text);//assume text is the value and not the Text property
private void chartRefresh()
{
while (true)
{
//code to refresh chart
for (int i = 0; i < 30; i++)
{
if (this.chart1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.chart1.Series[0].Points.AddXY(i, i + 2);
}
chart1.Series[0].Points.AddXY(i, i + 2);
Thread.Sleep(500);
}
}
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.chart1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.chart1.Series[0].Points.AddXY(1, 4);
}
}
while (true)
{
for (int i = 0; i < 20000; i++)
{
Temp ++;
chart1 .Invoke((MethodInvoker)delegate
{
// Running on the UI thread
Series.Points.AddXY(i, random.Next(0,100));
});
}
You may find interesting the modern approach, using the Progress class. You can instantiate such an object in the UI thread, supplying the lambda function that will update the chart:
private IProgress<int> _chartProgress
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_chartProgress = new Progress<int>(i =>
{
this.chart1.Series[0].Points.AddXY(i, i + 2);
});
}
Then pass this object somehow to the background worker, and invoke its Report method any time you want the chart to be updated.
private void ChartRefresh(IProgress<int> progress)
{
while (true)
{
//code to refresh chart
for (int i = 0; i < 30; i++)
{
progress.Report(i);
Thread.Sleep(500);
}
}
}
The lambda will always run in the UI thread, because the Progress object was created in that thread.
The advantage of this technique is that it allows decoupling the UI-related stuff from the background-related work. It was invented primarily for facilitating reporting progress from asynchronous operations, but it can be useful in multithreading too.

Windows form progress bar not working?

I have a button .It will open a file and process a file .I want to show the progress bar while processing the file .
when i am doing .its working
public MainframeDataExchangeTool()
{
InitializeComponent();
_ProgressBar.Style = ProgressBarStyle.Marquee;
_ProgressBar.Visible = false;
_Random = new Random();
InitializeBackgroundWorker();
}
private void InitializeBackgroundWorker()
{
_BackgroundWorker = new BackgroundWorker();
_BackgroundWorker.WorkerReportsProgress = true;
_BackgroundWorker.DoWork += (sender, e) => ((MethodInvoker)e.Argument).Invoke();
_BackgroundWorker.ProgressChanged += (sender, e) =>
{
_ProgressBar.Style = ProgressBarStyle.Continuous;
_ProgressBar.Value = e.ProgressPercentage;
};
_BackgroundWorker.RunWorkerCompleted += (sender, e) =>
{
if (_ProgressBar.Style == ProgressBarStyle.Marquee)
{
_ProgressBar.Visible = false;
}
};
}
In my button click i am doing
private void btnOpenScriptFile_Click(object sender, EventArgs e)
{
try
{
loadScriptFlDlg.Filter = Constants.SCRIPT_FILE_FILTER;
loadScriptFlDlg.FilterIndex = 3;
loadScriptFlDlg.RestoreDirectory = true;
loadScriptFlDlg.FileName = string.Empty;
DialogResult objDialogResult = loadScriptFlDlg.ShowDialog();
if (objDialogResult.Equals(DialogResult.OK))
{
_BackgroundWorker.RunWorkerAsync(new MethodInvoker(() =>
{
_ProgressBar.BeginInvoke(new MethodInvoker(() => _ProgressBar.Visible = true));
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10);
_BackgroundWorker.ReportProgress(i);
}
}));
EnableDisableControls("OpenScript");
string strScriptError = LoadScriptFromFile(loadScriptFlDlg.FileName);///loading will taking time but progress bar not showing
Basically progress bar is showing at the end of data load but not while loading the data
You cannot see the progress as UI thread cannot update UI because it is busy loading your file. You must call LoadScriptFromFile from the background worker and keep UI thread free to process events and update UI.
I used this in my project with 2 methode (exemple):
1* Invoke(new Action(() => _ProgressBar.Visible = true));
2* or use Application.DoEvents() after your _BackgroundWorker.ReportProgress(i);
Surprised the compiler didn't throw a hissy fit...because you need an extra }.
You open 4 levels of nesting, but only close 3.
I have modified your code as shown below:
private void btnOpenScriptFile_Click(object sender, EventArgs e)
{
try
{
if (objDialogResult.Equals(DialogResult.OK))
{
_ProgressBar.Style = ProgressBarStyle.Marquee;
_ProgressBar.BeginInvoke(new MethodInvoker(() => _ProgressBar.Visible = true));
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10);
_ProgressBar.BeginInvoke(new Action(() => _ProgressBar.Value = i));
//Process my file here
}
}
}
Catch
{
}
}
I would always suggest reducing the number of empty lines, for readability.
I also find that the 'allman' style bracketing is easiest to debug.
But as always, each to his own.
EDIT:
After OP editted code:
Try adding:
application.doevents()
after your:
_BackgroundWorker.ReportProgress(i);
Why? The code will stay in the for loop until completes. By doing 'doevents' you are telling it to update externally, before the next loop.
Without the 'doevents' i guess you would see 0 and 100 only.
Simply do something like this with a BackgroundWorker (bgw).
private void MyMethod()
{
bgw.RunWorkerAsync(); //this calls the DoWork event
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
//Expensive task
//Calculate how far you through your task (ie has read X of Y bytes of file)
bgw.ReportProgress(myInteger);
}
private void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myProgressBar.Value = e.ProgressPercentage;
}
Make sure you set the BackgroundWorker's "WorkerReportsProgress" property to True!

How to handle cross thread error in C sharp windows application?

I'm having an application with a progress bar and a buttom.
When the button clicked the progress bar value will get increased, here is the source code,
private void Run()
{
progressBar1.Maximum = 1000;
progressBar1.Minimum = 0;
progressBar1.Step = 1;
for (int l_nIndex = 0; l_nIndex < 1000; l_nIndex++)
{
progressBar1.Value++;
Thread.Sleep(10);
}
}
private void button1_Click(object sender, EventArgs e)
{
Run();
}
so when i run the application, the progress bar value is getting increased, but when i try to move the window its not responding.
I can not run it in a normay thread way - it will throw Cross-Thread error.
so i changed the code like,
private void Run()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(this.Run));
}
else
{
progressBar1.Maximum = 1000;
progressBar1.Minimum = 0;
progressBar1.Step = 1;
for (int l_nIndex = 0; l_nIndex < 1000; l_nIndex++)
{
progressBar1.Value++;
Thread.Sleep(10);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread myThread = new Thread(new ThreadStart( Run));
myThread.Start();
}
Now i can able to move the winodow, but when i move the progress bar is stopped, and when i release the mouse button its resuming. So still the execution is in UI Thread.
How to handle it in a better way.Please help me to do this .
Invoke() works by running the given delegate from the UI thread. So if you use Invoke() to run your entire method, then your entire method runs from the UI thread.
Instead, you should be doing your actual work in the other thread, and just performing UI updates in the UI thread, by just Invoke()ing the little bits of code that perform the updates.
One easy way to do this is to use the BackgroundWorker class built into the standard library.
This has been answered here - in your case the code should look something like:
this.BeginInvoke((Action)(() => progressBar1.Value++));

Categories