首页 > 解决方案 > MvvmCross 和使用 MvxContentView 导航

问题描述

我是 MvvmCross 的新手(我正在使用 Xamarin.Forms),在 MvxContentPages 之间导航很容易。

但我想在嵌入页面的 ContentView 之间导航,但找不到任何使用 MvxContentView 引用的文档。

考虑以下页面

<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>

当用户单击其中一个按钮时,我想在 ContentContainer 中显示一个 MvxContentView (或者如果有另一种方法可以做到这一点,那很好,我只是不想制作本质上相同的 4 个页面但在内容视图占位符中有不同的内容)。

标签: xamarinxamarin.formsmvvmcross

解决方案


我在 ContentPage 和 ContentView 上编写了一个包装器,以启用 ViewModel 首次导航。这很简单,你把一个 MvxNestableContentView 放在一个 MvxNestableContentPage 里面并给它一个名字。然后在您的 ViewModel(从 MvxNestableViewModel 派生)中,您只需调用 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; }
}

推荐阅读