Xamarin Xamarin Forms Prism

Xamarin Forms Prism 改寫ViewModelLocator

張阿鬼 2019/12/27 19:35:46
2017

 

在初探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

 

 

張阿鬼