views:

59

answers:

3

I have a WPF list view that is bound to a list of objects. One of the items to be displayed is a calculated property (read-only, returns a string) that takes a small amount of time to calculate. When the window initially loads (or anytime the UI is updated with a Notify event) the UI will hang as this data binding occurs. What I was wondering was a good mechanism to deal with this (ideally I'd like to do something along the lines of greying out the screen with a spinner and text "Processing ..." or similar).

I thought I could do that by capturing the start of the databound event and firing off a story (or similar) and the stopping the story when the end of the databound event was fired, but I cannot find events of that nature.

Is there a recommended mechanism for dealing with long data binding or do the events I'm looking for exist, but I'm looking in the wrong location? Any help would be appreciated.

EDIT: I am able to get a spinning icon (Cursor.Wait) while the data is sourced and data bound (using parts of the solution below), but now need to know when the data binding is complete. The .Loaded event seems to fire once the control is put on the screen (which happens right away), but does not happen when the data is updated. There doesn't appear to be a OnDataBoundCompleted type event for a ListView, any ideas/thoughts on how to be notified when the Data Binding process is completed?

EDIT: Looking at TargetUpdated event now, but getting some odd results. If I put a message box in the event handler for TargetUpdated, then the UI is updated (ListView displays data) and then the message box is shown. If I cut out the message box and just have a variable setting (i.e. IsBusyCursor = Cursors.Arrow) it does that before the ListView displays data.

** SOLUTION: ** I ended up creating a new presentation object and setting Cursor = Wait then looping through the objects I previously bound to the ListView and creating presentation objects out of them (which caused the calculated property to be executed) then once the list of presentation objects were created bound those to the ListView and stet Cursor = Arrow. Disappointed there doesn't appear to be a DataBinding Completed type event (or whatever event is fired once the data binding is complete that updates the UI is available), but this solution appears to work.

+1  A: 

Take a look at a current thread where I explain a basic BackgroundWorker, and see if that gives you enough information.

Essentially, you will have a function that displays your "Please Wait" control, and then uses the BackgroundWorker to do the long-running calculation on another thread. When the calculation is complete, another event fires saying that the BackgroundWorker is complete, and you can then hide the "Please Wait" control and bind the data. Optionally, you can report progress in the calculation, if that is possible.

Wonko the Sane
That would work very well if it was a long running calculation that I was calling (or similar), but since it is buried in a property on a list of objects that is being bound to a listview I don't have the ability to separate that out that way.
ChrisHDog
It sounds like the calculation should be done on a separate thread, rather than just at databinding time. Otherwise, you are going to lock the UI while the calculation performs. One way I've had success doing that in the past is to do the calculation in an extension method, called on a separate thread. It is a fairly complex process, for which the BackgroundWorker is a simplification (in the cases where it fits the design).
Wonko the Sane
+1  A: 

I have a similar situation ChrisHDog. I am using MVVM and this is what I do:

First, in my ViewModel, I establish a Property called IsBusy:

public bool IsBusy
{
   get { return _isBusy; }
   set
   {
      _isBusy = value;
      NotifyPropertyChanged("IsBusy");
   }
}

Then, when it comes to loading the actual data, I fire off an asynchronous event. But right before I do so, I set the IsBusy property to true. In my XAML, I am binding the cursor to this:

<UserControl x:Class="UserControls.Views.AgentListView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:Converters="clr-namespace:UserControls.Utility.Converters" 
             xmlns:Controls="clr-namespace:UserControls.Controls" 
             xmlns:DependencyProperties="clr-namespace:UserControls.DependencyProperties" 
             Cursor="{Binding IsBusy, Converter={Converters:CursorExtensionConverter}}" >

The CursorExtensionConverter is just a simple IValueConverter to convert a boolean to the proper Cursor value that can be understood by the XAML:

namespace UserControls.Utility.Converters
{
  public class CursorExtensionConverter : MarkupExtension, IValueConverter
  {
    private static CursorExtensionConverter instance = new CursorExtensionConverter();

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      if (value != null && ((bool) value))
        return Cursors.Wait;
      else
        return Cursors.Arrow;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      throw new NotImplementedException();
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      return instance;
    }

  }
}

When the data gets done being fetched asynchronously, I marshal it back to the UI Thread and then set the IsBusy Property to false. The cursor then returns to normal.

As for the asynchronous calls and marshaling back to the UI, I leave that for you to look up as it's a different part of this answer. I use an Action delegate and call it async with BeginInvoke and then I marshal back to the UI thread using a technique I found here: http://www.wintellect.com/CS/blogs/jlikness/archive/2009/12/16/dispatching-in-silverlight.aspx.

I hope that helps!

Chris Holmes
Good idea (and I wasn't aware of the cursor settings), but mine isn't a long running process that I can call, it is a property on a list of objects (so it is being evaluated when the data binding is occurring), so i was hoping for a "start data binding" or "end data binding" event to use (and i could set your isBusy item on those).
ChrisHDog
I tried to put the assignment of the data source (and notify property) into a separate thread, but what happens is: cursor is busy, calls thread to assign/notify, thread ends, cursor is not busy, a few seconds pass and then the data binding is complete. so either need another option or a way to determine when the data binding is complete and update isBusy at that point. thoughts?
ChrisHDog
So it appears that what is actually taking time is the binding itself, of the data to the grid, not the fetching. And for that I am not sure what to do; I haven't seen any events on the DataGrid yet that deal with that. I wonder if simply setting the IsBusy property on the ViewModel right before and right after you update your DataSource (ObservableCollection I assume) would work?
Chris Holmes
I did attempt to do that (set the isBusy property before and after updates to the dataSource). It is a shame there isn't an event (there appears to be one since the UI updates after the binding is done, but it just isn't available) that I could have hooked into.
ChrisHDog
A: 

I ended up creating a new presentation object and setting Cursor = Wait then looping through the objects I previously bound to the ListView and creating presentation objects out of them (which caused the calculated property to be executed) then once the list of presentation objects were created bound those to the ListView and stet Cursor = Arrow.

Disappointed there doesn't appear to be a DataBinding Completed type event (or whatever event is fired once the data binding is complete that updates the UI is available), but this solution appears to work.

ChrisHDog