views:

88

answers:

4

I am designing the architecture for a module where a search takes place. the search takes some time, and I would like to have the UI to be responsive, so I get to delegate the retrieval of the data to a class that will do the search on a separate thread.

Then, I can think of two options :

either 1° : the search method returns a viewmodel with empty data as a return value (place holder), but once the search process is over, the viewmodel's members will be updated and the result displayed on screen thanks to data binding,

2° : the search method doesn't have any return type, but once the search process is over, an event is raised and the viewmodel with its final values is passed in the event args so it can be used by the calling code. (and eventually be consumed by a view)

Any thoughts on this one ?

EDIT: Of course, with solution 1° I'm refering to WPF databinding on the objects returned by the search-results "place-holder" objects

A: 

I'd take advantage of data binding and bind your search result presentation control to a observable collection of search results and update that collection in your ViewModel from a background worker thread. Then it doesn't required any callbacks or update messages in you code, it just works as you'd expect taking advantage of the plumbing in place in WPF.

Dave White
Doing updates to INotifyPropertyChanged properties or INotifyCollectionChanged collections (like ObservableCollection) from a background thread will throw exceptions unless you use something like Dispatcher.Invoke, which is essentially creating a callback to the UI thread anyway. Unless you're doing multiple independent updates during DoWork it's usually more straightforward to just apply the changes in the RunWorkerCompleted callback.
John Bowen
I could have been more clear in my answer. I wouldn't actually update the collection from the background worker thread per say. I would have the background worker thread collect the results from my search, then I would take those results and put them into the observable collection that the View is bound to in the ViewModel. That way, the UI stays responsive while search results are acquired and we don't really need to worry about callbacks. There are lots of ways to skin this cat, some more dangerous than others as you've pointed out.
Dave White
the backgroundworker has an event which is raised in the main thread, so : no problems with updating view models values. actually my question was more about whether to use the WPF plumbing for this kind of "delayed reaction" or to manually push the data to the viewmodel when received, which, I believe, Dave answered convincively. At least, that confirms my own feeling, and as no one pointed this to be an heresy, that's the path I'm going to take ;) thanks :)
Salfab
A: 

You could also leverage Priority Binding (http://msdn.microsoft.com/en-us/library/system.windows.data.prioritybinding.aspx). It provides the fast / slow options and you don't have to worry about update threads. Unless of course you are looking to add some visuals to let the user know something is happening.

Scott
+1  A: 

If you use a BackgroundWorker, the design pattern is done for you. Call your search method in the DoWork event handler, and put the results in the Results property of the DoWorkEventArgs that's passed in.

Update the UI with the results in the RunWorkerCompleted event handler (they'll be in the RunWorkerCompletedEventArgs object's Results property).

If you want to update the UI while the search is in progress, have your search method call ReportProgress and update the UI in the ProgressChanged event handler. You can put anything you like into the UserState property of the ProgressChangedEventArgs, including intermediate search results, though you do have to be careful not to pass any objects that the background thread is going to touch as it continues executing.

Robert Rossney
A: 

Here is what I did on a recent project.

The IsBusy property is something I have in the base class for all my viewmodels. It is a boolean that a view can bind to if it wants to display some kind of waiting control like a spinner or whatever.

The _retrieveData field is just an Action that I set up during the construction of the viewmodel. I do this because the viewmodel may get its list of Cars in several different ways - so _retrieveData may have different code in it based on the constructor that was used. After _retrieveData gets the data, it will set the private backer, _cars with the data. So after _retrieveData is done, setting the public Cars to the value with the new data in _cars causes the PropertyChangedEvent which lets the view know to update itself.

So the effect is that when the view goes to get the data for the first time, it returns immediately but gets null. Then a few seconds later, it gets the actual data. During that time, the UI is responsive. And also, the IsBusy is true if the UI wants to let the user know that it is working in the background.

Not sure if this is a good way to handle it, but it is working for me so far.

public List<Car> Cars
{
   get
   {
       if (this._cars == null)
       {
           base.IsBusy = true;

           // Start a background thread to get the data...
           ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object nullObject)
           {
               this._retrieveData.Invoke();
               this.Cars = this._cars;
               base.IsBusy = false;
           }));

           // While waiting on the background thread, return null for now.  When the background thread
           // completes, the setter will raise OnPropertyChanged and the view will know its time to bind...
           return this._cars;
       }

       return this._cars;
   }
   set
   {
       this._cars = value;
       base.OnPropertyChanged("Cars");
   }

}

MarkB