Search code examples
c#windows-runtimewindows-store-appscaliburn.micro

How to pass parameter to navigated view model with WinRT Caliburn.Micro?


I am developing a Windows Store apps game using WinRT Caliburn.Micro, and I am relying on the navigation framework.

I have view models for the game setup (define players) and the actual game. When navigating from the setup to the game, I want to pass the collection of players to the game view model. How can I do this?

Schematically, my view models currently look like this:

public class SetupGameViewModel : NavigationViewModelBase
{
    public SetupGameViewModel(INavigationService ns) : base(ns) { }

    public IObservableCollection<Player> Players { get; set; }

    public void StartGame()
    {
        // This is as far as I've got...
        base.NavigationService.NavigateToViewModel<GameViewModel>();

        // How can I pass the Players collection from here to the GameViewModel?
    }
}

public class GameViewModel : NavigationViewModelBase
{
    public GameViewModel(INavigationService ns) : base(ns) { }

    public ScoreBoardViewModel ScoreBoard { get; private set; }

    public void InitializeScoreBoard(IEnumerable<Player> players)
    {
        ScoreBoard = new ScoreBoardViewModel(players);
    }
}

Ideally, I would like to call InitializeScoreBoard from within the GameViewModel constructor, but as far as I have been able to tell it is not possible to pass the SetupGameViewModel.Players collection to the GameViewModel constructor.

The INavigationService.NavigateToViewModel<T> (extension) method optionally takes an [object] parameter argument, but this parameter does not seem to reach the view model constructor navigated to. And I cannot figure out how to explicitly call the GameViewModel.InitializeScoreBoard method from the SetupGameViewModel.StartGame method either, since the GameViewModel has not been initialized at this stage.


Solution

  • In the end, I solved this by implementing a temporary event handler. It turned out that I could use the NavigateToViewModel<T>(object) overload to pass the player collection.

    From the Caliburn Micro discussion forum and MSDN documentation I get the impression that this approach is only guaranteed to work for "primitive" types, although in my scenario I have so far not detected any problems with it.

    My SetupGameViewModel.StartGame method is now implemented as follows:

    public void StartGame()
    {
        base.NavigationService.Navigated += NavigationServiceOnNavigated;
        base.NavigationService.NavigateToViewModel<GameViewModel>(Players);
        base.NavigationService.Navigated -= NavigationServiceOnNavigated;
    }
    

    And the very temporarily attached NavigationServiceOnNavigated event handler is implemented as follows:

    private static void NavigationServiceOnNavigated(object sender, NavigationEventArgs args)
    {
        FrameworkElement view;
        GameViewModel gameViewModel;
        if ((view = args.Content as FrameworkElement) == null || 
            (gameViewModel = view.DataContext as GameViewModel) == null) return;
    
        gameViewModel.InitializeScoreBoard(args.Parameter as IEnumerable<Player>);
    }
    

    Not really the clean solution I had striven for, but at least it seems to work.