Search code examples
c#wpfmvvmmvvmcross

Wpf Multiple nested Usercontrols with MvvmCross


I'm pretty new in MvvmCross but I'm working with mvvm for awhile. I know how to compose usercontrols with nested usercontrols. Now with mvvmcross I got stucked to show two or more usercontrols in another usercontrol. I don't use any other framework than MvvmCross. My Rootview looks like this:

`<views:MvxWpfView
    x:Class="MvvmCrossTest.Wpf.Views.RootView"
    xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    mc:Ignorable="d" 
    d:DesignHeight="450" d:DesignWidth="800">

    <DockPanel>  
        <TextBlock Text="Root" DockPanel.Dock="Top"/>
        <ContentControl x:Name="MainMenuVM" Content="{Binding MainMenuVM}" DockPanel.Dock="Top" />
    </DockPanel>
</views:MvxWpfView>`

The corresponding ViewModel looks like this:

using MvvmCross.Commands;
using MvvmCross.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;

namespace MvvmCrossTest.Core.ViewModels
{
    public class RootViewModel: MvxNavigationViewModel
    {
        private readonly IMvxViewModelLoader _mvxViewModelLoader;
        public RootViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService, IMvxViewModelLoader mvxViewModelLoader) : base(logProvider, navigationService)
        {
            _mvxViewModelLoader = mvxViewModelLoader;
            ShowMainMenu();
        }

        private MainMenuViewModel _mainMenuVM;

        public MainMenuViewModel MainMenuVM
        {
            get { return _mainMenuVM; }
            set 
            {
                SetProperty(ref _mainMenuVM, value);
                RaisePropertyChanged(() => MainMenuVM);
            }
        }

         public MvxCommand ShowMainMenuCommand { get; set; }

        public void ShowMainMenu()
        {
            MainMenuVM = (MainMenuViewModel)_mvxViewModelLoader.LoadViewModel(MvxViewModelRequest.GetDefaultRequest(typeof(MainMenuViewModel)), null, null);
        }
    }
}

The simplified View I want to show in the contentcontrol looks like this:

<views:MvxWpfView
    x:Class="MvvmCrossTest.Wpf.Views.MainMenuView"
    xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    mc:Ignorable="d"
    Background="Aqua"
    d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
            
    </Grid>
</views:MvxWpfView>

And also the corresponding ViewModel.

using MvvmCross.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;

namespace MvvmCrossTest.Core.ViewModels
{
    public class MainMenuViewModel : MvxNavigationViewModel
    {
        public MainMenuViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
        {

        }
    }
}

Instead of the View/ViewModel I see a text that says "MvvmCrossTest.Core.ViewModels.MainMenuViewModel".

I don't want to use Xamarin!


Solution

  • I created a project where I show you some basics.

    simple

    You can find the samples here.

    the RootView;

    <views:MvxWpfView xmlns:local="clr-namespace:SomeProject.Views" ...>
         <Grid>
               <Label Content="Hello from RootView" />
               <local:NestedView />    
         </Grid>
    </views:MvxWpfView>
    

    the NestedView;

    <views:MvxWpfView ...>
         <Grid>
               <Label Content="Hello from NestedView" />  
         </Grid>
    </views:MvxWpfView>
    

    extended (custom presenter)

    With this custom wpf presenter you can easily navigate to views while specified themn in a container. With that, you can close and open views from the view model while using the mvvmcross navigation service.

    I copied this from another project. You can find the whole implementation in my sample project here.

    the RootView;

    <views:MvxWpfView ...>
          <Grid>
               <Label Content="Hello" />
               <ItemsControl region:MvxContainer.Id="RootViewRegion"/>    
          </Grid>
    </views:MvxWpfView>
    

    the RootView code behind;

    [MvxContentPagePresentation(WrapInNavigationPage = true, NoHistory = false)]
    public partial class RootView: MvxWpfView<RootViewModel>
    {
         public RootView()
         {
              InitializeComponent();
         }
    }
    

    the RootViewModel;

    public class RootViewModel: MvxNavigationViewModel
    {
          private readonly IMvxNavigationService _navigationService;
    
          public IMvxAsyncCommand ShowNestedViewModelCommand { get; protected set; }
    
          public RootViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
          {
                this._navigationService = navigationService;
        
                // navigate to nested view
                this.ShowNestedViewModelCommand = new MvxAsyncCommand(() => this._navigationService.Navigate<NestedViewModel>());
    
                ShowNestedViewModelCommand.Execute();
          }
    }
    

    the NestedView code behind;

    [MvxWpfPresenter("RootViewRegion", mvxViewPosition.NewOrExsist)]
    public partial class NestedView : MvxWpfView<NestedViewModel>
    {
        public NestedView()
        {
            InitializeComponent();
        }
    }
    

    good to know

    when you have problems with navigation and view model connection use this code behind. It loads the viewmodel for the specific view;

    [MvxContentPagePresentation(WrapInNavigationPage = true, NoHistory = false)]
    public partial class SomeView : MvxWpfView<ViewModels.SomeViewModel>
    {
        public SomeView()
        {
             InitializeComponent();
        
             if (!(ViewModel is ViewModels.SomeViewModel))
             {
                  if (Mvx.IoCProvider.TryResolve<ViewModels.SomeViewModel>(out var someViewModel))
                  {
                       ViewModel = someViewModel;
                       return;
                  }
        
                  var _viewModelLoader = Mvx.IoCProvider.Resolve<IMvxViewModelLoader>();
                  var request = new MvxViewModelInstanceRequest(typeof(ViewModels.SomeViewModel));
                  request.ViewModelInstance = _viewModelLoader.LoadViewModel(request, null);
                  ViewModel = request.ViewModelInstance as ViewModels.SomeViewModel;
        
                  Mvx.IoCProvider.RegisterSingleton<ViewModels.SomeViewModel>(ViewModel);
             }
        }
    }