如何在不使用任何框架(如 PRISM 或任何导航服务)的情况下按照 Xamarin.Forms 中的 MVVM 架构进行导航?
Posted
技术标签:
【中文标题】如何在不使用任何框架(如 PRISM 或任何导航服务)的情况下按照 Xamarin.Forms 中的 MVVM 架构进行导航?【英文标题】:How to navigate as per MVVM architecture in Xamarin.Forms without using any frameworks like PRISM or any Navigation Service? 【发布时间】:2020-03-30 05:35:20 【问题描述】:我有一个继承 PageBase.xaml 的 LoginPage.xaml。 LoginViewModel 是 LoginPage 的 ViewModel,它继承了 BaseViewModel。 BaseViewModel 定义了 Func 委托 - OnModalNavigationRequest 由 PageBase 在其 OnAppearing 中订阅HandleModalNavigationRequest 方法中提供了方法和实现。当LoginPage.xaml上的Login Button被点击时,LoginViewModel的OnSubmit方法,通过命令绑定被调用,验证成功后,调用 BaseViewModel 的 NavigateToModal 方法。 BaseViewModel 的 NavigateToModel 方法调用 Func 委托 上 PageBase 的 HandleModalNavigationRequest 方法OnModalNavigationRequest 在检查它是否不是 null 之后。
我的问题是 OnModalNavigationRequest 是 always 以 null 的形式出现,表明它没有订阅者。这意味着 PageBase 的 OnAppearing 方法没有被调用,这意味着 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());
【问题讨论】:
,根据文章,我发现作者提供的很简单,所以你尝试作者的样本吗? 是的,但示例不包括从 PageBase.xaml 继承的页面,这是我在某些链接中面临挑战的地方,因为我们无法继承 XAML因为我刚开始使用 Xamarin,所以我不知道 x:TypeArguments 的作用。 【参考方案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>
修改LoginPage.xaml.cs如下图:
public partial class LoginPage : PageBase<LoginViewModel>
public LoginPage() : base()
BindingContext = new LoginViewModel();
InitializeComponent();
【讨论】:
很高兴听到您自己解决了您的问题,所以请记得将您的回复标记为答案,谢谢。以上是关于如何在不使用任何框架(如 PRISM 或任何导航服务)的情况下按照 Xamarin.Forms 中的 MVVM 架构进行导航?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Prism 的“OnNavigatingTo”中为任何和所有类执行操作