Xamarin Forms Prism 改寫ViewModelLocator
在初探Prism的文章中有提到過,
當View上的AutoWireViewModel=true時,
會自動依照內建的規則去找出對應的ViewModel。
<ContentPage
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
...
但實際在設計專案的架構階層時,
通常會把ViewModel搬出來到另一個專案上,
且有可能我的ViewModel命名上並不想用XXXPage+ViewModel這樣的模式,
因為彈性一點的設計上同一個View有可能給多個ViewModel使用,
這樣的命名就可能導致從ViewModel找對應的View會有點錯亂掉。
在專案中加入一個新的專案,並將ViewModel擺到該專案中,
並將ViewModel修改其Namespace與其專案相同名稱,
最後將Xamarin Forms專案參考到ViewModel專案。
在App.cs的RegisterTypes方法中,將註冊頁面的部份改成僅只註冊MainPage
containerRegistry.RegisterForNavigation<MainPage>();
執行後會發現原本ViewModel給的Title值不見了,
因為自動對應的規則沒辦法找到我們自行規劃放置ViewModel的位置,
如果要使用自己的規則,就必須複寫原本的規則。
ViewModelLocationProvider提供了一個靜態方法SetDefaultViewTypeToViewModelTypeResolver
讓我們可以複寫它的原本的規則。
要複寫原本的規則,在App.cs中首先要複寫ConfigureViewModelLocator這個方法。
並在該方法中使用ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver複寫規則。
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewType =>
{
});
}
SetDefaultViewTypeToViewModelTypeResolver需要傳一個Func<Type,Type>的委派方法進去,
傳入的參數表示View的型別,並要回傳對應的ViewModel型別。
由於我們已將ViewModel固定放在另一個專案中,
所以這裡我們可以改寫成如下程式碼片段:
var viewName = viewType.Name;//View的名稱
var viewModelAssemblyName = "Xamarin.Lab.PrismViewModelLocator.ViewModel";//ViewModel的組件名稱
var viewModelType = $"{viewModelAssemblyName}.{viewName}ViewModel, {viewModelAssemblyName}";//組合建立Type的字串
var typ = Type.GetType(viewModelType);
return typ;
這裡會提到Xamarin編譯的一個特性。如果組件內的任何東西都沒有使用到,
在編譯的時候會沒法利用反射的方式取回任何Type。
因此在ViewModelBase中建立了一個方法。
public static void InitViewModel() { }
並且在App.cs的OnInitialized方法中呼叫該方法
最後在註冊Page的部分,
也一樣可以利用反射的方式,
在相同組件下找到namespace=Xamarin.Lab.PrismViewModelLocator.Views的所有類別做頁面註冊
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
var pageTypes=AppDomain.CurrentDomain.GetAssemblies()//所有組件
.FirstOrDefault(x => x.FullName == this.GetType().Assembly.FullName)//與目前App.cs相同的組件
.GetTypes()//所有型別
.Where(x =>x.IsClass&&x.Namespace == "Xamarin.Lab.PrismViewModelLocator.Views");
foreach(var type in pageTypes)
containerRegistry.RegisterForNavigation(type, type.Name);
}
最後執行,成功看到ViewModel中設定的Title。
範例連結
https://github.com/stevenchang0529/Xamarin.Lab.PrismViewModelLocator