How can I alter XAML elements from a backgroundworker thread? - c#

In the following code example, I want to change the color of the Foreground text of a TextBox from my BackgroundThread, but it gets the error:
This thread cannot access this object
since it is in another thread.
What do I have to change in the code below so that the background worker thread can change the foreground color in the TextBox?
Answer:
Thanks Andy, it was just that small oversight, here is the corrected code for posterity's sake:
using System.Windows;
using System.ComponentModel;
using System.Threading;
using System.Windows.Media;
namespace TestBackgroundWorker7338
{
public partial class Window1 : Window
{
private BackgroundWorker _worker;
int _percentageFinished = 0;
public Window1()
{
InitializeComponent();
ButtonCancel.IsEnabled = false;
}
private void Button_Start(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker();
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += (s, args) =>
{
BackgroundWorker worker = s as BackgroundWorker;
int numberOfTasks = 300;
for (int i = 1; i <= numberOfTasks; i++)
{
if (worker.CancellationPending)
{
args.Cancel = true;
return;
}
Thread.Sleep(10);
float percentageDone = (i / (float)numberOfTasks) * 100f;
worker.ReportProgress((int)percentageDone);
}
};
_worker.ProgressChanged += (s,args) =>
{
_percentageFinished = args.ProgressPercentage;
ProgressBar.Value = _percentageFinished;
Message.Text = _percentageFinished + "% finished";
if (_percentageFinished < 500)
{
Message.Text = "stopped at " + _percentageFinished + "%";
}
else
{
Message.Text = "finished";
}
if (_percentageFinished >= 70)
{
InputBox.Foreground = new SolidColorBrush(Colors.Red);
}
else if (_percentageFinished >= 40)
{
InputBox.Foreground = new SolidColorBrush(Colors.Orange);
}
else if (_percentageFinished >= 10)
{
InputBox.Foreground = new SolidColorBrush(Colors.Brown);
}
else
{
InputBox.Foreground = new SolidColorBrush(Colors.Black);
}
};
_worker.RunWorkerCompleted += (s,args) =>
{
ButtonStart.IsEnabled = true;
ButtonCancel.IsEnabled = false;
ProgressBar.Value = 0;
};
_worker.RunWorkerAsync();
ButtonStart.IsEnabled = false;
ButtonCancel.IsEnabled = true;
}
private void Button_Cancel(object sender, RoutedEventArgs e)
{
_worker.CancelAsync();
}
}
}

As Andy said changing properties of controls must happen on the UI thread.
WPF provides a Dispatcher class that makes it easier to route calls for controls to the appropriate UI thread. Controls in WPF exposes a Dispatcher property object to dispatch the call to an appropriate UI thread.
You can use this as follows (I would add this after the line worker.ReportProgress((int)percentageDone); in the Button_Start method):
// the Window class exposes a Dispatcher property, alternatively you could use
// InputBox.Dispatcher to the same effect
if (!Dispatcher.CheckAccess())
{
Dispatcher.Invoke(new Action(() => ChangeForegroundColor(percentageDone));
}
else
{
ChangeForegroundColor(percentageDone);
}
...
private void ChangeForegroundColor(int percentageDone)
{
if (percentageDone >= 90)
{
InputBox.Foreground = new SolidColorBrush(Colors.Red);
}
else if(percentageDone >=10)
{
InputBox.Foreground = new SolidColorBrush(Colors.Orange);
}
}

The ProgressChanged delegate is run on the UI thread. If you set the BackgroundColor there instead of in DoWork, that should allow you to set the color without the error.

Related

mvvm backgroundworker - CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread

I am having an issue where my backgroundworker completes the work the first time I press the button that calls it but the second time I press the button an error is generated. The error is flagged on an integer not on a collection.
The error is "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread." The code line is " t += 1;" Maybe I need to define the backgroundworker in a different spot?
Here is part of the code.
public P4LabelBatteryViewModel()
{
BatteryCheckerModel BatteryCheckerModel = new BatteryCheckerModel();
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerCompleted += Worker_WorkerCompleted;
}
private void checkOutRestults()
{
TotalFiles = 0;
foreach (var _scripObject in ScriptCollection)
{
if (_scripObject.ScriptNameAdd != "")
{
TotalFiles += 1;
}
}
if (ScriptCollection.Count > 0)
{
_isrunning = true;
worker.RunWorkerAsync();
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var t = e.ProgressPercentage;
NewBatteryFiles += 1;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
ScriptModel _newFileResult = new ScriptModel();
int t = 0;
_newFileResult = CheckOutResults.CheckOutResultBatteryFiles(BatteryLocation, SelectedMachine.Machine);
ScriptCollectionTemp.Add(_newFileResult);
t += 1;
foreach (ScriptModel _checkOutFile in ScriptCollection)
{
_newFileResult = CheckOutResults.CheckOutResultFiles(_checkOutFile, BatteryLocation, SelectedMachine.Machine);
ScriptCollectionTemp.Add(_newFileResult);
t += 1;
worker.ReportProgress(t);
}
//worker.ReportProgress(t);
}
void Worker_WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
foreach (ScriptModel _checkOutFile in ScriptCollectionTemp)
{
_checkOutFile.BackGround = new SolidColorBrush(Colors.LightGreen);
}
ScriptCollection = ScriptCollectionTemp;
}
No control will like you changing them on a different thread to the UI thread. This is just the way the world works
Disregarding other problems, this pattern will allow you to update the UI from a different context than the UI Context
Application.Current.Dispatcher.Invoke(
() =>
{
// do any UI updates here
});
I modified my code as follows and it works the second time through now.
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
ScriptModel _newFileResult = new ScriptModel();
ObservableCollection<ScriptModel> _scriptCollectionTemp = new ObservableCollection<ScriptModel>();
for (int i = 0; i < ScriptCollection.Count; i++ )
{
_newFileResult = CheckOutResults.CheckOutResultFiles(ScriptCollection[i], BatteryLocation, SelectedMachine.Machine);
_scriptCollectionTemp.Add(_newFileResult);
worker.ReportProgress(_scriptCollectionTemp.Count);
}
ScriptCollection = _scriptCollectionTemp;
}

UIProgressView not updating from BackgroundWorker.ProgressChanged

I'm in the process of porting my Xamarin.Android app to Xamarin.iOS, I can't make my progress bar update, where am I going wrong?
The values are set in updateProgressBar() correctly and progressBarValue in this example is set as 0.25 as expected, but the UIProgressView is not updated on the screen. progressBar is a UIProgressView on the storyboard.
public BackgroundWorker backgroundWorker { get; private set; }
private float progressBarValue { get; set; }
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
startBackgroundWorker();
}
private void startBackgroundWorker()
{
if (backgroundWorker == null || backgroundWorker.CancellationPending) backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (s, e) =>
{
//do stuff
backgroundWorker.ReportProgress(25);
//do stuff
};
backgroundWorker.RunWorkerCompleted += (s, e) => { //do stuff };
backgroundWorker.ProgressChanged += (s, e) => { updateProgressBar(e.ProgressPercentage); };
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.RunWorkerAsync();
}
private void updateProgressBar(float v)
{
if (v > 0){
float value = v / 100;
progressBarValue = progressBarValue + value;
if (progressBarValue > 1) progressBarValue = 1;
progressBar.Progress = progressBarValue;
}
}
I also tried using SetProgress(progressBarValue,true)
private void updateProgressBar(float v)
{
if (v > 0){
float value = v / 100;
progressBarValue = progressBarValue + value;
if (progressBarValue > 1) progressBarValue = 1;
progressBar.SetProgress(progressBarValue,true);
}
}
and using InvokeOnMainThread
private void updateProgressBar(float v)
{
if (v > 0){
float value = v / 100;
progressBarValue = progressBarValue + value;
if (progressBarValue > 1) progressBarValue = 1;
InvokeOnMainThread ( () => {
// manipulate UI controls
progressBar.SetProgress(progressBarValue,true);
});
}
}
you need to be sure your UI updates are running on the main thread
InvokeOnMainThread ( () => {
// manipulate UI controls
progressBar.SetProgress(progressBarValue,true);
});
As is often the case, my problem was not related to my code in the question, or the question at all, I copied it into a new solution and it worked fine.
Incase anyone finds this in the future and has made the same mistake:
My problem was that I had set UIProgressView.TintColor to an invalid value elsewhere in my code so the ProgressView was updating all along but it just wasn't visible.
progressView.TintColor = iOSHelpers.GetColor(GenericHelpers.colorPrimaryGreenRGBA);
iOSHelpers
public static UIColor GetColor(int[] rgba)
{
if (rgba.Length == 4)
return UIColor.FromRGBA(rgba[0], rgba[1], rgba[2], rgba[3]);
else return UIColor.Black;
}
GenericHelpers
public static int[] colorPrimaryGreenRGBA = { 140, 185, 50, 1 };
Once I had changed thecolorPrimaryGreenRGBA values to floats and divided the RGB channels by 255 in the GetColor method it was then a visible color.

Pause and resume thread activity

I have a thread that adds points onto a zedgraph component on a certain time interval. I need to pause the adding of the points on pressing a check box and then resume adding them when the checkbox is pressed again. The following is what I have for thread:
public class myThread{
ManualResetEvent pauseResumeThread = new ManualResetEvent(true);
public void threadHeartrateGraph()
{
for (int k = 15; k < _hr.Length; k++)
{
pauseResumeThread.WaitOne();
if (HRDataSummary.threadPause == true)
{
break;
}
x = k;
y = _hr[k];
list1.Add(x, y);
_displayHRGraph.Invoke(list1, graph_HeartRate, _GraphName[0]);
graph_HeartRate.XAxis.Scale.Min = k-14;
graph_HeartRate.XAxis.Scale.Max = k+1;
Thread.Sleep(_interval * 1000);
}
}
catch (NullReferenceException)
{
}
}
public void play()
{
pauseResumeThread.Set();
}
public void pause()
{
pauseResumeThread.Reset();
}
}
And then, I have called for the play and pause thread from a checkbox.
private void checkBoxPause_CheckedChanged(object sender, EventArgs e)
{
if(checkBoxPause.Checked == true)
{
//HRDataSummary.threadPause = true;
checkBoxPause.Text = "Play >";
myThread mythread = new myThread();
Thread pause = new Thread(mythread.pause);
pause.Start();
}
if (checkBoxPause.Checked == false)
{
//HRDataSummary.threadPause = false;
checkBoxPause.Text = "Pause ||";
myThread mythread = new myThread();
Thread play = new Thread(mythread.play);
play.Start();
}
}
What am I missing? Or is it completely wrong use of ManualResetEvent?
First you declare your myThread Object globally (only one!).
myThread heartGraph = new myThread()
Then you want to start your Worker-Method in a new Thread.
Thread worker = new Thread(heartGraph.threadHeartrateGraph);
worker.Start();
Now you can use your ManualResetEvent to Pause/Resume the work.
if (checkBoxPause.Checked == true) {
//HRDataSummary.threadPause = true;
checkBoxPause.Text = "Play >";
heartGraph.pause();
} else {
//HRDataSummary.threadPause = false;
checkBoxPause.Text = "Pause ||";
heartGraph.play();
}

C# Timer thread crash, BeginInvoke

I've an "animateMyWindow" class to change opened window's opacity with Timer.
namespace POCentury
{
class animateMyWindow
{
Timer _timer1 = new Timer();
Window _openedWindow = null;
public void animationTimerStart(object openedWindow)
{
if (openedWindow == null)
{
throw new Exception("Hata");
}
else
{
_openedWindow = (Window)openedWindow;
_timer1.Interval = 1 * 25;
_timer1.Elapsed += new ElapsedEventHandler(animationStart);
_timer1.AutoReset = true;
_timer1.Enabled = true;
_timer1.Start();
}
}
private void animationStart(object sender, ElapsedEventArgs e)
{
if (_openedWindow.Opacity == 1)
animationStop();
else
_openedWindow.Opacity += .1;
}
private void animationStop()
{
_timer1.Stop();
}
}
}
animationStart function can't reach my window because it is working on a different thread.
I've tried Dispatcher.BeginInvoke and can't make it work.
Can you help me with doing that?
Basically, you can't access the openedWindow inside the animationStart event because it's happening in a different thread. You need the Dispatcher to do that.
Dispatcher.BeginInvoke(new Action(() =>
{
if (_openedWindow.Opacity == 1)
animationStop();
else
_openedWindow.Opacity += .1;
}));

Sometimes my listBox1 or the value im updating inside is blinking for second or less how can i fix it?

This is the code I'm updating the ListBox:
this.Invoke(new Action(() => data = new List<string>()));
this.Invoke(new Action(() => data.Add("Gpu Temeprature --- " + sensor.Value.ToString())));
this.Invoke(new Action(() => listBox1.DataSource = null));
this.Invoke(new Action(() => listBox1.DataSource = data));
this.Invoke(new Action(() => listBox1.Invalidate()));
The variable data is List<string> once I added a new for the data so now the value of sensor(sensor.Value) is updating on the place i mean the old value each time deleted and a new one is added.
Sometimes the item itself in the ListBox blinking for less then a second or so and sometimes only the sensor.Value is blinking.
Tried to add a Validate() for the ListBox didn't help.
This is the background do work event:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (true)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
if (tempCpuValue >= (float?)nud1.Value || tempGpuValue >= (float?)nud1.Value)
{
soundPlay = true;
blinking_label();
NudgeMe();
}
else
{
soundPlay = false;
stop_alarm = true;
}
cpuView();
gpuView();
}
}
}
I have two more events of the listBox:
private void listBox1_MeasureItem(object sender, MeasureItemEventArgs e)
{
e.ItemHeight = 25;
}
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index == -1)
{
}
else
{
ColorText.ColorListBox(data, e);
}
}
And the ColorText function from another class wich color the items in the listBox:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace GatherLinks
{
class ColorText
{
public static void Texts(RichTextBox box, string text, Color color)
{
box.SelectionStart = box.TextLength;
box.SelectionLength = 0;
box.SelectionColor = color;
box.AppendText(text);
box.SelectionColor = box.ForeColor;
}
public static void ColorListBox(List<string> data, DrawItemEventArgs e)
{
string strLeft = null;
string strMid = "---";
string strRight = null;
if (data[e.Index].Contains(strMid))
{
int index = data[e.Index].IndexOf(strMid);
strLeft = data[e.Index].Substring(0, index);
strRight = data[e.Index].Substring(index + strMid.Length);
}
using (Font f = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Regular))
{
float startPos;
e.Graphics.DrawString(strLeft, f, Brushes.Red, e.Bounds.X, e.Bounds.Y);
startPos = e.Graphics.MeasureString(strLeft, f).Width;
e.Graphics.DrawString(strMid, f, Brushes.Black, e.Bounds.X + startPos, e.Bounds.Y);
startPos = e.Graphics.MeasureString(strLeft + strMid, f).Width;
e.Graphics.DrawString(strRight, f, Brushes.Green, e.Bounds.X + startPos, e.Bounds.Y);
}
}
}
}
How can i make the ListBox so it will not blinking each few seconds or every time a new sensor.Value value is updated ? I,m not sure why and when exactly it happen but the ListBox item and the sensor.Value are blinking.
I think the problem is coming from the fact that you're clearing the DataSource and then setting it again. It shouldn't be necessary to set the data source to null and then reset it when the data changes.
Try removing these two lines and see if the flickering goes away:
this.Invoke(new Action(() => listBox1.DataSource = null));
this.Invoke(new Action(() => listBox1.DataSource = data));
Your usage of Invoke method is wrong and that could be the reason for the blinking.
Try to change the first section of your code like this:
this.Invoke(new Action(() =>
{
data = new List<string>();
data.Add("Gpu Temeprature --- " + sensor.Value.ToString());
listBox1.DataSource = null;
listBox1.DataSource = data;
listBox1.Invalidate()
}));
Every time you call the Invoke method, a message is posted to the application's message queue, and later is processed in the UI thread when UI is not busy. By calling Invoke multiple times, you send multiple messages, that will be processed separately.
Once I solved putting this on my form:
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}

Categories

Resources