I am building an app with WPF and Caliburn.Micro. I want to update a ProgressBar from an Task/Thread and I am wondering what I need to correctly update the UI:
public class DemoViewModel : PropertyChangedBase
{
private int m_Progress;
public int Progress
{
get { return m_Progress; }
set
{
if (value == m_Progress) return;
m_Progress = value;
NotifyOfPropertyChange();
NotifyOfPropertyChange(nameof(CanStart));
}
}
public bool CanStart => Progress == 0 || Progress == 100;
public void Start()
{
Task.Factory.StartNew(example);
}
private void example()
{
for (int i = 0; i < 100; i++)
{
Progress = i + 1; // this triggers PropertChanged-Event and leads to the update of the UI
Thread.Sleep(20);
}
}
}
From other programming languages I know that I need to synchronize with the UI thread to update the UI but my code just works. Is there something I missed and which could cause sporadic errors or is there some magic behind the scenes which care of the synchronization?
It will depend on how you've implemented INotifyPropertyChanged. The implementation should delgate all UI updates to the appropriate dispatcher.
Sample Implementation:
public void RaisePropertyChanged([CallerMemberName]string name) {
Application.Current.Dispatcher.Invoke(() => {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}, System.Windows.Threading.DispatcherPriority.Background);
}
Also to clean up the task start a bit:
Edit:
Removed unnecessary bool return value, and set ConfigureAwait to stay off UI thread when task completes.
public async void Start()
{
await Task.Run(() => example()).ConfigureAwait(false);
}
private async Task example()
{
for (int i = 0; i < 100; i++)
{
Progress = i + 1; // this triggers PropertChanged-Event and leads to the update of the UI
await Task.Delay(20);
}
}
Related
I am trying to do a "Clear Flag" in my code. I have data being shown on the UI continuously, and I want the user to be able to clear the data when the code is running with a "Clear" button. I have a second thread collecting and updating the data on the UI Thread. I tried to use a flag in the properties that when it sees the "Clear Flag" is true to rests to a predetermined value. When the clear button is pressed I set my "Clear Flag" to true and it clears the data, but my problem is knowing when it is done and setting the "Clear Flag" back to false when it is done.
private bool _clear;
public bool Clear
{
get
{
return _clear;
}
set
{
_clear = value;
OnPropertyChanged("Clear");
if (value)
{
OnPropertyChanged(String.Empty);
}
}
}
private int _motorRPM;
public int MotorRPM
{
get
{
return _motorRPM;
}
set
{
_motorRPM = value;
OnPropertyChanged("MotorRPM");
}
}
private int _aveRPM;
public int AveRPM
{
get
{
return _aveRPM;
}
set
{
if (Clear)
{
_aveRPM = 0;
}
else
{
_aveRPM = (_aveRPM + value) / 2;
}
OnPropertyChanged("AveRPM");
}
}
private int _minRPM = Int32.MaxValue;
public int MinRPM
{
get
{
return _minRPM;
}
set
{
if (Clear)
{
_minRPM = Int32.MaxValue;
}
else
{
if (value < _minRPM)
{
_minRPM = value;
}
}
OnPropertyChanged("MinRPM");
}
}
private int _maxRPM;
public int MaxRPM
{
get
{
return _maxRPM;
}
set
{
if (Clear)
{
_maxRPM = 0;
}
else
{
if (value > _maxRPM)
{
_maxRPM = value;
}
}
OnPropertyChanged("MaxRPM");
}
}
As you can see I have a "Clear" Property that when set to true will call an update all properties with OnPropertyChanged(String.Empty) with each property resting to a known value when "Clear" is true.
How do I set Clear back to false when all PropertyChanged events are done?
As said Niclas, I would said: do not do it like this! The purpose of the PropertyChanged in your ViewModel is to update the UI. If you had some sort of logic depending of your propertychanged, it will be a nightmare to debug!
Keep your property as clean as possible. Now to update some dependent value it would be better to do something like this:
private bool _clear;
public bool Clear
{
get
{
return _clear;
}
set
{
_clear = value;
OnPropertyChanged("Clear");
if (_clear)
ClearValues();
}
}
public void ClearValues()
{
AveRPM=0;
MinRPM=0;
MaxRPM=0;
...
Clear=False;
}
Not perfect but seems much more readable and maintainable to me.
Hope it helps.
I have a WinForms application that is calling a business class method that performs some heavy duty action taking about 5 seconds for each call. The main form calls this method in a loop. This loop can run from 10 times to maybe up to 10 thousand times.
The WinForms application sends a parameter to the business class and has an area to display the time taken for each method call and what the value returned by the method. How do I inform my main window and update a text area in the main winform with what the method has returned for each call?
Currently the data comes all at once after all the threads have finished. Is there a way to update the UI for all the iterations of the loop once the each call is done? I don't mind if it is done sequentially also.
The FORM
HeavyDutyClass hd;
public Form1()
{
InitializeComponent();
hd = new HeavyDutyClass();
}
//BUTTON CLICK
private void Start_Click(object sender, EventArgs e)
{
int filecount = 5000; //BAD - opening 5000 threads! Any other approach?
hd.FileProcessed += new EventHandler(hd_FileProcessed);
var threads = new Thread[filecount];
for (int i = 0; i < filecount; i++)
{
threads[i] = new Thread(() => { hd.LongRunningMethod(); });
threads[i].Start();
}
}
//BUSINESS CLASS EVENT THAT FIRES WHEN BUSINESS METHOD COMPELTES
void hd_FileProcessed(object sender, EventArgs e)
{
if (dgv.InvokeRequired)
{
dgv.Invoke((MethodInvoker)delegate { UpdateGrid(); });
}
}
private void UpdateGrid()
{
dgv.Rows.Add(1);
int i = dgv.Rows.Count;
dgv.Rows [ i-1].Selected = true;
dgv.FirstDisplayedScrollingRowIndex = i - 1;
}
The business HeavyDuty class
public event EventHandler FileProcessed;
public HeavyDutyClass()
{
}
protected virtual void OnMyEvent(EventArgs e)
{
if (FileProcessed != null)
{
FileProcessed(this, e);
}
}
public bool LongRunningMethod()
{
for (double i = 0; i < 199990000; i++)
{
//time consuming loop
}
OnMyEvent(EventArgs.Empty);
return true;
}
Add a Winforms Project, Drop a Label Control on the Form , Copy-Paste this code and Hit F5
[EDIT]: Updated with the business class comment from the user
NB: My form class is named Form3. You may have to change your Program.cs or vice-versa.
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
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);
}
}
}
With this you can achieve Cancel functionality also very easily.
Observe that during initialisation, I set the WorkerSupportsCancellation = true & then I check for _worker.CancellationPending in the DoWork. So, if you want to cancel the process by a Cancel Button click, then you will write this code in the button handler- _worker.CancelAsync();
I am developing a WPF application which is bind to a DataGrid as given below:
<DataGrid AutoGenerateColumns="True"
ItemsSource="{Binding Source={StaticResource tradeViewModel},
Path=Result}"
Where the Path pointing to Result property in view model which inturn calls TradePriceChanger.GetTrades()
The TradePriceChanger.GetTrades method return sList of Trade as given below:
public static class TradePriceChanger
{
static List<Trade> tradeList = new List<Trade>();
static int TradeCounter = 1;
static ManualResetEvent manualReset = new ManualResetEvent(false);
public static IEnumerable<Trade> GetTrades()
{
Thread t1 = new Thread(new ThreadStart(One));
t1.Start();
Thread t2 = new Thread(new ThreadStart(Two));
t2.Start();
manualReset.WaitOne();
return tradeList;
}
public static void One()
{
for (int i = 1; i < 10; i++)
{
tradeList.Add(new Trade ( "One" + i, i ));
}
Interlocked.Decrement(ref TradeCounter);
if (TradeCounter == 0)
manualReset.Set();
}
public static void Two()
{
for (int i = 1; i < 10; i++)
{
tradeList.Add(new Trade("Two" + i, i));
}
Interlocked.Decrement(ref TradeCounter);
if (TradeCounter == 0)
manualReset.Set();
}
}
And the Trade class has a timer which updates its price to a random value after every 1sec.
public class Trade:INotifyPropertyChanged
{
private string _name;
private double _price;
public Trade(string name, double price)
{
_name = name;
_price = price;
TimerCallback callback = new TimerCallback(ChangePrice);
Timer timer = new Timer(callback, this, 0, 1000);
}
private static void ChangePrice(object obj)
{
if (obj is Trade)
{
Trade trade = obj as Trade;
trade.Price = trade.Price + ( new Random().Next(-1,2) * (new Random().NextDouble()));
}
}
public string Name { get { return _name; } set { _name = value; } }
public double Price
{
get
{
return _price;
}
set
{
_price = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Price"));
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Now the problem is the values in the grid are refreshing properly When I run only thread t1 in the TradePriceChanger.GetTrades(). But, If I run both, no refresh is happening on UI.
Am I missing something here.
Please let me know.
Thanks,
Mahesh
It seems you're only waiting for one thread by using 'manualReset.WaitOne();', but if you want both threads to have the time to execute, you should use WaitAll(); Else one thread could finish and the function would exit as the wait would have been satisfied.
It also seems you're adding to your tradelist without any thread synchronization, so both threads can write to this list and cause inconsistent state in your list
I am working on a 'proof of concept' Silverlight 4 project and am learning the way of THE ASYNC. I have stopped fighting the urge to implement some pseudo-synchronous smoke and mirrors technique. I am going to learn to stop worrying and love THE ASYNC.
Most of the time I just use a BusyIndicator while async methods are running and all is good but I have run into a few situations where I need to call methods sequentially. I put together this example and it works. But in my experience... if it works... there is something wrong with it.
When is this going to blow up in my face or steal my wife or date one of my daughters?
Is there a better way to do this?
The Code:
public class CustomPage : Page
{
static readonly object _AsyncMethodChain_Lock = new object();
private Dictionary<Action<object>, string> _AsyncMethodChain = new Dictionary<Action<object>, string>();
public Dictionary<Action<object>, string> AsyncMethodChain
{
get { lock (_AsyncMethodChain_Lock) { return this._AsyncMethodChain; } }
set { lock (_AsyncMethodChain_Lock) { this._AsyncMethodChain = value; } }
}
private void CustomPage_Loaded(object sender, RoutedEventArgs e)
{
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
var user = this.SecurityProvider.UserObject as TimeKeeper.UserServiceReference.User;
if (user == null)
return;
this.AsyncMethodChain.Add(
data =>
{
var userServiceClient = new UserServiceClient();
userServiceClient.GetCompleted +=
(send, arg) =>
{
var userViewSource = this.Resources["userViewSource"] as CollectionViewSource;
userViewSource.Source = new List<UserServiceReference.User>(new UserServiceReference.User[1] { arg.Result });
userViewSource.View.MoveCurrentToPosition(0);
this.AsyncMethodChain.ExecuteNext(arg.Result.UserID, this.BusyIndicator);
};
userServiceClient.GetAsync(user.UserID);
},
"Loading user..."
);
this.AsyncMethodChain.Add(
data =>
{
var userID = (int)data;
var timeLogServiceClient = new TimeLogServiceClient();
timeLogServiceClient.FindByUserIDCompleted +=
(send, arg) =>
{
var timeLogViewSource = this.Resources["timeLogViewSource"] as CollectionViewSource;
timeLogViewSource.Source = arg.Result;
this.AsyncMethodChain.ExecuteNext(null, this.BusyIndicator);
};
timeLogServiceClient.FindByUserIDAsync(userID);
},
"Loading time logs..."
);
this.AsyncMethodChain.ExecuteNext(null, this.BusyIndicator);
}
}
}
public static class Extensions
{
public static void ExecuteNext(this Dictionary<Action<object>, string> methods, object data, BusyIndicator busyIndicator)
{
if (methods.Count <= 0)
{
busyIndicator.BusyContent = "";
busyIndicator.IsBusy = false;
return;
}
else
{
var method = methods.Keys.ToList<Action<object>>()[0];
busyIndicator.BusyContent = methods[method];
busyIndicator.IsBusy = true;
}
methods.ExecuteNext(data);
}
public static void ExecuteNext(this Dictionary<Action<object>, string> methods, object data)
{
var method = methods.Keys.ToList<Action<object>>()[0];
methods.Remove(method);
method(data);
}
}
What you have sine looks pretty good, but if you are still worried about the callsequence i would sugest that you create a new method in your webservice which will call the other four in whatever sequence you need them to and call this new method from your silverlight application.
I dont see the need for you to do that as your current implemenation is pretty good as well.
I have a small WPF application where I'm simulating movement which is detected by a sensor. I fake that movement occurs after 1 minute and it stops after 2 minutes. Below is my code:
public event Action OnMotionDetected;
public event Action OnMotionReset;
private DateTime startTime = DateTime.Now;
public MotionDetectionService()
{
startTime = DateTime.Now;
System.Threading.Thread mockThread = new System.Threading.Thread(new System.Threading.ThreadStart(StartMock));
mockThread.Start();
}
private void StartMock()
{
while (DateTime.Now < startTime.AddMinutes(1))
{
System.Threading.Thread.Sleep(1000);
Console.WriteLine("Remaining: " + (startTime.AddMinutes(1) - DateTime.Now).ToString());
}
FireMoveEvent();
while (DateTime.Now < startTime.AddMinutes(2))
{
System.Threading.Thread.Sleep(1000);
Console.WriteLine("Remaining: " + (startTime.AddMinutes(2) - DateTime.Now).ToString());
}
FireMoveEvent();
}
private void FireMoveEvent()
{
if(OnMotionDetected != null)
{
OnMotionDetected();
}
}
private void FireResetEvent()
{
if (OnMotionReset != null)
{
OnMotionReset();
}
}
When the thread fires the event my UI updates, but it says that it cannot update because the UI elements was generated on another thread.
Any ideas on how to solve?
You can use Dispatcher.Invoke() to marshall onto the UI thread.
Please see: Making sure OnPropertyChanged() is called on UI thread in MVVM WPF app