Busy Indicator - default ViewModel

Aug 24, 2010 at 6:51 PM

Hi All,

I have been trying to learn all the new stuff that is incorporated into the CM model by looking through provided sample code, etc...

I am trying to figure out how the BusyIndicator works based on the GameLibrary (GL) usage. Looking at how it's used it would appear that what I am doing should work, but its not so for a quick explaination and perhaps someone can point out to me what I have incorrect, or using wrong.

I have created a shell, that contains the busyIndicator as the top level element just as in GL, and have imported all the items under Framework and Framework/Results.

I have created a ViewModel/View that is loaded upon initialization of the shell that contains 2 other ViewModel/Views.

This all works fine, everything is attaching by convention, etc...

Now to test the busy indicator I created a simple button in one of the internal views, that calls the following method: 

        public IEnumerable<IResult> ShowBusy()
        {
            yield return Show.Busy();
            Thread.Sleep(5000);
            yield return Show.NotBusy();
        }

I think it should simply show the busy indicator for 5 seconds and then hide it again, just as it does in SearchViewModel.ExecuteSearch(). However I find that nothing appears, and when stepping through the code, the DefaultBusyService is actually looking for the busyIndicator on the internal view, which of course it will not find and exits instead of finding the busyIndicator in the top level shell container, which based on how its used in GL I thought would occur.

This is all new stuff to me, so I have found most of my errors, are...well...my errors. Someone able to point me in the right direction?

 

 

 

Aug 24, 2010 at 7:19 PM

Hi,

Check this thread http://caliburn.codeplex.com/Thread/View.aspx?ThreadId=212045 Copied the interesting part below. Just change Caliburn.PresentationFramework.Invocation to Caliburn.Micro. and it should be fine /Christoffer

For those that may be interested, here's my TimerResult class, for a non blocking IResult pause, great for seeing what a busy view looks like, and other testing purposes.

Usage Example:

yield return new TimerResult(TimeSpan.FromSeconds(3));

 

 

using System;
using System.Windows.Threading;
using Caliburn.PresentationFramework.RoutedMessaging;

/// <summary>
/// The timer result.
/// </summary>
public class TimerResult : IResult
{
    /// <summary>
    /// The _timer.
    /// </summary>
    private readonly DispatcherTimer _timer = new DispatcherTimer();

    /// <summary>
    /// Initializes a new instance of the <see cref="TimerResult"/> class.
    /// </summary>
    /// <param name="timeSpan">
    /// The time span.
    /// </param>
    public TimerResult(TimeSpan timeSpan)
    {
        TimeSpan = timeSpan;
        _timer.Tick += Timer_Tick;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="TimerResult"/> class.
    /// </summary>
    /// <param name="timeSpan">
    /// The time span.
    /// </param>
    /// <param name="userState">
    /// The user state.
    /// </param>
    public TimerResult(TimeSpan timeSpan, object userState)
    {
        TimeSpan = timeSpan;
        UserState = userState;
        _timer.Tick += Timer_Tick;
    }

    /// <summary>
    /// The completed event.
    /// </summary>
    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

    /// <summary>
    /// Gets TimeSpan.
    /// </summary>
    public TimeSpan TimeSpan { get; private set; }

    /// <summary>
    /// Gets UserState.
    /// </summary>
    public object UserState { get; private set; }

    /// <summary>
    /// The execute.
    /// </summary>
    /// <param name="context">
    /// The context.
    /// </param>
    public void Execute(ResultExecutionContext context)
    {
        _timer.Interval = TimeSpan;
        _timer.Start();
    }

    /// <summary>
    /// The timer_ tick.
    /// </summary>
    /// <param name="sender">
    /// The sender.
    /// </param>
    /// <param name="e">
    /// Timer empty event args.
    /// </param>
    private void Timer_Tick(object sender, EventArgs e)
    {
        _timer.Stop();            
        Caliburn.PresentationFramework.Invocation.Execute.OnUIThread(
            () => Completed(this, new ResultCompletionEventArgs()));
    }        
}


Aug 24, 2010 at 8:17 PM

Hi Syggen,

So your saying that its the way I am doing the delay that is causing the issue then, stopping the UI thread from processing the busy indicator perhaps?

Aug 24, 2010 at 8:46 PM

So after thinking about it for a minute, I did go ahead and implement the timer, however it really isn't related the to original problem.

The probelm is that Show.Busy is trying to find myinternalviewmodel.busyIndicator which doesn't exist, instead of using the shell.busyIndicator that does.

I get the following logged [INFO] message from DefaultBusyService when it trys to find the element named 'busyIndicator':

WARN: Could not find view for FSEActiveMapCM.ViewModels.FlightInfoViewModel.

 


Aug 24, 2010 at 9:58 PM

lol...whatever is going on has to be a complete newb problem I'm guessing.

A bit more digging and I am finding that while I get the INFO binding for the ViewModel/View in question, DC being set, and event handler connected GetView returns null in the DefaultBusyIndicator, whereas in GL it returns a view.

So I have something incorrect somewhere.

Aug 24, 2010 at 10:22 PM
Edited Aug 24, 2010 at 10:23 PM

Found the issue.

The error is I can't just pull code over and reuse it :p 

GL uses the Conductor and Screen stuff that I don't understand yet, and the busy indicator code requires the class calling it to use the IViewAware interface to find the attached view.
That is the cause of my problem as I did not pull that code across. 

While I don't think I am stupid, I certainly am naive about how alot of this works, and most samples and documentation is for those at a much higher level skill with IoC, Mef, etc...that abstracts this to a point that if you don't 'know' the underlying technology it becomes magic and very frustrating to debug when something doesn't work.

I really see where this could make life easier on one hand, however I would love to see some training material for those of us from linear programming to retrain our thoughts on how use a framework like this.

What are others using to pull this all together and to use it in projects you are working on I wonder?
Is there anything centralized or is it just trial and error, read alot of blogs, papers, and articles and try to gleam out what in those are used in Caliburn/Micro?

Signed,
Pattern newb trying to learn

Coordinator
Aug 24, 2010 at 11:10 PM
Edited Aug 24, 2010 at 11:10 PM

I apologize for the confusion. There are a lot of different ways to handle busy indicators. The busy service from Caliburn is an overly generalized solution, thus making it more complex and "magical" than it necessarily has to be. One simple solution is to just have an IsBusy property on your shell. Grab that from IoC in the busy service and set it to true/false. Databinding can handle the rest.

I'm working on trying to centralize more knowledge here on the project site. But, it takes a lot of time to create the samples and put together a coherent article. If you haven't already, have a read through the documentation. I'm trying to add a new article every two weeks or so. I'm going to be writing for a while...

Aug 25, 2010 at 12:27 PM

Good that you found the issue :).

Aug 25, 2010 at 3:56 PM

Silverlight version of WaitCommand...

  public abstract class AsyncCommand : IResult
  {
    public abstract void Execute(ActionExecutionContext context);

    protected void RaiseCompleted(Exception error, bool cancelled)
    {
      var e = Completed;
      if (e != null) e(this, new ResultCompletionEventArgs { WasCancelled = cancelled, Error = error });
    }

    protected void RaiseCompleted()
    {
      var e = Completed;
      if (e != null) e(this, null);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed;
  }

  public sealed class WaitCommand : AsyncCommand
  {
    int _delay;

    public WaitCommand(int delay)
    {
      _delay = delay;
    }

    public override void Execute(ActionExecutionContext context)
    {
      ThreadPool.QueueUserWorkItem(ExecuteAsync);
    }

    void ExecuteAsync(object state)
    {
      Thread.Sleep(_delay);
      RaiseCompleted();
    }
  }
Aug 25, 2010 at 6:18 PM

Thank Lex for the seperate threaded version, I'll be sure to check it out.

 

Rob,

No need for appologies sir. I know that you have to be busy and I appreciate the time you have put into this for the community, and responding to posts like mine.

With a simple change from PropertyChangedBase, to Screen in the views, and changing the shell to a Conductor<IScreen> base and nothing more it works fine.
Lol, hours trying to figure out the problem and 30seconds to fix, isn't that always the case ;)

 

Feb 13, 2011 at 9:59 AM

Wouldn't it be better to use DispatcherTimer instead of sleeping on a seperate thread?

 

    class DelayedResult : IResult
    {
        readonly TimeSpan delay;
        readonly Dispatcher dispatcher;
        readonly DispatcherPriority dispatcherPriority;

        public DelayedResult(TimeSpan delay, DispatcherPriority dispatcherPriority = DispatcherPriority.Normal)
        {
            this.delay = delay;
            this.dispatcherPriority = dispatcherPriority;
            dispatcher = Dispatcher.CurrentDispatcher;
        }

        #region IResult Members

        public void Execute(ActionExecutionContext context)
        {
            new DispatcherTimer(delay,
                dispatcherPriority,
                (sender, args) =>
                {
                    ((DispatcherTimer) sender).Stop();
                    Completed(this, new ResultCompletionEventArgs());
                },
                dispatcher).Start();
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

        #endregion
    }