XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書

2016/09/18

Xamarin.Forms XAML 的開發除錯技巧

當在進行 Xamarin.Forms 專案開發的時候,因為現階段 Visual Studio 的 Xamarin.Fomrs 對於 XAML 的編輯器支援程度不如 UWP 或者 WPF 的好用,因此,在開發除錯執行階段,往往會看到這樣的錯誤訊息:System.NullReferenceException: Object reference not set to an instance of an object.。而到底是哪裡發生了問題,也很難找出問題所在,現階段比較好的方法就是使用刪去法。
不過,當您切換起始專案到 UWP 專案之後,就會發現到真正 XAML 上的錯誤問題在哪裡,以及真正的原因是甚麼?

XAML 中的語法錯誤

在底下的 XAML 宣告一個 ListView 控制項,其中資料來源是由 MyItemList 資料綁定而來,而使用者選取的紀錄,將會綁定到 MyItemListSelected 屬性上。
    <ListView
      ItemsSource="{Binding MyItemList}"
      ItemSelected="{Binding MyItemListSelected}"
      />
乍看這樣的宣告似乎沒有甚麼問題,一旦當 Android 平台下執行的時候,就會發現到這樣的錯誤畫面
UnhandledExceptionXAML
為什麼會發生這樣的問題呢? 這是因為 ItemSelected 這是一個 ListView 的事件
不過,當切換到 UWP 開發環境下執行同樣的程式碼,會得到底下的訊息與畫面
'Xamarin.Forms.Xaml.XamlParseException' 類型的例外狀況發生於 Xamarin.Forms.Xaml.dll,但使用者程式碼未加以處理

其他資訊: Position 13:7. No Property of name ItemSelected found
XAMLParseException
其中,訊息: Position 13:7. No Property of name ItemSelected found 明確的說明了問題所在地點與內容,讓我們來對照原始 XAML 定義檔內容
XAML發生錯誤原始內容
您可以看到在13行的第7個位置,指的就是 ItemSelected,而根據錯誤訊息內容,那就使用錯誤的 XAML 屬性名稱,應該使要使用 SelectedItem 這個屬性。

2016/09/17

Visual Studio 自訂程式碼片段 Code Snippet

自訂程式碼片段 Code Snippet 在 Visual Studio 內,是相當好用一項功能,您可以將常用的片段程式碼做成一個類似巨集的功能,當您要寫這段程式碼的時候,便可以快速地取出這些定義好的程式碼片段,變更相關變數之後,立即就完成了程式碼寫作。
在這裡,將會記錄如何將經常在 MVVM 架構下,要在 ViewModel 內定義一個屬性,且可以呼叫 PropertyChanged 的作法,這段需要製作成為程式碼片段的程式碼為:
        #region DataField01
        private string m_DataField01 = string.Empty;
        /// <summary>
        /// 清單資料夾編號
        /// </summary>
        public string DataField01
        {
            get { return this.m_DataField01; }
            set { this.SetProperty(ref this.m_DataField01, value); }
        }
        #endregion
  1. 首先,需要在 Visual Studio 內安裝 Snippet Designer 這個擴充套件。請點選 工具 > 擴充功能與更新,搜尋 Snippet Designer 這個套件,安裝起來。
    Snippet Designer
  2. 在 Visual Studio 程式碼編輯器視窗中,框選一段您將成會用到的程式碼,並且在框選出來的程式碼上,使用滑鼠右擊,在彈出功能表上選擇 Export as Snippet
    ExportAsSnippet
  3. 在 Snippet Designer 編輯視窗中,滑鼠右擊、選取您要取代的程式碼內容,接者選擇 Make Replacement項目,接著在底下 Replacements 清單內,設定這個可替換變數的定義資訊。
    CodeSnippetEditor
  4. 請在屬性視窗內,設定
    • Snippet Kind 為 MethodDecl
    • Description 為 這個程式碼片段的介紹與目的
    • Shortcut 為 可以快速呼叫這個程式碼片段的指令
    CodeSnippetEditor屬性
  5. 設定完成後,請儲存這個程式碼片段到某個資料夾內
  6. 點選 工具 > 程式碼片段管理員,點選加入按鈕,選擇剛剛儲存的資料夾,此時,您就可以開始使用這個新設定的程式碼片段了。

修改舊的程式碼片段

  1. 點選 檢視 > 其他視窗 > Snippet Explorer
  2. 輸入您要修改的程式碼片段 Shortcut 名稱,接著點選 Search按鈕
  3. 在搜尋出來的結果,滑鼠右擊要編輯的項目,點選 Open 選項,就可以編輯了
    SnippetExplorer

2016/09/16

Xamarin.Forms 自訂附加屬性 Attached Property

在這個範例中,將會說明如何產生一個具有類似 Grid.Row 這樣用法的附加屬性;這裡,將會新建一個附加屬性類別,當這個附加屬性設定在 Entry 控制項上的時候,就會自動設定該 Entry 控制項上的其他相關屬性。
自訂附加屬性
首先您需要先產生一個類別。
使用 BindableProperty.CreateAttached 靜態方法,產生一個附加屬性物件,在產生這個附加屬性的當時,定義 propertyChanged 引數,用於設定當該附加屬性值有異動的時候,需要呼叫的委派方法;在這個方法內,將會依據新設定的附加屬性值,設定所附加這個 Entry 的浮水印、字體大小、輸入鍵盤的屬性;若附加屬性並不是設定 Entry 控制項內,則不會做任何處理動作。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace AttProp
{
    public class EntryTypeAttached
    {
        public static readonly BindableProperty EntryTypeProperty =
               BindableProperty.CreateAttached(
                   propertyName: "EntryType",
                   returnType: typeof(string),
                   declaringType: typeof(Entry),
                   defaultValue: null,
                   defaultBindingMode: BindingMode.OneWay,
                   validateValue: null,
                   propertyChanged: OnEntryTypeChanged);

        private static void OnEntryTypeChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var fooEntry = bindable as Entry;
            if (fooEntry == null)
                return;

            var foooldValue = (oldValue as string)?.ToLower();
            var foonewValue = (newValue as string)?.ToLower();
            if(foonewValue == null)
            {
                return;
            }
            switch (foonewValue)
            {
                case "None":
                    break;
                case "email":
                    fooEntry.SetValue(Entry.PlaceholderProperty, "請輸入電子郵件");
                    fooEntry.Keyboard = Keyboard.Email;
                    fooEntry.FontSize = 20;
                    break;
                case "phone":
                    fooEntry.SetValue(Entry.PlaceholderProperty, "請輸入電話號碼");
                    fooEntry.Keyboard = Keyboard.Telephone;
                    fooEntry.FontSize = 20;
                    break;
                case "number":
                    fooEntry.SetValue(Entry.PlaceholderProperty, "請輸入數值");
                    fooEntry.Keyboard = Keyboard.Numeric;
                    fooEntry.FontSize = 20;
                    break;
                default:
                    break;
            }
        }

        // Helper methods for attached bindable property. 
        public static void SetEntryType(BindableObject bindable, string entryType)
        {
            bindable.SetValue(EntryTypeProperty, entryType);
        }
        public static string GetEntryType(BindableObject bindable)
        {
            return (string)bindable.GetValue(EntryTypeProperty);
        }
    }
}
當要引用這個附加屬性到 Entry 控制項內,可參考底下用法:
在這裡,您需要先定義使用這個附加屬性的 XAML 命名空間,您可以在根項目內,使用xmlns:CustomAttached="clr-namespace:AttProp"來加入新可使用的 XAML 命名空間。
此時,您就可以在 XAML 頁面中,在 Entry 控制項內,使用這個新建立的附加屬性 : <EntryCustomAttached:EntryTypeAttached.EntryType="Email" />
<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             xmlns:CustomAttached="clr-namespace:AttProp"
             x:Class="AttProp.Views.MainPage"
             Title="MainPage">
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <Label Text="自訂附加屬性 Attached Property" />
    <Entry />
    <Entry CustomAttached:EntryTypeAttached.EntryType="Email" />
    <Entry CustomAttached:EntryTypeAttached.EntryType="Phone" />
    <Entry CustomAttached:EntryTypeAttached.EntryType="Number" />
  </StackLayout>
</ContentPage>

2016/09/15

Xamarin.Forms Image 圖片控制項的特色與使用說明

這份筆記將會描述 Xamarin.Forms 內的 XAML Image 控制項的各項使用方式與其相關屬性設定方式。
在 Image 控制項,其圖片來源是透過 Source 屬性來設定,基本上,您可以使用文字來說明這個圖片是從網路上而得到,或者這個圖片是位於每個專屬專案內。若您想要從核心PCL專案內或者從您應用程式檔案系統內取得,則需要搭配其他方式來做到。
當然,上述的各種不同圖片來源各有不同的效果,原則上,若是採用後兩者(PCL / 本機檔案系統),則這些圖片是可以在不同平台上進行共用的;底下將會進行說明。
這份筆記的範例專案可以從底下取得

圖片來源

c sharp 的用法

圖片資源可以透過 ImageSource 類別提供的四個靜態方法來取得圖片
  • ImageSource.FromFile
    這個方法可以取得個專屬平台下的圖片檔案
  • ImageSource.FromUri
    這個方法是透過一個網址,取得網路上的圖片資源
  • ImageSource.FromeResource
    這個方法可以讓您取得核心PCL專案內的圖片檔案
  • ImageSource.FromStream
    這個方法可以讓您取得本機應用程式下載後的圖片檔案

XAML 的用法

  • 取得個專屬平台下的圖片檔案
    在 Source 屬性內,直接定義一個在專屬平台內的圖片檔案名稱,就可以從每個專屬專案平台下取得這個圖片檔案;若每個專案平台的圖片檔案路徑或者檔案名稱不同,可以使用 OnPlatform 來指定不同平台下的實際圖片檔案名稱與路徑。
<Image Grid.Row="1" Grid.Column="0" Source="platformImage.jpg" />
  • 取得網路上的圖片資源
    在 Source 屬性內,可以使用一個網址字串,就會讓 Image 自動從網路下載這個圖片
<Image Source="https://developer.xamarin.com/demo/IMG_3256.JPG?width=640" />
  • 取得核心PCL專案內的圖片檔案
    若想要在取得 PCL 專案內的圖片檔案,您需要客製化一個有建置 IMarkupExtension 介面的類別,並且在 XAML 內提供取得 PCL 內的圖片檔案。
<Image Grid.Row="0" Grid.Column="1" Source="{extMark:ImageResource XFImage.Assets.platformImage.jpg}"/>
該 IMarkupExtension 的類別的程式碼,如下所示,在 ImageResourceExtension 類別中,使用了ImageSource.FromResource 取出 PCL 內的圖片檔案並且轉換成為 ImageSource
    public class ImageResourceExtension : IMarkupExtension
    {
        public string Source { get; set; }
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (Source == null) return null;
            return ImageSource.FromResource(Source);
        }
    }
  • 取得本機應用程式下載後的圖片檔案
    這裡有兩種方法,一個是在 ViewModel 定義一個 ImageSource 的可綁定屬性,透過 ViewModel 內的方法,取得本機檔案的圖片檔案;在這裡,使用了 PCL Storage 這個套件,讓您可以在 PCL 專案內,取得本機檔案。
 <Image Grid.Row="1" Grid.Column="1" Source="{Binding MyImageSource}" />
ViewModel 內的可綁定 ImageSource 屬性
        private ImageSource myImageSource;
        public ImageSource MyImageSource
        {
            get { return myImageSource; }
            set { SetProperty(ref myImageSource, value); }
        }
底下的為在 ViewModel 內的某個 RelayCommand 方法,當按下這個按鈕,就會使用 HttpClient 物件來下載網路上的圖片,並且使用 PCL Storage 將這個圖片檔案儲存到本機上。
接著,使用 ImageSource.FromStream(() => fooTargetReadStream) 方法,取得這個圖片檔案的 ImageSource 物件,以便可以顯示在頁面上。
            #region 從網路下載後,先儲存到本機檔案系統內,接著再讀出來,進行圖片資料綁定
            HttpClient client = new HttpClient();
            IFolder rootFolder = FileSystem.Current.LocalStorage;
            IFolder folder = await rootFolder.CreateFolderAsync("Images", CreationCollisionOption.OpenIfExists);
            IFile file = await folder.CreateFileAsync("MyLocalImage.jpg", CreationCollisionOption.ReplaceExisting);
            using (var fooTargetStream = await file.OpenAsync(FileAccess.ReadAndWrite))
            {
                using (var fooSourceString = await client.GetStreamAsync("https://developer.xamarin.com/demo/IMG_3256.JPG?width=640"))
                {
                    await fooSourceString.CopyToAsync(fooTargetStream);
                }
            }

            file = await folder.GetFileAsync("MyLocalImage.jpg");
            var fooTargetReadStream = await file.OpenAsync(FileAccess.Read);
            MyImageSource = ImageSource.FromStream(() => fooTargetReadStream);
            #endregion
另外一個方法就是使用數值轉換器 Value Converter,使用這個解決方案,您需要定義一個新類別,且建置IValueConverter 介面,如下所示:
這段數值轉換器使用了 PCL Storage 套件,取得該該圖片檔案的內容,接著透過 ImageSource.FromStream靜態方法取得 ImageSource 物件。
    class StringToLocalImage : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string foo1 = value as string;
            ImageSource fooImageSource=null;

            if (string.IsNullOrEmpty(foo1) == false)
            {
                IFolder rootFolder = FileSystem.Current.LocalStorage;
                IFolder folder;
                IFile file;
                folder = rootFolder.CreateFolderAsync("Images", CreationCollisionOption.OpenIfExists).Result;
                file = folder.GetFileAsync(foo1).Result;
                var fooStream = file.OpenAsync(FileAccess.Read).Result;
                Debug.WriteLine($"Length: {fooStream.Length}");

                fooImageSource = ImageSource.FromStream(() => fooStream);
            }
            else
            {
            }

            //fooImageSource= ImageSource.FromUri(new Uri("https://developer.xamarin.com/demo/IMG_1415.JPG?height=640"));

            return fooImageSource;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
在 XAML 下,可以使用這樣的方式來宣告
    <Image Grid.Row="2" Grid.Column="1"
          Source="{Binding LocalImage2, Converter={StaticResource StringToLocalImage}}" />

Aspect

這個屬性是用來控制圖片要顯示何種大小,這裡需要使用 Aspect 的屬性。
  • Fill
    會自動將圖片充滿整個容器內,這可能會導致圖片會失真。
  • AspectFill
    使用等比例的放大,讓圖片不會失真,但是,會導致有些圖片區域無法顯示出來。
  • AspectFit
    使用等比例的放大,讓圖片不會失真,但是,會導致有些區域留下空白的內容。