如何在 Xamarin.Forms 中按照 MVVM 架构进行导航而不使用 PRISM 或任何导航服务等任何框架?

How to navigate as per MVVM architecture in Xamarin.Forms without using any frameworks like PRISM or any Navigation Service?

我有一个 LoginPage.xaml 继承了 PageBase.xamlLoginViewModelLoginPageViewModel,它继承了 BaseViewModel . BaseViewModel 定义了 Func 委托 - OnModalNavigationRequestPageBase[=87 订阅=] 在它的 OnAppearing 方法中,HandleModalNavigationRequest 方法中提供了实现。在 LoginPage.xaml 上单击 登录按钮 时,OnSubmit 方法LoginViewModel,调用通过命令绑定,在成功验证后,调用 BaseViewModelNavigateToModal 方法。 NavigateToModel BaseViewModel 方法调用 HandleModalNavigationRequest 方法 PageBase Func 委托 OnModalNavigationRequest 检查它是否不是 null.

我的问题是 OnModalNavigationRequest always coming as null,说明没有订阅者。这意味着 PageBaseOnAppearing 方法没有被调用,这意味着 PageBase 没有被实例化。如何解决这个问题?

我正在发布正在发生的一系列事件的代码:

免责声明:代码帮助已取自 here

App.xaml.cs

    protected override void OnStart()
    {
        // Handle when your app starts
        var properties = Current.Properties;

        if (properties.ContainsKey("Username"))
        {
            if (properties["Username"] != null)
            {
                var loggedInUser = (string)properties["Username"];
                MainPage = new MainPage();
            }
            else
            {
                MainPage = new LoginPage();
            }
        }
        else
        {
            MainPage = new LoginPage();
        }
    }

IView

public interface IView
{
}

public interface IView<TViewModel> : IView where TViewModel : BaseViewModel
{
    TViewModel ViewModel { get; set; }
}

BaseViewModel

    public class BaseViewModel : INotifyPropertyChanged
    {
       private string title;
       public string Title
       {
         get { return GetProperty<string>("title"); }
         set { SetProperty(value, "title"); }
       }

       protected bool SetProperty<T>(T value,
        [CallerMemberName] string propertyName = "",
        Action onChanged = null)
       {
         if (!properties.ContainsKey(propertyName))
         {
            properties.Add(propertyName, default(T));
         }

         var oldValue = GetProperty<T>(propertyName);
         if (EqualityComparer<T>.Default.Equals(oldValue, value))
            return false;

         properties[propertyName] = value;
         onChanged?.Invoke();
         OnPropertyChanged(propertyName);
         return true;
       }

       protected T GetProperty<T>([CallerMemberName] string propertyName = null)
       {
         if (!properties.ContainsKey(propertyName))
         {
            return default(T);
         }
         else
         {
            return (T)properties[propertyName];
         }
       }

       public Func<BaseViewModel, Task> OnModalNavigationRequest { get; set; }

       public async Task NavigateToModal<TViewModel>(TViewModel targetViewModel) where TViewModel : BaseViewModel
      {
        await OnModalNavigationRequest?.Invoke(targetViewModel);
      }
    }

ViewResolver

    internal static class ViewResolver
    {
        public static Page GetViewFor<TargetViewModel>(TargetViewModel targetViewModel) where 
TargetViewModel : BaseViewModel, new()
        {
            var targetViewName = targetViewModel.GetType().Name.Replace("ViewModel", "Page");
            var definedTypes = targetViewModel.GetType().GetTypeInfo().Assembly.DefinedTypes;
            var targetType = definedTypes.FirstOrDefault(t => t.Name == targetViewName);
            return Activator.CreateInstance(targetType.AsType()) as Page;
        }
    }

PageBase.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:d="http://xamarin.com/schemas/2014/forms/design"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         mc:Ignorable="d"
         x:Class="IPMS.Views.Shared.PageBase">
    </ContentPage>

PageBase.xaml.cs

    public partial class PageBase<TViewModel> : ContentPage, IView<TViewModel> where TViewModel : BaseViewModel, new()
   {
    public TViewModel ViewModel
    {
        get
        {
            return GetValue(BindingContextProperty) as TViewModel;
        }
        set
        {
            SetValue(BindingContextProperty, value);
        }
    }

    #region <Events>

    protected override void OnAppearing()
    {
        base.OnAppearing();

        ViewModel.OnModalNavigationRequest = HandleModalNavigationRequest;
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        ViewModel.OnModalNavigationRequest = null;
    }
    #endregion

    async Task HandleModalNavigationRequest(BaseViewModel targetViewModel)
    {
        var targetView = ViewResolver.GetViewFor(targetViewModel);
        targetView.BindingContext = targetViewModel;
        await Navigation.PushModalAsync(new NavigationPage(targetView));
    }
}

LoginPage.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <views:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:views="clr-namespace:IPMS.Views.Shared;assembly=IPMS"
         x:Class="IPMS.Views.Shared.LoginPage"
         Title="{Binding Title}">

         <ContentPage.Resources>
           <ResourceDictionary>
             // Resources goes here
           </ResourceDictionary>
         </ContentPage.Resources>

         <ContentPage.Content>
           <StackLayout>
                <Button Command="{Binding SubmitCommand}" Text="Login" TextColor="White"  
                FontAttributes="Bold" FontSize="Large" HorizontalOptions="FillAndExpand" />
            </StackLayout>
         </ContentPage.Content>
    </views:PageBase>

LoginPage.xaml.cs

    public partial class LoginPage : PageBase
    {
        LoginViewModel ViewModel => BindingContext as LoginViewModel;

        public LoginPage() : base()
        {
            BindingContext = new LoginViewModel();

            InitializeComponent();
        }
    }

LoginViewModel.cs

        public class LoginViewModel : BaseViewModel
        {
            public Command SubmitCommand { protected set; get; }

            public LoginViewModel() : base()
            {
                Title = "Login";
                SubmitCommand = new Command(async () => await OnSubmit());
            }

            public async Task OnSubmit()
            {
                await NavigateToModal<ItemsViewModel>(new ItemsViewModel());
            }
        }

成功了
  1. LoginPage.xaml中添加x:TypeArguments如下图:

    <views:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:views="clr-namespace:IPMS.Views.Shared;assembly=IPMS"
         xmlns:ViewModel="clr-namespace:IPMS.ViewModels.Shared;assembly=IPMS"
         x:Class="IPMS.Views.Shared.LoginPage"
         x:TypeArguments="ViewModel:LoginViewModel"
         BackgroundImageSource="loginbg.9.jpg"
         Title="{Binding Title}">
    </views:PageBase>
    
  2. 改变LoginPage.xaml.cs如下图:

    public partial class LoginPage : PageBase<LoginViewModel>
    {
        public LoginPage() : base()
        {
            BindingContext = new LoginViewModel();
    
            InitializeComponent();
        }
    }