Görünüşe göre bu konu çok popüler ve burada alternatif bir yol olduğundan bahsetmemek üzücü olacak - ViewModel First Navigation
. MVVM çerçevelerinin çoğu bunu kullanıyor, ancak ne hakkında olduğunu anlamak istiyorsanız okumaya devam edin.
Tüm resmi Xamarin.Forms belgeleri, basit ama biraz da olsa MVVM saf çözümü gösteriyor. Bunun nedeni, Page
(Görünüm) 'ün ViewModel
ve bunun tersi hakkında hiçbir şey bilmemesidir . İşte bu ihlalin harika bir örneği:
// C# version
public partial class MyPage : ContentPage
{
public MyPage()
{
InitializeComponent();
// Violation
this.BindingContext = new MyViewModel();
}
}
// XAML version
<?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"
x:Class="MyApp.Views.MyPage">
<ContentPage.BindingContext>
<!-- Violation -->
<viewmodels:MyViewModel />
</ContentPage.BindingContext>
</ContentPage>
2 sayfalık bir uygulamanız varsa bu yaklaşım sizin için iyi olabilir. Ancak, büyük bir kurumsal çözüm üzerinde çalışıyorsanız, bir ViewModel First Navigation
yaklaşım izlemeniz daha iyi olur . (Görünümler) ViewModels
arasında gezinmek yerine aralarında gezinmenize izin veren biraz daha karmaşık ancak çok daha temiz bir yaklaşımdır Pages
. Kaygıları net bir şekilde ayırmanın avantajlarından biri, parametreleri kolayca bir sonrakine aktarabilmeniz ViewModel
veya navigasyondan hemen sonra bir zaman uyumsuz başlatma kodunu çalıştırabilmenizdir. Şimdi ayrıntılara.
(Tüm kod örneklerini olabildiğince basitleştirmeye çalışacağım).
1. Öncelikle tüm nesnelerimizi kaydedebileceğimiz ve isteğe bağlı olarak ömürlerini tanımlayabileceğimiz bir yere ihtiyacımız var. Bu konu için bir IOC konteyneri kullanabiliriz, kendiniz birini seçebilirsiniz. Bu örnekte Autofac kullanacağım (mevcut olan en hızlılardan biridir). App
Küresel olarak erişilebilir olması için ona bir referans tutabiliriz (iyi bir fikir değil, basitleştirme için gerekli):
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; }
// Pass here platform specific dependencies
public App(Module platformIocModule)
{
InitializeComponent();
DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
MainPage = new WelcomeView();
}
/* The rest of the code ... */
}
2. Page
Belirli bir için (Görünüm) ' ü almaktan sorumlu bir nesneye ihtiyacımız olacak ViewModel
ve bunun tersi de geçerlidir. İkinci durum, uygulamanın kök / ana sayfasının ayarlanması durumunda faydalı olabilir. Bunun için hepsinin dizinde ve (Görünümler) dizinde ViewModels
olması gerektiği konusunda basit bir kongre üzerinde anlaşmalıyız . Diğer bir deyişle ad alanında ve (Görünümler) ad alanında yaşamalıdır . Buna ek olarak, (Sayfa) ' nın bir vb. Olması gerektiğini kabul etmeliyiz . İşte bir eşleştiricinin kod örneği:ViewModels
Pages
Views
ViewModels
[MyApp].ViewModels
Pages
[MyApp].Views
WelcomeView
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. Bir kök sayfasının ViewModelLocator
ayarlanması durumunda, aşağıdakileri BindingContext
otomatik olarak ayarlayacak bir şeye ihtiyacımız olacak :
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;
}
}
// Usage example
<?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. Son olarak, NavigationService
bu ViewModel First Navigation
yaklaşımı destekleyecek bir yaklaşıma ihtiyacımız olacak :
public class NavigationService
{
TypeMapperService mapperService { get; }
public NavigationService(TypeMapperService mapperService)
{
this.mapperService = mapperService;
}
protected Page CreatePage(Type viewModelType)
{
Type pageType = mapperService.MapViewModelToView(viewModelType);
if (pageType == null)
{
throw new Exception($"Cannot locate page type for {viewModelType}");
}
return Activator.CreateInstance(pageType) as Page;
}
protected Page GetCurrentPage()
{
var mainPage = Application.Current.MainPage;
if (mainPage is MasterDetailPage)
{
return ((MasterDetailPage)mainPage).Detail;
}
// TabbedPage : MultiPage<Page>
// CarouselPage : MultiPage<ContentPage>
if (mainPage is TabbedPage || mainPage is CarouselPage)
{
return ((MultiPage<Page>)mainPage).CurrentPage;
}
return mainPage;
}
public Task PushAsync(Page page, bool animated = true)
{
var navigationPage = Application.Current.MainPage as NavigationPage;
return navigationPage.PushAsync(page, animated);
}
public Task PopAsync(bool animated = true)
{
var mainPage = Application.Current.MainPage as NavigationPage;
return mainPage.Navigation.PopAsync(animated);
}
public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
InternalPushModalAsync(typeof(TViewModel), animated, parameter);
public Task PopModalAsync(bool animated = true)
{
var mainPage = GetCurrentPage();
if (mainPage != null)
return mainPage.Navigation.PopModalAsync(animated);
throw new Exception("Current page is null.");
}
async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
{
var page = CreatePage(viewModelType);
var currentNavigationPage = GetCurrentPage();
if (currentNavigationPage != null)
{
await currentNavigationPage.Navigation.PushModalAsync(page, animated);
}
else
{
throw new Exception("Current page is null.");
}
await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
}
}
Gördüğünüz gibi , gezinmeden hemen sonra çalıştırılacak bu gibi yöntemleri tanımlayabileceğiniz BaseViewModel
her şey için bir - soyut temel sınıf vardır . Ve işte bir navigasyon örneği: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>());
}
}
Anladığınız gibi, bu yaklaşım daha karmaşık, hata ayıklaması daha zor ve kafa karıştırıcı olabilir. Ancak birçok avantajı vardır ve MVVM çerçevelerinin çoğu kutudan çıkar çıkmaz desteklediği için bunu kendiniz uygulamak zorunda değilsiniz. Burada gösterilen kod örneği github'da mevcuttur . Yaklaşım
hakkında birçok iyi makale var ViewModel First Navigation
ve bunu ve diğer birçok ilginç konuyu ayrıntılı olarak açıklayan Xamarin.Forms eBook kullanan ücretsiz bir Kurumsal Uygulama Modelleri var .