I am trying to figure out how to update my main UI (for example a textblock) from another thread. The only way I have been able to do it so far is using the Progress object. I have a situation where I cannot use the Progress object and I was recently pointed to using the MVVM / binding method. I have watch a few videos and done some examples but I can't seem to get it to work.
<TextBlock Name="txtblock1" Text="{Binding count}"></TextBlock>
Here are my errors
Exception thrown: 'System.Runtime.InteropServices.COMException' in
System.Runtime.WindowsRuntime.dll Exception thrown:
'System.Runtime.InteropServices.COMException' in mscorlib.ni.dll
Exception thrown: 'System.Runtime.InteropServices.COMException' in
mscorlib.ni.dll
View (Code behind)
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
ViewModelexample obj = new ViewModelexample();
txtblock1.DataContext = obj;
obj.Methodasync();
}
}
ViewModel
public class ViewModelexample : INotifyPropertyChanged
{
public string count { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void onPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler !=null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public async void Methodasync()
{
await Method();
}
public Task Method()
{
return Task.Factory.StartNew(() =>
{
for (int i = 0; i < 100; i++)
{
Task.Delay(1000).Wait();
Debug.WriteLine(i.ToString());
count = i.ToString();
onPropertyChanged(i.ToString());
}
});
}
}
Any ideas on what I might be doing wrong?
Thank you
Your count Property needs to be able to notify that it has changed
public class ViewModelexample : INotifyPropertyChanged
{
private string _count;
public string count {
get { return _count; }
set {
if(value != _count) {
_count = value;
OnPropertyChanged(nameof(count));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
// the new Null-conditional Operators are thread-safe:
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _testCount = 0;
public void Method() {
testCount++;
Debug.WriteLine(testCount.ToString());
count = testCount.ToString();
}
}
The above works because the new Null-conditional Operators are thread-safe:
Another use for the null-condition member access is invoking delegates
in a thread-safe way with much less code. ... The new way is
thread-safe because the compiler generates code to evaluate
PropertyChanged one time only, keeping the result in a temporary
variable.
To Test it you can edit your ViewModel method and have the view call it on an event like on page loaded or a button click.
public sealed partial class MainPage : Page
{
ViewModelexample obj = null;
public MainPage()
{
this.InitializeComponent();
obj = new ViewModelexample();
this.DataContext = obj;
}
public void OnSomeEventHandler() {
obj.Method();
}
}
I was under the assumption that binding would take care of the cross threading call.
No, this is only true for some MVVM frameworks (such as WPF). For this reason, I prefer to treat all my ViewModels as belonging to the UI.
With your code sample, you should be able to use Progress<T>:
public async void Methodasync()
{
var progress = new Progress<int>(value =>
{
count = value;
onPropertyChanged("count");
});
await Method(progress);
}
public Task Method(IProgress<int> progress)
{
return Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Task.Delay(1000).Wait();
Debug.WriteLine(i.ToString());
progress.Report(i);
}
});
}
Also note that I changed from StartNew to Run. As a general rule, don't use StartNew for reasons I describe on my blog.
If you "really for serious" cannot use Progress<T> for some odd reason, then you can use Reactive Extensions or SynchronizationContext directly.
Example using SynchronizationContext:
public Task Method()
{
var context = SynchronizationContext.Current;
return Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Task.Delay(1000).Wait();
Debug.WriteLine(i.ToString());
var localI = i;
context.Post(() =>
{
count = localI;
onPropertyChanged("count");
});
}
});
}
The localI thing is to avoid closing over the loop variable.
Related
I'm trying to update a progress bar from two separate tasks at the same time. I've tried it a number of ways but haven't had much success. See below full code example. In WPF, add a progressbar, bind Maximum to TextMax and Value to TextProgress and you will notice that the progress bar only fills to about half way.
NOTE: This is not my actual solution, just a sample of what I am doing that shows the issue that I threw together, please ignore code style/pattern issues.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace DelegateTesting
{
public partial class MainWindow : Window
{
ResultsItemViewModel _ViewModel = new ResultsItemViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _ViewModel;
TextProcessing();
}
private static void Method1(
Action<int> reportProgress)
{
var progress = 0;
for(int i = 0;i<100;i++)
{
//Thread.Sleep(200);
reportProgress?.Invoke(++progress);
}
}
private static void Method2(
Action<int> reportProgress)
{
var progress = 0;
for (int i = 0; i < 100; i++)
{
//Thread.Sleep(200);
reportProgress?.Invoke(++progress);
}
}
private async Task TextProcessing()
{
_ViewModel.TextMax += 100;
_ViewModel.TextMax += 100;
var dispatcher = Application.Current.Dispatcher;
var reportProgress = dispatcher.MakeInvoker<int>(p => _ViewModel.TextProgress = p);
await Task.WhenAll(
Task.Run(() => Method1(reportProgress)),
Task.Run(() => Method2(reportProgress)));
}
}
public static class DispatcherHelper
{
public static Action<T> MakeInvoker<T>(
this Dispatcher dispatcher,
Action<T> action,
DispatcherPriority priority = DispatcherPriority.Normal)
{
return o => dispatcher.BeginInvoke(priority, action, o);
}
}
public class ResultsItemViewModel : INotifyPropertyChanged
{
int _textProgress, _textMax;
public int TextProgress
{
get => _textProgress;
set
{
_textProgress = value;
NotifyPropertyChanged();
}
}
public int TextMax
{
get => _textMax;
set
{
_textMax = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You are not awaiting TextProcessing. You cannot just call await in the middle. You can either go asynchronous all the way or not at all.
Also, doing work in a constructor is not a good practice.
In order for this to work you have to allow WPF to handle async calls for you. I assume you want to start an action when someone does something, e.g.:
protected async void Button_OnClick(Object sender, EventArgs e)
{
await TextProcessing();
}
You could always bind it to an event that starts a window or something like that.
To be honest your code is very unclear to me, so this might help you understand what you actually need:
int _textProgress, _textMax;
public int TextProgress
{
get => _textProgress;
set
{
_textProgress = value;
NotifyPropertyChanged();
}
}
public int TextMax
{
get => _textMax;
set
{
_textMax = value;
NotifyPropertyChanged();
}
}
protected async void Button_OnClick(Object sender, EventArgs e)
{
TextMax = 0;
var t1 = Task.Run(() => {
TextProgress += 50;
});
var t2 = Task.Run(() => {
TextProgress += 50;
});
await Task.WhenAll(t1, t2);
}
And in your view you should have some kind of button with command for Button_OnClick and progress bar:
<ProgressBar Maximum="100" Height="50" Value="{Binding TextProgress}"></ProgressBar>
One more thing. It looks like you have a ViewModel, but you are doing work in a view. You should move that logic to your ViewModel.
I have a class, say Person, with an Id and a name. This class properly implements INotifyPropertyChanged
Addition: some people asked for class Person.
My real problem is a more elaborate class, I've simplified it to a fairly simple POCO to be certain it was not because of my class.
Originally:
public class Person
{
public int Id {get; set;}
public string Name {get; set;}
}
For updates it needed to implement INofityChanged. The full code is at the end of this question
StackOverflow: How to properly implement INotifyPropertyChanged
I have a System.Windows.Forms.Form
This form has a BindingSource.
The DataSource property of the binding source is set to my class Person
I have a DataGridView that is bound to the BindingSource
I have added several Person instances to the binding source
The added persons are properly shown.
If I programmatically change a Person in the bindingsource, the changed value is properly displayed.
So far so good. Problems arise if the Person is changed in a separate thread.
I regularly get the an InvalidOperationException with the message
BindingSource cannot be its own data source. Do not set the DataSource and DataMember properties to values that refer back to BindingSource.
I guess this has something to do with the fact that the update is done in a an awaitable async Task. I know that before updating a user interface item you should check if InvokeRequired and act accordingly.
private void OnGuiItemChanged()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(() => { OnGuiItemChanged(); }));
}
else
{
... // update Gui Item
}
}
However, when using a binding source the changes are handled inside the bindingsource. So I can't check for InvokeRequired
So how to update items that are also stored in a binding source in a non-UI thread?
By request: implementation of class Person and some code of my form
class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int id = 0;
private string name = null;
public int Id
{
get { return this.id; }
set { this.SetField(ref this.id, value); }
}
public string Name
{
get { return this.name; }
set { this.SetField(ref this.name, value); }
}
protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
RaiseEventPropertyChanged(propertyName);
}
}
private void RaiseEventPropertyChanged(string propertyName)
{
var tmpEvent = this.PropertyChanged;
if (tmpEvent != null)
{
tmpEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Some code of the form:
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i < 10; ++i)
{
var person = new Person()
{
Id = i,
Name = "William " + i.ToString(),
};
this.bindingSource1.Add(person);
}
}
private void buttonStart_Click(object sender, EventArgs e)
{
this.cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
Task.Run(() => ChangePersonsAsync(this.cancellationTokenSource.Token));
}
private async Task ChangePersonsAsync(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
foreach (var p in this.bindingSource1)
{
Person person = (Person)p;
person.Id = -person.Id;
}
await Task.Delay(TimeSpan.FromSeconds(0.01), token);
}
}
catch (TaskCanceledException)
{
}
}
As you mentioned, the changes are handled inside the BindingSource class, so the easiest way I see is to replace it with the following
public class SyncBindingSource : BindingSource
{
private SynchronizationContext syncContext;
public SyncBindingSource()
{
syncContext = SynchronizationContext.Current;
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (syncContext != null)
syncContext.Send(_ => base.OnListChanged(e), null);
else
base.OnListChanged(e);
}
}
Just make sure it's created on the UI thread.
I dont know if it possible but what I want is something like that
In WinForm listbox1 has a list of lines(copied from file)
In another Thread and class I run on a List that contains the same lines each line I parse and DoSomething
once I finish with that line I want the index in the listbox to change
from my basic and limited understanding my approach was with an Event to fire in form and than maybe using Invoke (for not to cross thread )
Is there is a way to somehow bind to index of the listbox somehow with my for/foreach loop ?
class form
{
listBoxScript.SetSelected(ScriptCounter, true);<--bind the ScriptCounter?
}
in another Class
class RunScript
{
//..
public void RunScriptList()
{
ScriptCounter = 0 ;
foreach ( var cell in ScriptList)
{
ScriptCounter ++;
//DoSomething
}
}
}
Make sure you implement INotifyPropertyChanged in RunScript class. Here's a complete sample:
class RunScript : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int scriptCounter;
private ISynchronizeInvoke invoker;
public RunScript(ISynchronizeInvoke invoker)
{
if (invoker == null) throw new ArgumentNullException("invoker");
this.invoker = invoker;
}
public async void RunScriptList()
{
ScriptCounter = 0;
foreach (var cell in Enumerable.Range(1, 15))
{
ScriptCounter++;
await Task.Delay(1000);
//DoSomething
}
}
public int ScriptCounter
{
get { return scriptCounter; }
set
{
scriptCounter = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
Action action = () => handler(this, new PropertyChangedEventArgs(propertyName));
invoker.Invoke(action, null);
}
}
}
private RunScript rs;
public Form1()
{
InitializeComponent();
rs = new RunScript(this)
var binding = new Binding("SelectedIndex", rs, "ScriptCounter", false, DataSourceUpdateMode.OnPropertyChanged);
listBox1.DataBindings.Add(binding);
}
private void button1_Click(object sender, EventArgs e)
{
rs.RunScriptList();
}
Note I have used async/await in RunScriptList, If you do it in another thread you need to fire PropertyChanged event in main thread to avoid cross thread exception.
I'm working on a football simulator and i have 9 matches in backgroung on separate threads. And in the method what's in the heart of each thread, there is an event. And when that even occurs (when a goal is "kicked"), I want to update a label (named goalLabel) on the form with the partial result. I wrote a code...:
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0) homeGoals++;
else awawyGoals++;
if (goal != null) goal(this); //(goal is an event)
Thread.Sleep(1000);
} //this is the full method
...where on each match the exact count of the goals will be 6(and the result will be 3 - 3), so with 9 (9 is fix too) background matches, the goalLabel should change text (6*9=)54 times. However it only changes a few times.
And here's the event's eventhandler method:
public void GoalEventHandler(Match match)
{
string akt = string.Format("{0} {1} - {2} {3}", match.Opps[0].Name, match.Hgoals, match.Agoals, match.Opps[1].Name);
UpdateGoalLabel(akt);
}
And the UpdateGoalLabel method:
public void UpdateGoalLabel(string update)
{
if (InvokeRequired)
{
MyDel del = new MyDel(UpdateGoalLabel); // yeah, I have a delegate for it: delegate void MyDel(string update);
Invoke(del, update);
}
else
{
lock (this) // if this lock isn't here, it works the same way
{
this.goalLabel.Text = update;
}
}
}
So I can reach and change the label's text, but I dont why it don't changes 54 times. And that would be the goal, to get notified after every single goal.
Any idea?
Thank you in advance.
Update #1:
I'm using VS2010.
Here's the code where I launch the threads:
List<Thread> allMatches = new List<Thread>();
foreach (Match match in matches)
{
Thread newtmatch = new Thread(match.PlayMatch); //this is the first code block I wrote
allMatches.Add(newtmatch);
newtmatch.Start();
}
Update #2:
Here's where I attach the eventhandlers (this is in the same method, few lines above the previous code block):
matches = new List<Match>();
foreach (Team[] opponents in Program.cm.nextMatches)
{
Match vmi = new Match(opponents);
matches.Add(vmi);
vmi.goal += new Match.goalevent(GoalEventHandler);
}
//Program.cm.nextMatches is a List<Team[]> object that contains the pairs of teams for the next matches;
And I convert those Team arrays to a Match object, because this class has two Team fields, and has the event and the PlayMatch method which is still the method that contains (only) the first code block.
Let me make sure I am understanding what you are doing. You have started 9 threads that loop 6 times, each time updating a text box and sleeping for 1 second. From the sounds of it they are all updating the same label. You commented that if you push the updates to a list you get them all, my guess is that all 9 threads are updating so fast you can't see it and the last one wins.
I am not sure how you are building your UI but I think this would be perfect for WPF and MVVM(Model-View-ViewModel). I wouldn't use WinForms unless you had a damn good reason, WPF is so much easier to work with.
I would create a few view models:
public class MainWindowViewModel : DispatcherObject, INotifyPropertyChanged
{
public MainWindowViewModel()
{
Matches = new ObservableCollection<MatchViewModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<MatchViewModel> _matches;
public ObservableCollection<MatchViewModel> Matches
{
get { return _matches; }
set
{
_matches = value;
OnPropertyChanged("Matches");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MatchViewModel : DispatcherObject, INotifyPropertyChanged
{
public MatchViewModel()
{
HomeTeam = new TeamViewModel();
AwayTeam = new TeamViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private TeamViewModel _homeTeam;
public TeamViewModel HomeTeam
{
get { return _homeTeam; }
set
{
_homeTeam = value;
OnPropertyChanged("HomeTeam");
}
}
private TeamViewModel _awayTeam;
public TeamViewModel AwayTeam
{
get { return _awayTeam; }
set
{
_awayTeam = value;
OnPropertyChanged("AwayTeam");
}
}
public void PlayMatch()
{
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0)
OnGoalScored(HomeTeam);
else
OnGoalScored(AwayTeam);
Thread.Sleep(1000);
}
}
private void OnGoalScored(TeamViewModel team)
{
if (!team.Dispatcher.CheckAccess())
{
team.Dispatcher.Invoke((Action<TeamViewModel>)OnGoalScored, team);
}
else
{
team.Score++; // do other stuff here
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class TeamViewModel : DispatcherObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private int _score;
public int Score
{
get { return _score; }
set
{
_score = value;
OnPropertyChanged("Score");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in the program class on the UI thread, do something like this:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindowViewModel mainWindow = new MainWindowViewModel();
List<Thread> matchThreads = new List<Thread>();
foreach (Team[] opponents in Program.cm.nextMatches)
{
MatchViewModel match = new MatchViewModel();
match.HomeTeam.Name = opponents[0].Name;
match.AwayTeam.Name = opponents[1].Name;
mainWindow.Matches.Add(match);
Thread matchThread = new Thread(match.PlayMatch);
matchThreads.Add(matchThread);
matchThread.Start();
}
MainWindow = new MainWindow();
MainWindow.DataContext = mainWindow;
MainWindow.Show();
}
I did mine in the override for OnStartup because in VS2010 when you create a project you would have your starup item inherit from System.Windows.Application.
I have this simple UI for testing:
<Window x:Class="TestMatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Matches}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1} - {2} {3}">
<Binding Path="HomeTeam.Name"/>
<Binding Path="HomeTeam.Score"/>
<Binding Path="AwayTeam.Name"/>
<Binding Path="AwayTeam.Score"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
This code is pretty crude and thrown together to give a quick demo of a MVVM way of doing this. I was able to get all 9 teams to update every second. I had some filler code to simulate the objects you had:
public partial class Program
{
protected override void OnStartup(StartupEventArgs e)
{
...
}
private class Team
{
public string Name { get; set; }
}
private static class cm
{
static cm()
{
nextMatches =
new Team[][]
{
new[] { new Team { Name = "TeamA" }, new Team { Name = "TeamB" }},
new[] { new Team { Name = "TeamC" }, new Team { Name = "TeamD" }},
new[] { new Team { Name = "TeamE" }, new Team { Name = "TeamF" }},
new[] { new Team { Name = "TeamG" }, new Team { Name = "TeamH" }},
new[] { new Team { Name = "TeamI" }, new Team { Name = "TeamJ" }},
new[] { new Team { Name = "TeamK" }, new Team { Name = "TeamL" }},
new[] { new Team { Name = "TeamM" }, new Team { Name = "TeamN" }},
new[] { new Team { Name = "TeamO" }, new Team { Name = "TeamP" }},
new[] { new Team { Name = "TeamQ" }, new Team { Name = "TeamR" }},
};
}
public static Team[][] nextMatches { get; set; }
}
}
I've had problems with UI refreshes too and have worked with "BackgroundWorker" threading directly instead of just "Thread" class. The BackgroundWorker thread allows you to report progress changed explicitly and call some method outside the thread (ie: the calling UI thread that invoked the secondary thread). So, I've created a custom class derived from the background worker, and also created my own version of the "Match" class you have described
public class Match
{
public Match( string Home, string Away )
{
HomeTeam = Home;
HomeGoals = 0;
AwayTeam = Away;
AwayGoals = 0;
}
// simple properties with PROTECTED setters, yet readable by anyone
public string HomeTeam
{ get; protected set; }
public int HomeGoals
{ get; protected set; }
public string AwayTeam
{ get; protected set; }
public int AwayGoals
{ get; protected set; }
// just place-holder since I don't know rest of your declarations
public EventHandler goal;
public void PlayMatch()
{
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0)
HomeGoals++;
else
AwayGoals++;
// Report to anyone listening that a score was made...
if (goal != null)
goal(this, null);
Thread.Sleep(1000);
}
}
}
// Now, the background worker
public class MatchBGW : BackgroundWorker
{
// each background worker preserves the "Match" instance it is responsible for.
// this so "ReportProgress" can make IT available for getting values.
public Match callingMatch
{ get; protected set; }
// require parameter of the match responsible for handling activity
public MatchBGW(Match m)
{
// preserve the match started by the background worker activity
callingMatch = m;
// tell background worker what method to call
// using lambda expression to cover required delegate parameters
// and just call your function ignoring them.
DoWork += (sender, e) => m.PlayMatch();
// identify we can report progress
WorkerReportsProgress = true;
// Attach to the match. When a goal is scored, notify ME (background worker)
m.goal += GoalScored;
}
// this is called from the Match.
public void GoalScored(object sender, EventArgs e)
{
// Now, tell this background worker to notify whoever called IT
// that something changed. Can be any percent, just something to
// trigger whoever called this background worker, so reported percent is 1
ReportProgress(1);
}
}
Now, from your calling window that has the label, such as started from a button click...
private void button1_Click(object sender, RoutedEventArgs e)
{
// create your "Matches" between teams
matches = new List<Match>();
matches.Add(new Match("A", "B"));
matches.Add(new Match("C", "D"));
matches.Add(new Match("E", "F"));
foreach (Match m in matches)
{
// create an instance of background worker and pass in your "match"
MatchBGW bgw = new MatchBGW(m);
// tell the background worker that if it is notified to "
// report progress" to, to pass itself (background worker object)
// to this class's SomeoneScored method (same UI thread as textbox)
bgw.ProgressChanged += SomeoneScored;
// Now, start the background worker and start the next match
bgw.RunWorkerAsync();
}
}
// This is called from the background worker via "ReportProgress"
public void SomeoneScored(object sender, ProgressChangedEventArgs e)
{
// Just ensuring that the background worker IS that of what was customized
if (sender is MatchBGW)
{
// get whatever "match" associated with the background worker
Match m = ((MatchBGW)sender).callingMatch;
// add it's latest score with appropriate home/away team names
this.txtAllGoals.Text +=
string.Format("{0} {1} - {2} {3}\r\n",
m.HomeTeam, m.HomeGoals, m.AwayGoals, m.AwayTeam );
}
}
Yes, it may be more code, but I am explicitly handling who is called back and reporting what on their proper thread... no BeginInvoke required test / actions. Just an alternative to your issue.
I have some code that raises PropertyChanged events and I would like to be able to unit test that the events are being raised correctly.
The code that is raising the events is like
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public string MyProperty
{
set
{
if (_myProperty != value)
{
_myProperty = value;
NotifyPropertyChanged("MyProperty");
}
}
}
}
I get a nice green test from the following code in my unit tests, that uses delegates:
[TestMethod]
public void Test_ThatMyEventIsRaised()
{
string actual = null;
MyClass myClass = new MyClass();
myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
{
actual = e.PropertyName;
};
myClass.MyProperty = "testing";
Assert.IsNotNull(actual);
Assert.AreEqual("MyProperty", actual);
}
However, if I then try and chain the setting of properties together like so:
public string MyProperty
{
set
{
if (_myProperty != value)
{
_myProperty = value;
NotifyPropertyChanged("MyProperty");
MyOtherProperty = "SomeValue";
}
}
}
public string MyOtherProperty
{
set
{
if (_myOtherProperty != value)
{
_myOtherProperty = value;
NotifyPropertyChanged("MyOtherProperty");
}
}
}
My test for the event fails - the event that it captures is the event for the MyOtherProperty.
I'm pretty sure the event fires, my UI reacts like it does, but my delegate only captures the last event to fire.
So I'm wondering:
1. Is my method of testing events correct?
2. Is my method of raising chained events correct?
Everything you've done is correct, providing you want your test to ask "What is the last event that was raised?"
Your code is firing these two events, in this order
Property Changed (... "My Property" ...)
Property Changed (... "MyOtherProperty" ...)
Whether this is "correct" or not depends upon the purpose of these events.
If you want to test the number of events that gets raised, and the order they get raised in, you can easily extend your existing test:
[TestMethod]
public void Test_ThatMyEventIsRaised()
{
List<string> receivedEvents = new List<string>();
MyClass myClass = new MyClass();
myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
{
receivedEvents.Add(e.PropertyName);
};
myClass.MyProperty = "testing";
Assert.AreEqual(2, receivedEvents.Count);
Assert.AreEqual("MyProperty", receivedEvents[0]);
Assert.AreEqual("MyOtherProperty", receivedEvents[1]);
}
If you're doing TDD then event testing can start to generate a lot of repetitive code. I wrote an event monitor that enables a much cleaner approach to unit test writing for these situations.
var publisher = new PropertyChangedEventPublisher();
Action test = () =>
{
publisher.X = 1;
publisher.Y = 2;
};
var expectedSequence = new[] { "X", "Y" };
EventMonitor.Assert(test, publisher, expectedSequence);
Please see my answer to the following for more details.
Unit testing that an event is raised in C#, using reflection
This is very old and probably wont even be read but with some cool new .net features I have created an INPC Tracer class that allows that:
[Test]
public void Test_Notify_Property_Changed_Fired()
{
var p = new Project();
var tracer = new INCPTracer();
// One event
tracer.With(p).CheckThat(() => p.Active = true).RaisedEvent(() => p.Active);
// Two events in exact order
tracer.With(p).CheckThat(() => p.Path = "test").RaisedEvent(() => p.Path).RaisedEvent(() => p.Active);
}
See gist: https://gist.github.com/Seikilos/6224204
Below is a slightly changed Andrew's code which instead of just logging the sequence of raised events rather counts how many times a specific event has been called. Although it is based on his code I find it more useful in my tests.
[TestMethod]
public void Test_ThatMyEventIsRaised()
{
Dictionary<string, int> receivedEvents = new Dictionary<string, int>();
MyClass myClass = new MyClass();
myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
{
if (receivedEvents.ContainsKey(e.PropertyName))
receivedEvents[e.PropertyName]++;
else
receivedEvents.Add(e.PropertyName, 1);
};
myClass.MyProperty = "testing";
Assert.IsTrue(receivedEvents.ContainsKey("MyProperty"));
Assert.AreEqual(1, receivedEvents["MyProperty"]);
Assert.IsTrue(receivedEvents.ContainsKey("MyOtherProperty"));
Assert.AreEqual(1, receivedEvents["MyOtherProperty"]);
}
Based on this article, i have created this simple assertion helper :
private void AssertPropertyChanged<T>(T instance, Action<T> actionPropertySetter, string expectedPropertyName) where T : INotifyPropertyChanged
{
string actual = null;
instance.PropertyChanged += delegate (object sender, PropertyChangedEventArgs e)
{
actual = e.PropertyName;
};
actionPropertySetter.Invoke(instance);
Assert.IsNotNull(actual);
Assert.AreEqual(propertyName, actual);
}
With this method helper, the test becomes really simple.
[TestMethod()]
public void Event_UserName_PropertyChangedWillBeFired()
{
var user = new User();
AssertPropertyChanged(user, (x) => x.UserName = "Bob", "UserName");
}
Don't write a test for each member - this is much work
(maybe this solution is not perfect for every situation - but it shows a possible way. You might need to adapt it for your use case)
It's possible to use reflection in a library to test if your members are all responding to your property changed event correctly:
PropertyChanged event is raised on setter access
Event is raised correct (name of property equals argument of raised event)
The following code can be used as a library and shows how to test the following generic class
using System.ComponentModel;
using System.Linq;
/// <summary>
/// Check if every property respons to INotifyPropertyChanged with the correct property name
/// </summary>
public static class NotificationTester
{
public static object GetPropertyValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
public static bool Verify<T>(T inputClass) where T : INotifyPropertyChanged
{
var properties = inputClass.GetType().GetProperties().Where(x => x.CanWrite);
var index = 0;
var matchedName = 0;
inputClass.PropertyChanged += (o, e) =>
{
if (properties.ElementAt(index).Name == e.PropertyName)
{
matchedName++;
}
index++;
};
foreach (var item in properties)
{
// use setter of property
item.SetValue(inputClass, GetPropertyValue(inputClass, item.Name));
}
return matchedName == properties.Count();
}
}
The tests of your class can now be written as. (maybe you want to split the test into "event is there" and "event raised with correct name" - you can do this yourself)
[TestMethod]
public void EveryWriteablePropertyImplementsINotifyPropertyChangedCorrect()
{
var viewModel = new TestMyClassWithINotifyPropertyChangedInterface();
Assert.AreEqual(true, NotificationTester.Verify(viewModel));
}
Class
using System.ComponentModel;
public class TestMyClassWithINotifyPropertyChangedInterface : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private int id;
public int Id
{
get { return id; }
set { id = value;
NotifyPropertyChanged("Id");
}
}
}
I've made an extension here:
public static class NotifyPropertyChangedExtensions
{
private static bool _isFired = false;
private static string _propertyName;
public static void NotifyPropertyChangedVerificationSettingUp(this INotifyPropertyChanged notifyPropertyChanged,
string propertyName)
{
_isFired = false;
_propertyName = propertyName;
notifyPropertyChanged.PropertyChanged += OnPropertyChanged;
}
private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == _propertyName)
{
_isFired = true;
}
}
public static bool IsNotifyPropertyChangedFired(this INotifyPropertyChanged notifyPropertyChanged)
{
_propertyName = null;
notifyPropertyChanged.PropertyChanged -= OnPropertyChanged;
return _isFired;
}
}
There is the usage:
[Fact]
public void FilesRenameViewModel_Rename_Apply_Execute_Verify_NotifyPropertyChanged_If_Succeeded_Through_Extension_Test()
{
// Arrange
_filesViewModel.FolderPath = ConstFolderFakeName;
_filesViewModel.OldNameToReplace = "Testing";
//After the command's execution OnPropertyChanged for _filesViewModel.AllFilesFiltered should be raised
_filesViewModel.NotifyPropertyChangedVerificationSettingUp(nameof(_filesViewModel.AllFilesFiltered));
//Act
_filesViewModel.ApplyRenamingCommand.Execute(null);
// Assert
Assert.True(_filesViewModel.IsNotifyPropertyChangedFired());
}