views:

1066

answers:

3

So I've got a task that can be preformed by my GUI that will pull information to populate a ViewModel with SQL database query response. Assume I want to start this task and keep my gui free to proceed with other things, and in the meantime play a "searching" animation, what is the proper way to do this in WPF/MVVM? I'm assuming you need to start an asynchronous process and set a bool tied to a datatrigger that starts the animation storyboard. But what do I use to start the process? Thread? I'm still new to WPF and just want to make sure i'm using the right classes available to me.

+4  A: 

I use the BackgroundProcess thread to do things like this.

Here's a link to MSDN on this: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Additional details related to this.

You have three EVENTS associated with your BackgroundProcess object: DoWork, ReportProgress and WorkCompleted.

Now, to use this--and to use it with an observablecollection--you'll want to tell the BackgroundProcess object to be able to REPORT PROGRESS (this is a boolean property that I always explicitly set, along with the allows cancellation).

Now to start off a process, you'll call the RunWorkerAsync method. This method has the ability to accept an OBJECT variable in case you need to pass it data (if you want more than 1 value, create a struct to be passed into RunWorkerAsync).

The RunWorkerAsync fires off the DoWork event, so control heads over to your DoWork event handler. Here's (sanitized) code from where I'm utilizing it:

Dim dt As System.Data.DataTable
dt = da.GetDataTable(sql, System.Data.CommandType.Text, params)
For Each row As System.Data.DataRow In dt.Rows
   If loadQuestionsWorker.CancellationPending Then
       e.Cancel = True
       Exit Sub
   End If
   Dim item As New DataObject
   // Assign Item Values
   backgroundProcessObject.ReportProgress(1, item)
Next

What's happening here, is that I'm getting a datatable from my data layer, and then while this backgroundprocess is not being canceled, I'm walking the data table, and when I build a new DataObject, I report that object as being built.

Now, in my ProgressChanged event handler (the ReportProgress method raises the ProgressChanged event) control is back in the UI thread's hands so I can do things like impact the UI and add the element I'm reporting on to an ObservableCollection.

Finally, in my WorkedCompleted event handler (whose corresponding event is raised when the DoWork event handler method runs to completion) I check to see if my progress was canceled (which sometimes means that I want to dump the ObservableCollection) and I may or may not impact the UI (such as removing a "SEARCHING" animation.

Stephen Wrighton
tried this, seems to lock up my GUI tho...
Firoso
if it locks up your GUI you're doing it wrong.
Stephen Wrighton
i was doing it wrong, went to thread, looking at this however i really like this.
Firoso
+1  A: 

You can start a new Thread or BackgroundWorker explicitly, or use a ThreadPool. I'm not aware of any recommended way of doing this in MVVM...

If your asynchronous operation is populating an ObservableCollection bound to a control, note that you can't add items to it on a different thread. You need to do it on the control's dispatcher thread. Or you also can use this collection, which raises the CollectionChanged event on the adequate thread

Thomas Levesque
I think i'm having a related problem here to the Observable collection, my code is MVVM based and so my worker is making changes to the view model... that view model is bound to UI elements... so it's locking up.
Firoso
Binding on scalar properties works fine across threads, the change notification is automatically marshalled on the right thread. For collections, it's not supported. See the link I gave you for a possible solution
Thomas Levesque
A: 
var t = new Thread((ThreadStart)delegate
{
    DoWork(...)
    Dispatcher.BeginInvoke((Action)delegate
    {
          SomethingOntheUiThread(...)
          FinishedWork(...);
    }
});

t.Start();
Jeremiah Morrill