views:

237

answers:

2

I'm applying the MVVM pattern. I've got a button which, when clicked, invokes a delegate Command in my ViewModel. At the very start of that delegate method, I set a property value (WaitOn) that should notify the user back in the UI to wait by displaying an animated control.

However, the binding to display that animated control doesn't get refreshed until the delegate has completed execution, by which point the waiting is done. Why does this happen and what should I do to workaround it?

Sample XAML:

<Button Command="{Binding DoStuffCommand}" />
<ctl:MyAnimatedControl Name="ctlWait" Caption="Please Wait..." 
Visibility="{Binding WaitNotification}" />

Snippets from ViewModel:

public bool WaitPart1On
{
  get { return _waitPart1On; }
  set
  {
    _waitPart1On = value;
    if (_waitPart1On == true)
    {
      WaitNotification = "Visible";
    }
    else
    {
      WaitNotification = "Hidden";
    }
    RaisePropertyChanged("WaitPart1On");
  }
}

public string WaitNotification
{
  get { return _waitNotification; }
  set
  {
    _waitNotification = value;
    RaisePropertyChanged("WaitNotification");
  }
}


public void DoStuff()
{
  WaitPart1On = true;
  //Do lots of stuff (really, this is PART 1)
  //Notify the UI in the calling application that we're finished PART 1
  if (OnFinishedPart1 != null)
  {
    OnFinishedPart1(this, new ThingEventArgs(NewThing, args));
  }
  WaitPart1On = false;
}

And now code-behind from the XAML to catch the raised event:

public void Part1FinishedEventHandler(NewThing newThing, ThingEventArgs e)
    {
      //at this point I expected the WaitPart1On to be set to false
      //I planned to put a new wait message up (WaitPart2)
      FinishPart2();
    } 
+1  A: 

Chances are the binding is being updated, but because you're doing "lots of stuff" on the UI thread, the application isn't getting a chance to update the screen. You should consider moving the processing to a background thread or using Dispatcher.BeginInvoke() so that the UI is free to update and display your wait message.

In WPF, the Dispatcher class has a static CurrentDispatcher property which you can use from within the ViewModel to schedule tasks. Your DoStuff method would look something like this:

public void DoStuff()
{
  WaitOn = true;
  Dispatcher.CurrentDispatcher.BeginInvoke(() =>
    {
      //Do lots of stuff
      WaitOn = false;
    });
}

In Silverlight, you can access the current dispatcher using the Deployment class:

public void DoStuff()
{
  WaitOn = true;
  Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
      //Do lots of stuff
      WaitOn = false;
    });
}

On a side note, you might want to make use of the BooleanToVisibilityConverter so that you only need the OnWait property for the binding. Also, your OnWait setter is currently firing notifications even if the property is set back to the same value, you should implement it like this:

public bool WaitOn
{
  get { return _waitOn; }
  set
  {
    if(value != _waitOn)
    {
      _waitOn = value;
      RaisePropertyChanged("WaitOn");
    }
  }
}
Rory
Thanks for the info on the Converter. I'll definitely make use of that. But for the Dispatcher bit, it's not so straightforward from inside the ViewModel. My ViewModel's method is invoked from a direct Command binding in the UI, so I can't control things until the method is already underway. I've looked at this interesting post on using the Dispatcher with MVVM-->http://www.wintellect.com/CS/blogs/jlikness/archive/2009/12/16/dispatching-in-silverlight.aspxI'm not sure it's going to update the UI before it completes either, because it would wrap the whole contents of my method also.
ml_black
@ml_black: I've updated my answer with a couple of examples for using the the Dispatcher from the View model. Your `DoStuff` method is still called by the UI thread, and `WaitOn` is set before the work begins, but everything else is invoked through the dispatcher. I know this method works because I've used it before, but if it doesn't work for you, you could do it with manually created threads or the ThreadPool. You just need to be careful about setting the `WaitOn` property (and any others that the UI might be bound to) from background threads. Hope it helps.
Rory
Thanks. I was exploring your suggestion, but hadn't seen the code written out so plainly. I've got this functional, however, it's not working. I don't know if this might be because the "lots of stuff" that I'm handing to the Dispatcher will also raise an event that the View is handling in code-behind. I'll update the code sample a bit.
ml_black
A: 

I did get Rory's solution working, with a bit of tweaking. I ended up doing this:

public void DoStuff() 
{ 
  WaitPart1On = true; 
  Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(() =>

    { 
      //Do lots of stuff 
      WaitPart1On = false; 
    }); 
}

Using "(Action)" to create new Action to wrap the code meant I didn't have to declare a delegate anywhere, and using the priority of "Background" allowed the UI its chance to update.

ml_black
Glad you got it working. Just wondering if the cast to `Action` is strictly necessary, I'm sure the lambda syntax would have taken care of that for you...
Rory