XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書
顯示具有 Visual Studio 標籤的文章。 顯示所有文章
顯示具有 Visual Studio 標籤的文章。 顯示所有文章

2019/09/04

升級到 VS2019 16.2.3 後,要執行 Xamarin.Android 專案,卻得到 System.IO.FileNotFoundException: 'Invalid Image' 錯誤訊息

升級到 VS2019 16.2.3 後,要執行 Xamarin.Android 專案,卻得到 System.IO.FileNotFoundException: 'Invalid Image' 錯誤訊息

最近這兩個月因為忙於 Xamarin.Forms 的授課,所以,一直沒有將上課用的 Surface Pro 4 電腦上的 Visual Studio 2019 軟體升級到最新版本,本來想等到 09/03 最後一天課程上完之後,再來進行 Visual Studio 2019 的升級工作,沒想到 09/03 當天授課的時候,卻發現到 Visual Studio 2019 無法打開,電腦上顯示需要我把 Visual Studio 進行升級,這是我最擔心的一件事情,因為,每次軟體有升級的時候,都會怕對於上課過程中有些影響(這也就是為什麼,當會進行類似 Xamarin.Forms 6 天這樣長時間的授課時候,通常不會把 Visual Studio 進行升級 ,一來擔心新功能的推出對於授課會有,二來擔心有些狀況僅會出現在新升級後的電腦,而學員的電腦上的 Visual Studio 卻沒有升級,造成不一致的問題;所以,在上課之前,我通常是僅會將作業系統進行更新到最新版本這樣的動作而已。
好的,那麼當天究竟發生了甚麼事情?當我將 Visual Studio 2019 升級到最新版本,也就是 16.2.3 版本,當我使用了 Prism Template Pack 建立起一個 Xamarin.Forms 的專案後,首先在 Visual Studio 2019 的錯誤視窗中,看到了底下的訊息。
警告        Skipping BlankApp2.Droid.Resource.Attribute.actionBarSize. Please check that your Nuget Package versions are compatible.    BlankApp2.Android
雖然該訊息僅是個警告訊息,不過,還是覺得怪怪的,緊接著開始進行 Xamarin.Android 的專案建置與執行的動作,不幸的是,此時在螢幕上顯示了 System.IO.FileNotFoundException Message=Invalid Image 這個錯誤訊息,如同底下螢幕截圖顯示的狀況。
這下慘了,課程僅進行到 30 分鐘,若無法建立一個空的 Xamarin.Forms for Prism 專案,那麼要如何繼續底下 6個小時的課程呢?(心裡有著 今日可以提早完課,下次再來的念頭)。
我還是先花了一些時間做了檢測,首先,看到在 Visual Studio 2019 輸出視窗中的最後一行,看到了 Assembly Loader probing location: '/storage/emulated/0/Android/data/com.companyname.appname/files/.__override__/Xamarin.Interactive.dll'. **System.IO.FileNotFoundException:** 'Invalid Image' 這樣的訊息,我想,這個訊息應該是上面對話窗所遇到的主要問題。
接下來的內容,是我回到家裡後,使用另外一台電腦上的 Visual Studio 2019,也把它升級到 16.2.3 版本,結果看到相同的問題,但是,當我不使用 Prism Template Pack 來產生一個 Prism Blank App (Xamarin.Forms) 的應用程式,而是使用 Visual Studio 2019 內建的 行動應用程式 (Xamarin.Forms) 專案範本,建立起一個空的 Xamarin.Forms 的專案,卻沒有發現到這個專案在建置與執行過程中,會出現上述的問題,一切都可以正常運作。
我針對 使用 Prism Template Pack 所產生的 Xamarin.Form 專案,打開 Xamarin Android 專案內的 MainActivity.cs 檔案,接著,在 OnCreate 方法內加入訂閱 AppDomain.CurrentDomain.UnhandledException 這個事件,看看是否能夠捕捉到任何可疑的例外異常錯誤資訊,並且在 int foo = 10; 這個敘述上設定一個中斷點,若執行的過程中可以停在剛剛設定的那行中斷點上,那麼,也許可以從 UnhandledExceptionEventArgs 這個參數中,看到一些端倪。
protected override void OnCreate(Bundle bundle)
{
    AppDomain.CurrentDomain.UnhandledException += (s, e) =>
    {
        // 這一行是要用於設定中斷點之用
        int foo = 10;
    };
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    base.OnCreate(bundle);

    global::Xamarin.Forms.Forms.Init(this, bundle);
    LoadApplication(new App(new AndroidInitializer()));
}
當修正好了之後,再度執行這個專案,依然還是看到這個這個錯誤訊息,並且並沒有停留在剛剛設定的中斷點上,所以,這應該是這個專案尚未啟動到 MainActivity 的時候,就產生了問題。
現在回到 Xamarin.Forms 授課當天,那時,只好針對幾個設定項目進行測試,看看是否可以讓課程繼續進行下去,這個時候,只要使用 Prism Template Pack 建立的專案,可以正常建置、執行,那麼,當天的課程就可以繼續下去囉。
這裡是當天解決這個問題的其中一個做法
  • 首先打開 Xamarin.Android 專案的屬性設定視窗 (可以滑鼠右擊 Android 專案節點,選擇最後面的屬性項目,或者在 Android 專案內找到 Properties 節點,使用滑鼠雙擊這個節點)
  • 當 Android 屬性視窗出現之後,切換到 Android 選項 標籤頁次 Tabbed,且解除 使用 Fast Deployment (僅限偵錯模式) 這個選項)
注意,底下的螢幕截圖,是 使用 Fast Deployment (僅限偵錯模式) 這個選項被設定的狀態,請要解除該 Checkbox 的設定
現在,重新建置與執行這個專案,看看能否執行起來
喔喔,這樣的設定終於解除的當前危機,再度建立一個新的 Prism Black App 專案,看看是否會有同樣的效果,結果也是沒有問題。
另外一個解法則是剛剛測試出來的,不需要取消 使用 Fast Deployment (僅限偵錯模式) 這個選項設定,維持著 Fast Deployment 的設定,不過,在同一個標籤頁次下,也就是 Android 專案屬性視窗下的 Android 選項視窗中,往下捲動將會看到 正在連結 這個設定。
一開始使用 Prism Template Pack 建立起來的 Xamarin Android 專案中,其 正在連結 設定為 無 ,現在可以修正為 僅限 SDK 組譯碼 這個選項,我發現到可以維持在除錯模式下持續使用 Fast Deployment 的機制,也可以正常運作。



2019/04/24

解決要建置 Xamarin.Forms 專案,發生 ResolveLibraryProjectImports 與 LinkAssemblies 和 參考 `Xamarin.Forms.Xaml` 的問題

解決要建置 Xamarin.Forms 專案,發生 ResolveLibraryProjectImports 的問題


了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式

有時候當建立好一個 Xamarin.Forms 專案,撰寫了一些程式碼之後,準備要進行建置該專案的動作,此時,會得到這樣的錯誤訊息
嚴重性    程式碼    說明    專案    檔案    行    隱藏項目狀態
錯誤        "ResolveLibraryProjectImports" 工作發生未預期的失敗。
System.IO.FileNotFoundException: Could not load assembly 'XF2001, Version=0.0.0.0, Culture=neutral, PublicKeyToken='. Perhaps it doesn't exist in the Mono for Android profile?
檔案名稱: 'XF2001.dll'
   於 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver.Resolve(AssemblyNameReference reference, ReaderParameters parameters)
   於 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver.Resolve(String fullName)
   於 Xamarin.Android.Tasks.ResolveLibraryProjectImports.Extract(DirectoryAssemblyResolver res, ICollection`1 jars, ICollection`1 resolvedResourceDirectories, ICollection`1 resolvedAssetDirectories, ICollection`1 resolvedEnvironments)
   於 Xamarin.Android.Tasks.ResolveLibraryProjectImports.Execute()
   於 Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   於 Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()    XF2001.Android
或者
嚴重性    程式碼    說明    專案    檔案    行    隱藏項目狀態
錯誤        Failed to generate resource table for split '' "Failed to generate resource table for split ''".    DynamicFlexLayout.Android
嚴重性    程式碼    說明    專案    檔案    行    隱藏項目狀態
錯誤        "LinkAssemblies" 工作發生未預期的失敗。
System.IO.FileNotFoundException: Could not load assembly 'DynamicFlexLayout.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken='. Perhaps it doesn't exist in the Mono for Android profile?
檔案名稱: 'DynamicFlexLayout.Android.dll'
   於 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver.Resolve(AssemblyNameReference reference, ReaderParameters parameters)
   於 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver.GetAssembly(String fileName)
   於 Xamarin.Android.Tasks.LinkAssemblies.Execute(DirectoryAssemblyResolver res)
   於 Xamarin.Android.Tasks.LinkAssemblies.Execute()
   於 Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   於 Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()    DynamicFlexLayout.Android
或者
嚴重性    程式碼    說明    專案    檔案    行    隱藏項目狀態
錯誤        Can not resolve reference: `Xamarin.Forms.Xaml`, referenced by `XF3031`. Please add a NuGet package or assembly reference for `Xamarin.Forms.Xaml`, or remove the reference to `XF3031`.    XF3031.Android            
錯誤        No resource found that matches the given name: attr 'colorPrimaryDark'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        No resource found that matches the given name: attr 'windowNoTitle'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        No resource found that matches the given name: attr 'colorPrimary'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        No resource found that matches the given name: attr 'windowActionBar'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        Error retrieving parent for item: No resource found that matches the given name 'Theme.AppCompat.Light.DarkActionBar'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        No resource found that matches the given name: attr 'colorAccent'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    3    
錯誤        No resource found that matches the given name: attr 'colorAccent'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    4    
錯誤        No resource found that matches the given name: attr 'windowActionModeOverlay'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    4    
錯誤        Error retrieving parent for item: No resource found that matches the given name 'Theme.AppCompat.Light.Dialog'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    4
從此錯誤訊息中可以看到 "ResolveLibraryProjectImports" 工作發生未預期的失敗,而且有個 System.IO.FileNotFoundException: Could not load assembly 這樣的例外異常;當看到這樣的錯誤訊息,最快的解決方案就是先清除所有的專案,並且關閉您的 Visual Studio 2019 專案,並且重新開啟該 Xamarin.Forms 方案,重新再建置一次,一切問題都迎刃而解了。



2019/04/23

使用 FlexLayout 配合 BindableLayout 建立一個動態產生與自動配置的效果

使用 FlexLayout 配合 BindableLayout 建立一個動態產生與自動配置的效果

在這裡,將要使用 Visual Studio 2019 建立一個 Xamarin.Forms 的專案,並且使用 FlexLayout 彈性版面配置 這個檢視來做出一個可以動態成長的布局設計;在這裡將會一開始顯示三個區塊,這三個區塊將會顯示在同一個 Row,接下來將會顯示一個文字,該文字將會獨佔一個 Row,在下一個 Row 將會顯示出一個比較大的區塊而且也是獨占一個 Row,最後,將會產生出 31 個區塊,用來模擬可以做到動態的產生出不同數量的區塊檢視,但是一樣可以顯示在螢幕上的效果,而最後的執行效果將會如下圖所示,左下圖為一開始執行的畫面,而右下圖為向上捲動後的結果,此時底下是還有很多的區塊尚未顯示出來的。
 
該文件的專案原始碼可以透過 GitHub 來取得

建立一個 使使用 FlexLayout 專案

  • 開啟 Visual Studio 2019 程式
  • 當 Visual Studio 2019 開始 視窗 出現之後,請點選左下角的 [建立新專案] 選項
  • 當 [建立新專案] 對話窗出現之後,請在中間最上方的搜尋文字輸入盒中輸入 [prism] 關鍵字,搜尋所有與 Prism 有關的專案樣板
  • 請選擇 [Prism Blank App (Xamarin.Forms)] 這個專案樣板
  • 當出現 [設定新的專案] 對話窗,請在 [專案名稱] 輸入 [DynamicFlexLayout]
  • 最後點選該對話窗右下方的 [建立] 按鈕
  • 現在將會看到 [PRISM PROJECT WIZARD] 對話窗,請勾選 ANDROID, iOS, UWP 三個行動裝置平台,接著在底下 [Container] 下拉選單,選擇 Unity 項目
  • 最後,點選 [CREATE PROJECT] 按鈕,以便產生 Xamarin.Forms 專案

安裝需要用到的 PropertyChanged.Fody NuGet 套件

  • 當這個 Xamarin.Forms 專案建立成功之後,請在該方案中,找到 Xamarin.Forms 使用的專案(這是一個 .NET Standard 類別庫,簡稱為 SCL ),請在該專案中,使用滑鼠右擊 [相依性] 節點,選擇 [管理 NuGet 套件] 選項
  • 在 [NuGet: XXX] 視窗中,點選 [瀏覽] 標籤頁次,並且在下方的搜尋文字輸入盒中,輸入 [propertychanged.fody] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [PropertyChanged.Fody] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內
  • 請查看 Xamarin.Forms 專案內,並沒有 [FodyWeavers.xml] 這個檔案,因此,使用滑鼠右擊 Xamarin.Forms 專案節點,選擇 [建置] 選項
  • 當建置完成之後,在這個 Xamarin.Forms 專案內將會出現 [FodyWeavers.xml] 檔案

安裝需要用到的 Xamarin.Essentials NuGet 套件

  • 在 [NuGet: XXX] 視窗中,搜尋文字輸入盒中,輸入 [Xamarin.Essentials] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [Xamarin.Essentials] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內

修正因為安裝 Xamarin.Essentials 帶來的錯誤

現在,可以從 Visual Studio 2019 的錯誤視窗中,看到底下的錯誤訊息
NU1107    偵測到 Xamarin.Android.Support.Compat 有版本衝突。請將 Xamarin.Android.Support.Compat 28.0.0.1 直接安裝/參考到專案 DynamicFlexLayout.Android 來解決此問題。 
 DynamicFlexLayout.Android -> DynamicFlexLayout -> Xamarin.Essentials 1.1.0 -> Xamarin.Android.Support.Compat (>= 28.0.0.1) 
 DynamicFlexLayout.Android -> Xamarin.Android.Support.Design 27.0.2.1 -> Xamarin.Android.Support.Compat (= 27.0.2.1).    DynamicFlexLayout.Android    D:\Vulcan\GitHub\Xamarin2019\DynamicFlexLayout\DynamicFlexLayout\DynamicFlexLayout.Android\DynamicFlexLayout.Android.csproj    1
想要解決此一問題:
  • 使用滑鼠右擊方案節點(方案總管最上方的那個節點),選擇 [管理方案的 NuGet 套件]
  • 點選 [更新] 標籤頁次
  • 勾選該標籤頁次內的所有項目
  • 點選右上方的更新按鈕,就可以升級這些套件到最新版本了

建立資料模型

  • 滑鼠右擊 Xamarin.Forms 專案,選擇 [加入] > [新增資料夾]
  • 將新增資料夾的名稱設定為 [Models]
  • 滑鼠右擊剛剛建立的 [Models] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗下方的 [名稱] 欄位中,輸入 [ItemBlock]
  • 點選右下方的 [新增] 按鈕
  • 將底下程式碼填入到這個新建立的類別檔案內
C Sharp / C#
public class ItemBlock
{
    public double Width { get; set; }
    public double Height { get; set; }
    public Color Color { get; set; }
    public bool ShowLabel { get; set; }
    public bool ShowBoxView { get; set; } = true;
}

建立支援方法類別

  • 滑鼠右擊 Xamarin.Forms 專案,選擇 [加入] > [新增資料夾]
  • 將新增資料夾的名稱設定為 [Helpers]
  • 滑鼠右擊剛剛建立的 [Helpers] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗下方的 [名稱] 欄位中,輸入 [ScreenInfo]
  • 點選右下方的 [新增] 按鈕
  • 將底下程式碼填入到這個新建立的類別檔案內
這個類別將會儲存來自於 Xamarin.Essentials:裝置顯示資訊 Device Display Information 所讀取到的相關螢幕資訊,例如:當時螢幕的實際可用寬度與高度的畫素是多少?這個裝置的密度是多少?接著將會經過計算,得知該螢幕的寬度與高度的設計尺寸(單位為 dip) 是多少?而這個裝置當時寬度的設計尺寸將會與 360 dip (這是在設計這個 App 畫面時所使用的設計尺寸)進行計算,得到一個縮放比例,將其值儲存到 DesignScalar 靜態屬性中。最後,將會提供一個靜態方法,該方法會使用剛剛計算出來的設計尺寸所放比例 DesignScalar,計算出現在提供的設計尺寸,是否放大多少還是要縮小多少。
C Sharp / C#
public class ScreenInfo
{
    public static double ScreenPixelWidth { get; set; }
    public static double ScreenPixelHeight { get; set; }
    public static double DesignScreenWidth { get; set; } 
    public static double DesignScreenHeight { get; set; }
    public static double DesignTimeScreenWidth { get; set; } = 360;
    public static double DesignScalar { get; set; }
    public static double Density { get; set; }
    public static double GetNewDesingSize(double value)
    {
        return DesignScalar * value;
    }
}

建立一個啟動頁面

  • 在 SCL 專案中,滑鼠右擊 [Views] 這個節點,選擇 [加入] > [新增項目]
  • 在 [新增項目] 對話窗的左方,分別點選 [已安裝] > [Visual C# 項目] > [Prism] > [Xamarin.Forms]
  • 在 [新增項目] 對話窗的中間,點選 [Prism ContentPage (Xamarin.Forms)] 這個項目
  • 在 [新增項目] 對話窗的最下方的名稱欄位,輸入 [SplashPage]
  • 最後點選 [新增] 按鈕,完成建立這個新頁面 View 與 檢視模型 ViewModel
  • 在 [ViewModels] 資料夾內,找到 [SplashPageViewModel.cs] 節點,並開啟這個節點
  • 使用底下程式碼覆寫這個這個檔案內容
在這個檢視模型類別中,將會透過 OnNavigatedTo 事件,從 Xamarin.Essentials:裝置顯示資訊 Device Display Information 所讀取到的相關螢幕資訊,儲存到 ScreenInfo 靜態屬性內,最後將會 navigationService.NavigateAsync("/NavigationPage/MainPage"); 敘述,導航到 MainPage 這個頁面
C Sharp / C#
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;

namespace DynamicFlexLayout.ViewModels
{
    using System.ComponentModel;
    using DynamicFlexLayout.Helpers;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using Xamarin.Essentials;

    public class SplashPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private readonly INavigationService navigationService;

        public SplashPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;

        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
            ScreenInfo.Density = mainDisplayInfo.Density;
            ScreenInfo.ScreenPixelWidth = mainDisplayInfo.Width;
            ScreenInfo.ScreenPixelHeight = mainDisplayInfo.Height;
            ScreenInfo.DesignScreenWidth = ScreenInfo.ScreenPixelWidth/ScreenInfo.Density;
            ScreenInfo.DesignScreenHeight = ScreenInfo.ScreenPixelHeight / ScreenInfo.Density;
            ScreenInfo.DesignScalar = ScreenInfo.DesignScreenWidth / ScreenInfo.DesignTimeScreenWidth;
            navigationService.NavigateAsync("/NavigationPage/MainPage");
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }

    }
}

修正該 Xamarin.Forms 的第一個顯示頁面

  • 打開 [App.xaml.cs] 檔案
  • 將第 26 行修正為 await NavigationService.NavigateAsync("SplashPage");

建立使用 FlexLayout 的頁面與商業邏輯

  • 在 [Views] 資料夾內,打開 [MainPage.xaml] 檔案
  • 修正使用底下的 XAML 語言宣告
在這裡的 FlexLayout 版面配置,將會透過 [BindableLayout.ItemsSource] 這個附加屬性,指定其子項目的來源,而這些子項目將會由該頁面的 檢視模型 ViewModel 來自動產生,因此 [BindableLayout.ItemsSource] 的屬性值將會是透過資料綁定的方式來取得。
而該 FlexLayout 的每個子項目將會使用 [BindableLayout.ItemTemplate] 這個附加屬性來指定,透過 [DataTemplate] 這個項目 Element,將會指定該子項目要顯示的 XAML 項目。
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"
             x:Class="DynamicFlexLayout.Views.MainPage"
             Title="FlexLayout 的動態調適">

    <Grid>
        <ScrollView>
            <Grid
                HorizontalOptions="Center">
                <FlexLayout
                    Wrap="Wrap"
                    Direction="Row"
                    AlignItems="Start"
                    AlignContent="Start"
                    JustifyContent="Start"
                    BindableLayout.ItemsSource="{Binding myItemList}"  
                    >
                    <BindableLayout.ItemTemplate>
                        <DataTemplate>
                            <Grid
                                WidthRequest="{Binding Width}" HeightRequest="{Binding Height}">
                                <Label
                                    Text="Logo"
                                    FontSize="30"
                                    HorizontalTextAlignment="Center"
                                    WidthRequest="{Binding Width}" HeightRequest="{Binding Height}"
                                    IsVisible="{Binding ShowLabel}"/>
                                <BoxView
                                    Color="{Binding Color}"
                                    WidthRequest="{Binding Width}" HeightRequest="{Binding Height}"
                                    IsVisible="{Binding ShowBoxView}"/>
                            </Grid>
                        </DataTemplate>
                    </BindableLayout.ItemTemplate>
                </FlexLayout>
            </Grid>
        </ScrollView>
    </Grid>

</ContentPage>
  • 在 [ViewModels] 資料夾內,打開 [MainPageViewModel.xaml] 檔案
  • 修正使用底下的 C# 程式碼
在 [OnNavigatedTo] 事件中,將會建立 [ItemBlock] 類別物件,並且加入到型別為 ObservableCollection<ItemBlock> 的 [myItemList] 屬性內。
C Sharp / C#
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DynamicFlexLayout.ViewModels
{
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using DynamicFlexLayout.Helpers;
    using DynamicFlexLayout.Models;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using Xamarin.Forms;

    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ObservableCollection<ItemBlock> myItemList { get; set; } = new ObservableCollection<ItemBlock>();
        private readonly INavigationService navigationService;

        public MainPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;

        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            Random rnd = new Random();
            ItemBlock fooItem;
            for (int i = 0; i < 3; i++)
            {
                fooItem = new ItemBlock()
                {
                    Width = ScreenInfo.GetNewDesingSize(100),
                    Height = ScreenInfo.GetNewDesingSize(100),
                    Color = Color.FromRgba(rnd.Next(256), rnd.Next(256), rnd.Next(256), rnd.Next(256)),
                    ShowLabel = false,
                };
                myItemList.Add(fooItem);
            }
            fooItem = new ItemBlock()
            {
                Width = ScreenInfo.GetNewDesingSize(300),
                Height = ScreenInfo.GetNewDesingSize(50),
                Color = Color.FromRgba(rnd.Next(256), rnd.Next(256), rnd.Next(256), rnd.Next(256)),
                ShowLabel = true,
                ShowBoxView = false
            };
            myItemList.Add(fooItem);
            fooItem = new ItemBlock()
            {
                Width = ScreenInfo.GetNewDesingSize(301),
                Height = ScreenInfo.GetNewDesingSize(200),
                Color = Color.FromRgba(rnd.Next(256), rnd.Next(256), rnd.Next(256), rnd.Next(256)),
                ShowLabel = false,
            };
            myItemList.Add(fooItem);
            for (int i = 0; i < 31; i++)
            {
                fooItem = new ItemBlock()
                {
                    Width = ScreenInfo.GetNewDesingSize(100),
                    Height = ScreenInfo.GetNewDesingSize(100),
                    Color = Color.FromRgba(rnd.Next(256), rnd.Next(256), rnd.Next(256), rnd.Next(256)),
                    ShowLabel = false,
                };
                myItemList.Add(fooItem);
            }
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }

    }
}

執行結果

在這裡,將會開啟兩種模式的 Android 模擬器,在下圖左方的是 xxhdpi (螢幕密度為 2.65)、1080x1920 的裝置,而右下方的是 xhdpi (螢幕密度為 1.84)、720x1280 的裝置。透過上面的演算法之後,不論當時螢幕的設計尺寸是否大於 360 dip(device independent pixels 與裝置無關的畫素) 或者小於 360 dip 的裝置,都可以完美的顯示比例將這個內容顯示在裝置螢幕上;不過,對於不需要做到完美排版的需求,其實是不需要針對顯示縮放比例做這樣的獨特設計的。
 
在下方,將會使用 iOS 模擬器作為執行測試環境,在下圖左方(iPhone 6)的螢幕密度為2、750x1334 的裝置,而右下方(iPhone5s)的是螢幕密度為2、640x1136 的裝置。透過上面的演算法之後,不論左下方螢幕的設計尺寸(為375)是大於 360 dip(device independent pixels 與裝置無關的畫素) 而右下方螢幕的設計尺寸(為320)是小於 360 dip 的裝置,都可以完美的顯示比例將這個內容顯示在裝置螢幕上。
 
在下方(iPhone 8 Plus)的螢幕密度為3、1242x2208 的裝置,此時螢幕的設計尺寸為 414x736,是大於 360 dip(device independent pixels 與裝置無關的畫素) 可以完美的顯示比例將這個內容顯示在裝置螢幕上。




2019/04/22

Xamarin.Forms 之 XAML 設計預覽的設計時期資料

Xamarin.Forms 之 XAML 設計預覽的設計時期資料

在前一篇文章 Xamarin.Forms 之 XAML 設計預覽 有說明,如何在 Visual Studio 2019 下,使用頁面預覽的功能,以便在進行 XAML 語言設計過程中,可以即時看到這些設計後的執行結果。

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
現在遇到一個問題,那就是通常在進行 XAML 設計的時候,都會使用 資料綁定 Data Binding 手法,與該頁面的 ViewModel 進行綁定再一起,可是,當使用這樣的設計方式的時候,又想要使用設計時期預覽功能,就會發現到有許多內容,還是需要在執行時期的時候,才能夠看到該頁面的執行結果。
為了解決這樣的問題,需要使用所謂的 [設計時期資料] 這樣的機制,需要使用 Xamarin.Forms 提供的新功能,首先,需要在 ContentPage 內加入底下的命名空間宣告,有了這些宣告,才能夠在 XAML 中使用設計時期資料的功能。
xaml
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
現在,可以將 MainPage.xaml 的內容修正成為如下所示,其中對於 Label Text="{Binding Message}" />這個 XAML 標記,由於使用資料綁定來宣告,因此,這樣的用法只能夠在該專案執行的時候,才能夠看到該文字標籤顯示的內容;若想要在設計時期指定該 Label 這個文字標籤的 Text 屬性值,則需要使用 d: 這個命名空間來指定設計時期的屬性名稱,指定該設計時期的屬性值內容,如此,才能夠在設計階段在預覽畫面上看到這些內容,在此,要使用 <Label Text="{Binding Message}" d:Text="這是設計時期指定資料"/> 這樣的宣告語法。從下圖將會看到該 XAML 所指定的設計時期的資料值顯示情況。
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="BlankApp5.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Label Text="{Binding Message}" />
        <Label Text="{Binding Message}" d:Text="這是設計時期指定資料"/>
        <BoxView Color="Blue" d:Color="Red"/>
    </StackLayout>

</ContentPage>
可是,這個頁面在執行時期,將會顯成為這樣的情況,從這兩個畫面,可以看到當初指定在設計時期的 XAML 屬性值,是沒有影響到執行時期的 XAML 屬性值。
然而,對於像是 ListView 這樣的檢視,需要指定一個集合紀錄物件給 ItemsSource 這個使用,當然,最為方便的方式還是使用 ViewModel 來建立這樣的物件,並且透過資料綁定的方式指定給 ListView。
首先,在 ContentPage 內加入一個 XAML 命名空間,該命名空間將會指向該頁面的 ViewModel,在此,將會使用 xmlns:ViewModel="clr-namespace:BlankApp5.ViewModels" 這樣的宣告語法。接著,可以在該頁面內,使用 d: 命名空間,指定設計時期的該頁面之 BindingContext 屬性值,指向為 MainPageViewModel。
xaml
    <d:ContentPage.BindingContext>
        <ViewModel:MainPageViewModel/>
    </d:ContentPage.BindingContext>
不過,這樣又產生一個問題,那就是原有的 ViewModel 類別中,沒有預設建構式存在,而會得到 : XLS0507 類型 'MainPageViewModel' 無法用做為物件元素,因為它並非公用,或是未定義公用的無參數建構函式或類型轉換子 這樣的錯誤訊息。
因此,需要在 MainPageViewModel 類別中加入一個預設建構式,並且把 XAML 頁面修改成為底下的宣告,如此,就可以在設計時期,使用 Visual Studio 2019 的頁面預覽功能,看到 ListView 的設計時期呈現的樣貌了。
底下是執行螢幕截圖
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"
             xmlns:ViewModel="clr-namespace:BlankApp5.ViewModels"
             x:Class="BlankApp5.Views.MainPage"
             Title="{Binding Title}">

    <d:ContentPage.BindingContext>
        <ViewModel:MainPageViewModel/>
    </d:ContentPage.BindingContext>

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Label Text="{Binding Message}" />
        <Label Text="{Binding Message}" d:Text="這是設計時期指定資料"/>
        <BoxView Color="Blue" d:Color="Red"/>
        <ListView
            ItemsSource="{Binding myItemList}"
            >
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Label Text="{Binding Name}"
                               FontSize="20"/>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>

</ContentPage>
C Sharp / C#
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BlankApp5.ViewModels
{
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class MyModel : INotifyPropertyChanged
    {
        public string Name { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;
    }
    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Message { get; set; }
        public ObservableCollection<MyModel> myItemList { get; set; } = new ObservableCollection<MyModel>();
        private readonly INavigationService navigationService;

        public MainPageViewModel()
        {
            Message = "Come From Default Constuctor  !!";
            myItemList.Add(new MyModel() { Name = "張三" });
            myItemList.Add(new MyModel() { Name = "李四" });
            myItemList.Add(new MyModel() { Name = "王五" });
        }

        public MainPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;
            Message = "Come From Injection Constructor";
            myItemList.Add(new MyModel() { Name = "張三A" });
            myItemList.Add(new MyModel() { Name = "李四B" });
            myItemList.Add(new MyModel() { Name = "王五C" });
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }

    }
}