Chris Lees
Chris Lees

Reputation: 2210

UI Doesnt update as expected in WPF MVVM application with Caliburn Micro and BusyIndicator control

I'm writing an application in WPF using Caliburn Micro. Following some tutorials on their website I wanted to implement a BusyIndicator control from the Xceed.Wpf.Toolkit. I'm using coroutines and return IEnumerable from my method to do 3 things: show the busy indicator, switch screens, hide the busy indicator. Seems simple enough, but whats happening is, the BusyIndicator never shows up. I think there's something I don't understand about the way WPF renders its controls. Here's some code.

This is my Loader class for displaying my BusyIndicator control on the ShellView.xaml

public class Loader : IResult
{


    private readonly String _message;
    private readonly bool _hide;
    private readonly IShell _shell;

    public Loader(IShell shell, String message)
    {
        _message = message;
        _shell = shell;
    }

    public Loader(IShell shell, bool hide)
    {
        _hide = hide;
        _shell = shell;
    }

    public void Execute(CoroutineExecutionContext context)
    {
        var view = _shell.View as ShellView;
        if (view == null)
            return;

        if (_hide)
        {
            view.BusyIndicator.IsBusy = false;
        }
        else
        {
            view.BusyIndicator.BusyContent = _message;
            view.BusyIndicator.IsBusy = true;
            // I WOULD ASSUME THIS WOULD IMMEDIATELY UPDATE THE BusyIndicator CONTROL TO SHOW BUT IT DOESNT
        }
        Completed(this, new ResultCompletionEventArgs());
    }


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

    public static IResult Show(String message = null)
    {
        return new Loader(IoC.Get<IShell>(), message);
    }

    public static IResult Hide()
    {
        return new Loader(IoC.Get<IShell>(), true);
    }

}

This is my ShowScreen class that navigates to the next screen by getting the IShell and calling ActivateItem. Nothing fancy here.

public class ShowScreen : IResult
{

private readonly Type _screenType;

    public ShowScreen(Type screenType)
    {
        _screenType = screenType;
    }

    public void Execute(CoroutineExecutionContext context)
    {
        var screen = IoC.GetInstance(_screenType, null);
        shell.ActivateItem(screen);
        Completed(this, new ResultCompletionEventArgs());
    }

    public event EventHandler<ResultCompletionEventArgs> Completed;

    public static ShowScreen Of<T>()
    {
        return new ShowScreen(typeof(T));
    }

}

Both of these on their own work with no problems, its when I chain them together in a coroutine like this is when it doesnt work the way I'd expect:

    public class HomeViewModel : Screen
{

    public IEnumerable<IResult> OpenFirstPage()
    {
        yield return Loader.Show("Please Wait");
        yield return ShowScreen.Of<FirstPageViewModel>();
        yield return Loader.Hide();
    }

}

I almost feel like I need to tell WPF to explicitly show my BusyIndicator somehow. Like it doesn't instantly show the BusyIndicator when I tell it to. When I take out the last Loader.Hide() command, it navigates to the next screen THEN shoes the BusyIndicator. This is driving me insane.

Upvotes: 0

Views: 532

Answers (1)

Chris Lees
Chris Lees

Reputation: 2210

After messing with this stupid thing all night I've finally found a solution. In my ShowScreen class I needed to wrap the showing of the screen in a Task.Factory.StartNew() like this

    public void Execute(CoroutineExecutionContext context)
    {
        Task.Factory.StartNew(() =>
        {
            object screen = null;
            var shell = IoC.Get<IShell>();
            if (_viewModel != null)
            {
                screen = _viewModel;
            }
            else
            {
                screen = !String.IsNullOrEmpty(_name)
                    ? IoC.Get<object>(_name)
                    : IoC.GetInstance(_screenType, null);
            }
            shell.ActivateItem(screen);
            Completed(this, new ResultCompletionEventArgs());
        });
    }

Now everything executes in the order I want it to execute. Thanks @pushpraj for the ideas.

Upvotes: 1

Related Questions