问题
I have a LoginPage.xaml that inherits PageBase.xaml. LoginViewModel is the ViewModel for LoginPage and it inherits BaseViewModel. BaseViewModel has Func delegate defined - OnModalNavigationRequest which is subscribed by PageBase in it's OnAppearing method and implementation is provided in HandleModalNavigationRequest method. When Login Button is clicked on LoginPage.xaml, OnSubmit method of LoginViewModel, bound through command is invoked which, on successful authentication, calls NavigateToModal method of BaseViewModel. NavigateToModel method of BaseViewModel invokes HandleModalNavigationRequest method of PageBase on Func delegate OnModalNavigationRequest after checking if it is not null.
My problem is OnModalNavigationRequest is always coming as null, indicating that there is no subscriber to it. That means OnAppearing method of PageBase is not getting called which implies that PageBase is not getting instantiated. How to resolve this issue?
I'm posting the code of sequence of events that is taking place:
Disclaimer: Code help has been taken from 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:
Got it working by
Adding x:TypeArguments in LoginPage.xaml as shown below:
<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>
Changing the LoginPage.xaml.cs as shown below:
public partial class LoginPage : PageBase<LoginViewModel> { public LoginPage() : base() { BindingContext = new LoginViewModel(); InitializeComponent(); } }
来源:https://stackoverflow.com/questions/59192571/how-to-navigate-as-per-mvvm-architecture-in-xamarin-forms-without-using-any-fram