1
votes

I'm new to MvvmCross (I'm using Xamarin.Forms) and navigating between MvxContentPages is easy.

But I'd like to navigate between ContentView embedded in a page but can't find any documentation that references using MvxContentView.

Consider the following Page

<ContentPage.Content>

    <Grid>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackLayout Grid.Column="0">
            <Button Text="Content 1" Command="{Binding GotoContent1}"/>
            <Button Text="Content 2" Command="{Binding GotoContent2}"/>
            <Button Text="Content 3" Command="{Binding GotoContent3}"/>
            <Button Text="Content 4" Command="{Binding GotoContent4}"/>
        </StackLayout>

        <ContentView x:Name="ContentContainer" Grid.Column="1"
                     HorizontalOptions="Fill"
                     VerticalOptions="Fill"/>

    </Grid>

</ContentPage.Content>

When the user clicks one of the buttons, I'd like to display a MvxContentView in the ContentContainer (or if there is an alternative way to do it, that's fine, I just don't want to make 4 pages that are essentially the same but have different content in the content view placeholder).

2
How're using the navigation ? MvvmCross framework is mainly to support navigation between viewmodels instead views. - FabriBertani
If we define "Page" as a full screen view, and a "View" as a visual component - either of which can have a ViewModel - then MvvmCross allows easy navigation between Pages using the associated ViewModel. But if I want to use ViewModel navigation and have the associated View appear within a Page, that doesn't seem possible. - jo phul
Yes, i mean "View" as "Page". I've done that, you just simply create a ContentView and a Page, associate this page with a ViewModel and insert that ContentView in the Page. By this way you ensure that you're using the ViewModel navigation instead Page navigation. I can write a sample if you want. - FabriBertani
Right, I've got a ContentPage associated with its ViewModel, and when I navigate via _navigationService.Navigate<MyPageViewModel>(), I transition to that page. But inside of that page is a ContentView which I would like to change out as the user clicks different buttons. Each ContentView has its own ViewModel, of course. But using _navigationService.Navigate<ContentOneViewModel>(), I can't seem to find a way to assign the associated ContentView with ContentOneViewModel to the ContentView in the ContentPage. Does that make sense? - jo phul
and I realize I could use a MasterDetailPage to get the same behavior, or a TabPage, but I'm going to be nesting even deeper. This project is specific to a large tablet where there is enough screen realestate. - jo phul

2 Answers

0
votes

The Content View is simply a Layout underneath, so just use 1 content view and just use the same existing ViewModel for all 4. And just change the value of the stuff inside the content view from the ViewModel depending on which button was clicked.

0
votes

I wrote a wrapper over a ContentPage and ContentView to enable ViewModel first navigation. It's quite simple, you put a MvxNestableContentView inside of a MvxNestableContentPage and give it a name. Then in your ViewModel (which is derived from MvxNestableViewModel) you simple call SetContent(typeof(viewmodel));

public interface INestedViewModelEventArgs
{
    string ContainerName { get; set; }
    IMvxNestableViewModel ViewModel { get; set; }
    bool SuppressAppearing { get; set; }
}

public interface IMvxNestableViewModel : IMvxViewModel
{
    event EventHandler SetNestedViewModel;

    IMvxNestableViewModel SetContent(Type tViewModel, string containerName, bool suppressAppearing = false);
}

public interface IMvxNestableContentView
{
    void OnAppearing();
    void OnDisappearing();
}

public interface IMvxNestableContentPage
{
}

public class MvxNestableContentView : MvxContentView, IMvxNestableContentView
{
    public MvxNestableContentView()
    {
    }

    protected override void OnViewModelSet()
    {
        if (ViewModel is IMvxNestableViewModel vm)
        {
            vm.SetNestedViewModel += VmOnSetNestedViewModel;
        }
        ViewModel?.ViewCreated();
    }

    public virtual void OnAppearing()
    {
        ViewModel?.ViewAppearing();
        ViewModel?.ViewAppeared();
        foreach (var v in _nestedContentViews)
        {
            v.OnAppearing();
        }
    }

    public virtual void OnDisappearing()
    {
        if (ViewModel is IMvxNestableViewModel vm)
        {
            vm.SetNestedViewModel -= VmOnSetNestedViewModel;
        }
        foreach (var v in _nestedContentViews)
        {
            v.OnDisappearing();
        }
        ViewModel?.ViewDisappearing();
        ViewModel?.ViewDisappeared();
        ViewModel?.ViewDestroy();
    }

    private readonly List<MvxNestableContentView> _nestedContentViews = new List<MvxNestableContentView>();

    private void VmOnSetNestedViewModel(object sender, EventArgs e)
    {
        if (!(e is NestedViewModelEventArgs args)) return;
        if (string.IsNullOrWhiteSpace(args.ContainerName) || args.ViewModel == null) return;
        var contentView = this.FindByName<MvxNestableContentView>(args.ContainerName);
        if (contentView == null)
        {
            throw new Exception("MvxNestableContentView : MvxNestableContentView named " + args.ContainerName + " not found");
        }
        var viewLookup = Mvx.IoCProvider.Resolve<IMvxViewsContainer>();
        var viewType = viewLookup.GetViewType(args.ViewModel.GetType());
        var viewObject = Mvx.IoCProvider.IoCConstruct(viewType);
        if (!(viewObject is MvxNestableContentView view))
        {
            throw new Exception("MvxNestableContentView : view is not MvxNestableContentView");
        }
        view.ViewModel = args.ViewModel;
        var existingContent = contentView.Content as MvxNestableContentView;
        if (!args.SuppressAppearing) view.OnAppearing();
        _nestedContentViews.Add(view);
        contentView.Content = view;
        if (existingContent == null) return;
        existingContent.OnDisappearing();
        _nestedContentViews.Remove(existingContent);
    }
}

public class MvxNestableContentView<TViewModel> : MvxNestableContentView, IMvxElement<TViewModel>
    where TViewModel : class, IMvxViewModel
{
    public new TViewModel ViewModel
    {
        get => (TViewModel)base.ViewModel;
        set => base.ViewModel = value;
    }
}

public class MvxNestableContentPage : MvxContentPage, IMvxNestableContentPage
{
    public MvxNestableContentPage()
    {
    }

    protected override void OnViewModelSet()
    {
        if (ViewModel is IMvxNestableViewModel vm)
        {
            vm.SetNestedViewModel += VmOnSetNestedViewModel;
        }
        base.OnViewModelSet();
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        foreach (var v in _nestedContentViews)
        {
            v.OnAppearing();
        }
    }

    protected override void OnDisappearing()
    {
        if (ViewModel is IMvxNestableViewModel vm)
        {
            vm.SetNestedViewModel -= VmOnSetNestedViewModel;
        }
        foreach (var v in _nestedContentViews)
        {
            v.OnDisappearing();
        }
        base.OnDisappearing();
    }

    private readonly List<MvxNestableContentView> _nestedContentViews = new List<MvxNestableContentView>();

    private void VmOnSetNestedViewModel(object sender, EventArgs e)
    {
        if (!(e is NestedViewModelEventArgs args)) return;
        if (string.IsNullOrWhiteSpace(args.ContainerName) || args.ViewModel == null) return;
        var contentView = this.FindByName<MvxNestableContentView>(args.ContainerName);
        if (contentView == null)
        {
            throw new Exception("MvxNestableContentPage : MvxNestableContentView named " + args.ContainerName + " not found");
        }
        var viewLookup = Mvx.IoCProvider.Resolve<IMvxViewsContainer>();
        var viewType = viewLookup.GetViewType(args.ViewModel.GetType());
        var viewObject = Mvx.IoCProvider.IoCConstruct(viewType);
        if (!(viewObject is MvxNestableContentView view))
        {
            throw new Exception("MvxNestableContentPage : view is not MvxNestableContentView");
        }
        view.ViewModel = args.ViewModel;
        var existingContent = contentView.Content as MvxNestableContentView;
        if (!args.SuppressAppearing) view.OnAppearing();
        _nestedContentViews.Add(view);
        contentView.Content = view;
        if (existingContent == null) return;
        existingContent.OnDisappearing();
        _nestedContentViews.Remove(existingContent);
    }
}

public class MvxNestableContentPage<TViewModel> : MvxNestableContentPage, IMvxPage<TViewModel>
    where TViewModel : class, IMvxViewModel
{
    public new TViewModel ViewModel
    {
        get => (TViewModel)base.ViewModel;
        set => base.ViewModel = value;
    }
}

public abstract class MvxNestableViewModel : MvxViewModel, IMvxNestableViewModel
{
    public event EventHandler SetNestedViewModel;

    public virtual IMvxNestableViewModel SetContent(Type viewModelType, string containerName, bool suppressAppearing = false)
    {
        if (!(Mvx.IoCProvider.IoCConstruct(viewModelType) is IMvxNestableViewModel viewModel))
        {
            return null;
        }
        viewModel.Start();
        viewModel.Prepare();
        viewModel.Initialize();
        SetNestedViewModel?.Invoke(this, new NestedViewModelEventArgs
        {
            ContainerName = containerName,
            ViewModel = viewModel,
            SuppressAppearing = suppressAppearing
        });
        return viewModel;
    }
}

public abstract class MvxNestableViewModel<TParameter> : MvxNestableViewModel, IMvxViewModel<TParameter>, IMvxNestableViewModel
{
    public abstract void Prepare(TParameter parameter);
}

public abstract class MvxNestableViewModelResult<TResult> : MvxNestableViewModel, IMvxViewModelResult<TResult>, IMvxNestableViewModel
{
    public TaskCompletionSource<object> CloseCompletionSource { get; set; }

    public override void ViewDestroy(bool viewFinishing = true)
    {
        if (viewFinishing && CloseCompletionSource != null && !CloseCompletionSource.Task.IsCompleted && !CloseCompletionSource.Task.IsFaulted)
            CloseCompletionSource?.TrySetCanceled();
        base.ViewDestroy(viewFinishing);
    }
}

public abstract class MvxNestableViewModel<TParameter, TResult> : MvxNestableViewModelResult<TResult>, IMvxViewModel<TParameter, TResult>, IMvxNestableViewModel
{
    public abstract void Prepare(TParameter parameter);
}

public class NestedViewModelEventArgs : EventArgs, INestedViewModelEventArgs
{
    public string ContainerName { get; set; }
    public IMvxNestableViewModel ViewModel { get; set; }
    public bool SuppressAppearing { get; set; }
}