MVVM Tasks and UI Updating
-
I'm struggling to get my MVVM WPF app to work with a set of Tasks updating the UI. I've got a
Command
that starts X number of tasks off, for each task I'd like the UI to update when started and when finished. I've also got a secondCommand
to cancel the task, which I'd obviously like working (the code does but as the UI locks the button doesn't) I've used aListBox
bound to anObvservableCollection<string>
for the results, as this seemed better than binding to astring
. At the moment I get the UI lockup until all tasks are finished. This is with or without aTask.WaitAll
. The results are correct, the tasks don't always start/finish in the same order so they're obviously being generated and updating the collection asynchronously. I've tried an AysncObservableCollection (as per this SO answer) I've tried using the Dispatcher in my method that adds a item to the collection. I've tried variations on the Binding parameters. Can someone give me some hints :confused: Surely people have created a status log with async stuff in MVVM before?? Do I need to move the task creation to the Model? I've seen mention of making sure the Collection is initiated on/by the View, not sure I understand that! The View has a DataContext that's set to my ViewModel. Do they mean I've got to create the Collection in the code behind of the View? that doesn't seem right. Oh this is .NET4.0 on XP, and I'm not using any MVVM frameworks (didn't think that was a good idea as I'm still learning) -
I'm struggling to get my MVVM WPF app to work with a set of Tasks updating the UI. I've got a
Command
that starts X number of tasks off, for each task I'd like the UI to update when started and when finished. I've also got a secondCommand
to cancel the task, which I'd obviously like working (the code does but as the UI locks the button doesn't) I've used aListBox
bound to anObvservableCollection<string>
for the results, as this seemed better than binding to astring
. At the moment I get the UI lockup until all tasks are finished. This is with or without aTask.WaitAll
. The results are correct, the tasks don't always start/finish in the same order so they're obviously being generated and updating the collection asynchronously. I've tried an AysncObservableCollection (as per this SO answer) I've tried using the Dispatcher in my method that adds a item to the collection. I've tried variations on the Binding parameters. Can someone give me some hints :confused: Surely people have created a status log with async stuff in MVVM before?? Do I need to move the task creation to the Model? I've seen mention of making sure the Collection is initiated on/by the View, not sure I understand that! The View has a DataContext that's set to my ViewModel. Do they mean I've got to create the Collection in the code behind of the View? that doesn't seem right. Oh this is .NET4.0 on XP, and I'm not using any MVVM frameworks (didn't think that was a good idea as I'm still learning)How are you binding the
ListBox
andObservableCollection<string>
? Are you creating theObservableCollection
in the ViewModel? Is it bound to theItemsSource
on theListBox
? If you can show some of the code/xaml we can provide more assistance. -
How are you binding the
ListBox
andObservableCollection<string>
? Are you creating theObservableCollection
in the ViewModel? Is it bound to theItemsSource
on theListBox
? If you can show some of the code/xaml we can provide more assistance.Ok, In my view XAML I think the only relevant bits are:
I've tried some of the Binding options like Async. There's nothing in the .xaml.cs file apart from the initialize call. Oh and its in a UserControl because my app has two modes so I'm using a tab control displaying two UserControls. For my ViewModel, I've got an abstract base ViewModel then a design time and run time classes. All the child classes do is set some properties, there's no other methods or additional properties. In the base ViewModel class the collection is defined:
private ObservableCollection \_results = new ObservableCollection(); public ObservableCollection Results { get { return \_results; } set { \_results = value; RaisePropertyChanged("Results"); } }
I've tried the creation of the collection in the constructor of the BaseViewModel and in the constructors of the child classes. And in case its my Task stuff that's wrong:
private void DoRunTestCommand() { \_cancellationTokenSource = new CancellationTokenSource(); Log(String.Format("Submitting {0} requests, every {1}ms, {2} at time." , Model.NumberOfRequests , Model.MSBetweenRequests , Model.NumberOfConcurrentRequests)); CancellationToken cancellationToken = \_cancellationTokenSource.Token; Task\[\] tasks = new Task\[Model.NumberOfRequests\]; for (int i = 0; i < Model.NumberOfRequests; i++) { int idx = i; Task r = Task.Factory.StartNew((state) => { SubmitRequest((int) state); cancellationToken.ThrowIfCancellationRequested(); } , i , cancellationToken , TaskCreationO
-
Ok, In my view XAML I think the only relevant bits are:
I've tried some of the Binding options like Async. There's nothing in the .xaml.cs file apart from the initialize call. Oh and its in a UserControl because my app has two modes so I'm using a tab control displaying two UserControls. For my ViewModel, I've got an abstract base ViewModel then a design time and run time classes. All the child classes do is set some properties, there's no other methods or additional properties. In the base ViewModel class the collection is defined:
private ObservableCollection \_results = new ObservableCollection(); public ObservableCollection Results { get { return \_results; } set { \_results = value; RaisePropertyChanged("Results"); } }
I've tried the creation of the collection in the constructor of the BaseViewModel and in the constructors of the child classes. And in case its my Task stuff that's wrong:
private void DoRunTestCommand() { \_cancellationTokenSource = new CancellationTokenSource(); Log(String.Format("Submitting {0} requests, every {1}ms, {2} at time." , Model.NumberOfRequests , Model.MSBetweenRequests , Model.NumberOfConcurrentRequests)); CancellationToken cancellationToken = \_cancellationTokenSource.Token; Task\[\] tasks = new Task\[Model.NumberOfRequests\]; for (int i = 0; i < Model.NumberOfRequests; i++) { int idx = i; Task r = Task.Factory.StartNew((state) => { SubmitRequest((int) state); cancellationToken.ThrowIfCancellationRequested(); } , i , cancellationToken , TaskCreationO
Solved it I think. I mentioned that I was trying the
AsyncObservableCollection
, apparently if I use that then I don't want to passTaskScheduler.FromCurrentSynchronizationContext()
to the task. I don't understand why (I guess a conflict somewhere)...but not sure if I need to understand either :) So my ObservableCollection is now defined as:private ObservableCollection \_results = new AsyncObservableCollection(); public ObservableCollection Results { get { return \_results; } set { \_results = value; RaisePropertyChanged("Results"); } }
And my tasks updated the ListBox as and when they start/finish. So I've just got to check if cancellation works, and see if I can still use WaitAll. Ideally I'd like a message when the tasks have all been started and finished (more a WhenAll (if that even exists)), at the moment I get that in the middle. But if I use WaitAll then I don't get the async updating. Note for any others if your using .NET 4.5 then I don't think any of this is an issue as they changed the ObversableCollection.
-
Solved it I think. I mentioned that I was trying the
AsyncObservableCollection
, apparently if I use that then I don't want to passTaskScheduler.FromCurrentSynchronizationContext()
to the task. I don't understand why (I guess a conflict somewhere)...but not sure if I need to understand either :) So my ObservableCollection is now defined as:private ObservableCollection \_results = new AsyncObservableCollection(); public ObservableCollection Results { get { return \_results; } set { \_results = value; RaisePropertyChanged("Results"); } }
And my tasks updated the ListBox as and when they start/finish. So I've just got to check if cancellation works, and see if I can still use WaitAll. Ideally I'd like a message when the tasks have all been started and finished (more a WhenAll (if that even exists)), at the moment I get that in the middle. But if I use WaitAll then I don't get the async updating. Note for any others if your using .NET 4.5 then I don't think any of this is an issue as they changed the ObversableCollection.
-
I'm struggling to get my MVVM WPF app to work with a set of Tasks updating the UI. I've got a
Command
that starts X number of tasks off, for each task I'd like the UI to update when started and when finished. I've also got a secondCommand
to cancel the task, which I'd obviously like working (the code does but as the UI locks the button doesn't) I've used aListBox
bound to anObvservableCollection<string>
for the results, as this seemed better than binding to astring
. At the moment I get the UI lockup until all tasks are finished. This is with or without aTask.WaitAll
. The results are correct, the tasks don't always start/finish in the same order so they're obviously being generated and updating the collection asynchronously. I've tried an AysncObservableCollection (as per this SO answer) I've tried using the Dispatcher in my method that adds a item to the collection. I've tried variations on the Binding parameters. Can someone give me some hints :confused: Surely people have created a status log with async stuff in MVVM before?? Do I need to move the task creation to the Model? I've seen mention of making sure the Collection is initiated on/by the View, not sure I understand that! The View has a DataContext that's set to my ViewModel. Do they mean I've got to create the Collection in the code behind of the View? that doesn't seem right. Oh this is .NET4.0 on XP, and I'm not using any MVVM frameworks (didn't think that was a good idea as I'm still learning)Use the
INotifyPropertyChanged
event on the observable collection. This will be a neater and simpler solution.WP Apps - Color Search | Arctic | XKCD | Sound Meter | Speed Dial
-
Use the
INotifyPropertyChanged
event on the observable collection. This will be a neater and simpler solution.WP Apps - Color Search | Arctic | XKCD | Sound Meter | Speed Dial
How do you mean? The ViewModel already had that, and that on its own didn't work. No items were added until the end.
private ObservableCollection \_results = new ObservableCollection(); public ObservableCollection Results { get { return \_results; } set { \_results = value; RaisePropertyChanged("Results"); } }
Or do you mean create a class that derives from ObservableCollection and implement it there?