首先,我们需要一个地方来注册所有的对象,并可选地定义它们的生命周期。 对于这个问题,我们可以使用一个IOC容器,您可以自己选择一个。 在此示例中,我将使用Autofac(它是可用的最快的之一)。 我们可以在 App
中保留一个引用,以便全局可用(这不是一个好主意,但为了简化而需要):
public class DependencyResolver
{
static IContainer container;
public DependencyResolver(params Module[] modules)
{
var builder = new ContainerBuilder();
if (modules != null)
foreach (var module in modules)
builder.RegisterModule(module);
container = builder.Build();
}
public T Resolve<T>() => container.Resolve<T>();
public object Resolve(Type type) => container.Resolve(type);
}
public partial class App : Application
{
public DependencyResolver DependencyResolver { get; }
public App(Module platformIocModule)
{
InitializeComponent();
DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
MainPage = new WelcomeView();
}
}
2.我们需要一个对象负责检索特定ViewModel
的Page
(View),反之亦然。第二种情况可能在设置应用程序的根/主页时有用。为此,我们应该约定一个简单的规则,即所有ViewModels
都应该在ViewModels
目录中,而Pages
(Views)应该在Views
目录中。换句话说,ViewModels
应该位于[MyApp].ViewModels
命名空间中,而Pages
(Views)应该位于[MyApp].Views
命名空间中。除此之外,我们还应该约定WelcomeView
(Page)应该拥有一个WelcomeViewModel
等。以下是一个映射器的代码示例:
public class TypeMapperService
{
public Type MapViewModelToView(Type viewModelType)
{
var viewName = viewModelType.FullName.Replace("Model", string.Empty);
var viewAssemblyName = GetTypeAssemblyName(viewModelType);
var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName);
return Type.GetType(viewTypeName);
}
public Type MapViewToViewModel(Type viewType)
{
var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewModelAssemblyName = GetTypeAssemblyName(viewType);
var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName);
return Type.GetType(viewTypeModelName);
}
string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName;
string GenerateTypeName(string format, string typeName, string assemblyName) =>
string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName);
}
3. 对于设置根页面的情况,我们需要一种类似于ViewModelLocator
的东西,它会自动设置BindingContext
:
public static class ViewModelLocator
{
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable) =>
(bool)bindable.GetValue(AutoWireViewModelProperty);
public static void SetAutoWireViewModel(BindableObject bindable, bool value) =>
bindable.SetValue(AutoWireViewModelProperty, value);
static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>();
static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
var viewType = view.GetType();
var viewModelType = mapper.MapViewToViewModel(viewType);
var viewModel = (Application.Current as App).DependencyResolver.Resolve(viewModelType);
view.BindingContext = viewModel;
}
}
<?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:viewmodels="clr-namespace:MyApp.ViewModel"
viewmodels:ViewModelLocator.AutoWireViewModel="true"
x:Class="MyApp.Views.MyPage">
</ContentPage>
4. 最后,我们需要一个NavigationService
来支持ViewModel First Navigation
方法:
public class NavigationService
public NavigationService(TypeMapperService mapperService)
protected Page CreatePage(Type viewModelType)
");
}
return Activator.CreateInstance(pageType) as Page;
}
protected Page GetCurrentPage()
if (mainPage is TabbedPage || mainPage is CarouselPage)
return mainPage;
}
public Task PushAsync(Page page, bool animated = true)
public Task PopAsync(bool animated = true)
public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
InternalPushModalAsync(typeof(TViewModel), animated, parameter);
public Task PopModalAsync(bool animated = true)
async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
else
await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
}
}
你可能会看到一个BaseViewModel
- 所有ViewModels
的抽象基类,你可以在其中定义像InitializeAsync
这样的方法,在导航后立即执行。以下是导航的示例:
public class WelcomeViewModel : BaseViewModel
{
public ICommand NewGameCmd { get; }
public ICommand TopScoreCmd { get; }
public ICommand AboutCmd { get; }
public WelcomeViewModel(INavigationService navigation) : base(navigation)
{
NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>());
TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>());
AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>());
}
}
你应该明白,这种方法更加复杂,更难调试,可能会令人困惑。然而,它也有许多优点,而且实际上你不必自己实现,因为大多数MVVM框架都支持它。这里演示的代码示例可在github上获取。
有很多关于ViewModel First Navigation
方法的好文章,还有一本免费的Xamarin.Forms企业应用程序模式电子书,其中详细介绍了这个方法和许多其他有趣的主题。