WasCancelled in Coroutines

Topics: Actions & Coroutines
Jun 16, 2011 at 10:30 PM

Hi,

I've just had a look at the Caliburn.Micro.Coroutines project and have one question regarding this.

Let's say an error occurs in the Execute method in the LoadCatalog class. I will set WasCancelled to true and the CM will not execute any further methods in the GoForward method. How can I hide the Loader if the LoadCatalog class wouln't know, that there's a Loader active because it's an independent module?

Thanks in advance.
Stefan

Coordinator
Jun 17, 2011 at 2:30 AM
Edited Jun 17, 2011 at 2:31 AM

I really need to change my sample so that the loader is not done with an IResult. A better solution would be to do this:

 

using(Show.Loader()){
        yield return new LoadCatalog("Caliburn.Micro.Coroutines.External.xap");
        yield return new ShowScreen("ExternalScreen");
}

In this case Show.Loader would not be an IResult, but rather, something that implements IDisposable. Under the covers it would call an IBusyService, or something like that, in its constructor. In dispose it would call hide. Then, regardless of whether the coroutine completes successfully, the dispose method would run. It's a pretty elegant way to handle this situation.

Jun 20, 2011 at 10:28 AM

That does seem like an elegant way to handle wrapping a sequence of steps, and this is a useful specific pattern.  Is the reason for an IBusyService approach because of the ugliness of passing the context into the Loader class?  That seems to work and allows the current Loader logic, but it is a lot less appealing to pass the context through.  I couldn't see an obvious better way for Loader to pick up the view.

public IEnumerable<IResult> GoForward(ActionExecutionContext executionContext)
{
    using (Loader.Show(executionContext, "Downloading..."))
    {
        yield return new LoadCatalog("Caliburn.Micro.Coroutines.External.xap");
        yield return new ShowScreen("ExternalScreen");
    }
}

namespace Caliburn.Micro.Coroutines
{
    using System;
    using System.Windows;
    using System.Windows.Controls;

    public class Loader : IDisposable
    {
        private BusyIndicator busyIndicator;

        public Loader(ActionExecutionContext context, string message)
        {
            var view = context.View as FrameworkElement;
            while (view != null)
            {
                busyIndicator = view as BusyIndicator;
                if (busyIndicator != null)
                {
                    busyIndicator.BusyContent = message;
                    busyIndicator.IsBusy = true;
                    break;
                }
                view = view.Parent as FrameworkElement;
            }
        }

        public static Loader Show(ActionExecutionContext context, string message = null)
        {
            return new Loader(context, message);
        }

        public void Dispose()
        {
            if (busyIndicator != null)
            {
                busyIndicator.IsBusy = false;
                busyIndicator = null;
            }
        }
    }


Coordinator
Jun 20, 2011 at 12:28 PM

It all depends on how you are handling the busy indicators. If you only have one global indicator, then you don't need the context to find it. But, if you have an indicator per screen, there are several ways you can do it. You can do it as you have shown, by passing the context in, which is based on my original sample of using tree lookup. But, you could also pass the VM in, if it inherits Screen. Screen implements IViewAware and will cache the view. So you can get the view and lookup the busy indicator that way. Even easier though, you could just put a bool prop on the VM for toggling the busy indicator, you could pass the vm to the IResult then. The VM would implement an interface such as IHaveBusyState that the IResult could use to toggle the property. I really need to update that sample :)