I "inherited" a WPF application following the MVVM pattern (as far as I know).
In most windows the initial data retrieval is done in the view model like this:
Dim t1 As Task = Task.Factory.StartNew(Of IEnumerable(Of Item))(AddressOf Me.GetItems) _
.ContinueWith(Sub(t2 As Task(Of IEnumerable(Of Item)))
For Each item As Item In t2.Result
_items.Add(New ItemViewModel(item))
Next item
End Sub, TaskScheduler.FromCurrentSynchronizationContext)
_items
is an ObservableCollection
, and a ListView
in the window is bound to it.
Considering a few code comments this is done like this to retrieve the data while the UI is still initializing and loading and so on.
This seems to work as expected.
Now I'd like to show some kind of loading animation while the data is initially retrieved or while the data is refreshed. For that I added the animated control to the XAML and bound it's Visibility
to a boolean property in the view model.
When I test this setting and set the boolean property in the view model to True
, then my animated control is shown and it's spinning and spinning until I set the boolean property in the view model to False
again.
But when I do something like this
Me.ShowBusy = True
<Data Retrieval Code shown above>
Me.ShowBusy = False
the data is still retrieved, but I don't see my animation.
I assume it's because the data retrieval code isn't awaited (as far as I can see), so the switching on and off of the Visibility
of the animated control happens so fast, that I don't see anything in the UI.
Is there an easy way to achieve my goal without spreading Async
s and Await
s in my code?
I'm not very familiar with Async
and Await
yet and I'd like to read and learn about it first, before I decide about the proper way to use it in this program.
Answers are fine in VB.net or C#.
Edit:
Following Elshasps suggestion I tried a few different things:
First test (left the TaskScheduler.FromCurrentSynchronizationContext
where it was):
Dim t1 As Task = Task.Factory.StartNew(Of IEnumerable(Of Item))(AddressOf Me.GetItems) _
.ContinueWith(Sub(t2 As Task(Of IEnumerable(Of Item)))
For Each item As Item In t2.Result
_items.Add(New ItemViewModel(item))
Next item
End Sub, TaskScheduler.FromCurrentSynchronizationContext) _
.ContinueWith(Sub() Me.ShowBusy = False)
This showed the animation, but only for a very short time, it seems it's only shown while the loaded data gets shown in the ListView
, but not while the data is retrieved from the database).
Second test (put the TaskScheduler.FromCurrentSynchronizationContext
at the end again):
Dim t1 As Task = Task.Factory.StartNew(Of IEnumerable(Of Item))(AddressOf Me.GetItems) _
.ContinueWith(Sub(t2 As Task(Of IEnumerable(Of Item)))
For Each item As Item In t2.Result
_items.Add(New ItemViewModel(item))
Next item
End Sub) _
.ContinueWith(Sub()
Me.ShowBusy = False
End Sub, TaskScheduler.FromCurrentSynchronizationContext)
Now I get an exception "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread" while I add the items to _items
.
But I guess I get around this exception reading about it in other articles on Stack Overflow.
You need to change the property value after the Task is executed.
Something like this:
Me.ShowBusy = true;
Task.Run(/* Method that performs data loading */)
.ContinueWith(_ => Me.ShowBusy = false);
P.S. I still recommend that you learn how to create and use asynchronous methods. They greatly simplify coding, testing, bug detection, etc.