XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2016/08/04

Xamarin.Forms MVVM 123

有無MVVM的專案開發比較

在這篇筆記中,將會透過三個專案,分別說明:
  1. 當沒有採用 MVVM 設計方法來進行專案開發的時候,如何透過程式碼後置 (Code Behind) 的方式,把相關商業邏輯串接起來。
  2. 當採用 MVVM 設計方法來進行專案開發的時候,如何自行定義與架構出您的 ViewModel,並且,在 View 中如何設定目標物件的綁定模式。
  3. 在這裡,將會說明如何使用 微軟 Microsoft Prism 這個套件提供的 MVVM 功能,快速方便的開發出 MVVM 專案。
這個專案的視覺設計,首先會
  1. 放置一個 Label 控制項,這個控制項會取得下一個 Entry 使用者輸入的值。
  2. 接著放置一個 Entry 控制項,讓使用者輸入任何文字,並且會動態即時更新道上一個 Label 控制項內
  3. 放置一個 Entry 控制項,讓使用者輸入任何文字,並且會更新上一個 Entry 控制項內的值,當然,就會連動更新第一個 Label 控制項的顯示內容。
  4. 放置一個 Button 按鈕,當按下這個按鈕,會在下一個 Label 內顯示內容
  5. 放置一個 Label 控制項,用來顯示當按鈕按下之後的內容。
下圖說明了,當使用者在 Entry 控制項輸入任何內容的時候,會在第一個 Label 控制項內顯示出來。
接著,當使用按下按鈕,也會顯示一段訊息。
MVVM1的結果
下圖表示當在第二個 Entry 控制項內輸入了任何內容,前面兩個 Label & Entry 控制項,都會同步更新同樣的內容。
MVVM1的結果

沒有使用 MVVM

底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM1.sln 方案
開啟查看核心PCL MVVM1 專案內的 MainPage.xaml,其 XAML 標記宣告內容如下:
這裡依照前面的視覺定義,使用 StackLayout 版面配置物件項目將這些控制項垂直排列出來。
<?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:local="clr-namespace:MVVM1"
             x:Class="MVVM1.MainPage">

  <StackLayout
    HorizontalOptions="Fill" VerticalOptions="Center">
    <Label x:Name="lbl輸入文字" />
    <Entry x:Name="eny輸入文字1" />
    <Entry x:Name="eny輸入文字2" />
    <Button x:Name="btn按鈕" Text="請按下我" />
    <Label x:Name="lbl按鈕文字" />
  </StackLayout>

</ContentPage>
開啟查看核心PCL MVVM1 專案內的 MainPage.xaml.cs,其code behind 的C#程式碼如下:
在 code beind 內,
  1. 定義了 Entry 的 TextChanged 的事件,當這個事件發生的時候,要進行其他資料的更新。
  2. 定義了 Button 的 Clicked 事件,當使用者按下這個按鈕之後,要更新相關控制項的內容。
這樣的定義與處理,好像非常普通,可是,由於相關商業邏輯都寫在 Code Behind 內,會帶來許多不良問題,因此,才會需要接下來的 MVVM 設計方法來改善這些問題。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace MVVM1
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            eny輸入文字1.TextChanged += (s, e) =>
            {
                lbl輸入文字.Text = eny輸入文字1.Text;
            };
            eny輸入文字2.TextChanged += (s, e) =>
            {
                eny輸入文字1.Text = eny輸入文字2.Text;
            };
            btn按鈕.Clicked += (s, e) =>
            {
                lbl按鈕文字.Text = $"您已經按下按鈕";
            };
        }
    }
}

使用 INotifyPropertyChanged 的 MVVM

底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM1.sln 方案
開啟查看核心PCL MVVM2 專案內的 MainPage.xaml,其 XAML 標記宣告內容如下:
  1. 在 ContentPage 的屬性項目 (Property Element)中(ContentPage.BindingContext) ,定義了這個整個頁面與所有控制項的 BindingContext 可以資料綁定的來源,都是來自於 MainPageViewModel 物件內。
  2. 在需要的控制項內,使用 XAML 標記延伸 (Markup Extension) 功能,也就是大括號,標示這個屬性值,是要透過資料細節的方式,從 BindingContext 內的 ViewModel 內取得。
  3. 在這裡,其實已經不再需要 x:Name 這個標記延伸功能,因為,在 ViewModel 內,是無法讀取到任何 View 內的 x:Name 內容(這個標記延伸僅是提供 code behind 程式碼可以抓取到 XAML 內的這個控制項)
  4. 在按鈕的宣告中,其相關事件將會使用 Command 這個屬性來取代,透過資料細節到 ViewModel 內的特定的 ICommand 屬性;也就是,當使用者按下這個按鈕之後,就會執行綁定的 ICommand 方法。
<?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:local="clr-namespace:MVVM2"
             x:Class="MVVM2.MainPage">

  <ContentPage.BindingContext>
    <local:MainPageViewModel />
  </ContentPage.BindingContext>

  <StackLayout
  HorizontalOptions="Fill" VerticalOptions="Center">
    <Label x:Name="lbl輸入文字"
           Text="{Binding EntryText1}"/>
    <!--這裡若改成 OneWay 會有甚麼效果呢?-->
    <Entry x:Name="eny輸入文字1"
           Text="{Binding EntryText1, Mode=TwoWay}"/>
    <Entry x:Name="eny輸入文字2"
           Text="{Binding EntryText2, Mode=TwoWay}"/>
    <Button x:Name="btn按鈕" Text="請按下我"
            Command="{Binding PushMeCommand}"/>
    <Label x:Name="lbl按鈕文字"
           Text="{Binding LabelText1}"/>
  </StackLayout>

</ContentPage>
由於使用了 MVVM 設計方法,所以,也就不會有任何 code behind 在 MainPage.xaml.cs 內。
開啟查看核心PCL MVVM2 專案內的 MainPageViewModel,其 ViewModel 的C#程式碼如下:
  1. 在這個 ViewModel 內,定義了四個屬性 (Property)
  2. 這個 ViewModel 需要實作 INotifyPropertyChanged 葉面,並且也要實作 OnPropertyChanged 方法
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;

namespace MVVM2
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        #region EntryText1
        private string _EntryText1;

        public string EntryText1
        {
            get
            {
                return _EntryText1;
            }
            set
            {
                if (_EntryText1 != value)
                {
                    _EntryText1 = value;
                    OnPropertyChanged("EntryText1");
                }
            }
        }
        #endregion

        #region EntryText2
        private string _EntryText2;

        public string EntryText2
        {
            get
            {
                return _EntryText2;
            }
            set
            {
                if (_EntryText2 != value)
                {
                    EntryText1 = value;
                    _EntryText2 = value;
                    OnPropertyChanged("EntryText2");
                }
            }
        }
        #endregion

        #region LabelText1
        private string _LabelText1;

        public string LabelText1
        {
            get
            {
                return _LabelText1;
            }
            set
            {
                if (_LabelText1 != value)
                {
                    _LabelText1 = value;
                    OnPropertyChanged("LabelText1");
                }
            }
        }
        #endregion

        #region Button ICommand
        public ICommand PushMeCommand { protected set; get; }
        #endregion

        public MainPageViewModel()
        {
            PushMeCommand = new Command(() =>
            {
                this.LabelText1 = $"您已經按下按鈕";
            });
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

使用 Prism MVVM 套件

底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM3.sln 方案
開啟查看核心PCL MVVM3 專案內的 MainPage.xaml,其 XAML 標記宣告內容如下:
  1. 在這個 XAML 宣告中,與 MVVM2 不同的地方,在於 Prism 提供了 ViewModel 自動注入之功能,只要在根項目內,使用 prism:ViewModelLocator.AutowireViewModel="True" 語法,就可以在建立 View 的時候,自動產生相對應的 ViewModel 並且,綁定到 BindingContext 屬性內。
<?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"
             x:Class="MVVM3.Views.MainPage"
             Title="MainPage">

  <StackLayout
  HorizontalOptions="Fill" VerticalOptions="Center">
    <Label x:Name="lbl輸入文字"
           Text="{Binding EntryText1}"/>
    <Entry x:Name="eny輸入文字1"
           Text="{Binding EntryText1, Mode=TwoWay}"/>
    <Entry x:Name="eny輸入文字2"
           Text="{Binding EntryText2, Mode=TwoWay}"/>
    <Button x:Name="btn按鈕" Text="請按下我"
            Command="{Binding PushMeCommand}"/>
    <Label x:Name="lbl按鈕文字"
           Text="{Binding LabelText1}"/>
  </StackLayout>

</ContentPage>
由於使用了 MVVM 設計方法,所以,也就不會有任何 code behind 在 MainPage.xaml.cs 內。
開啟查看核心PCL MVVM3 專案內的 MainPageViewModel,其 ViewModel 的C#程式碼如下:
  1. 由於使用了 Prism 框架來開發,所以,繼承了 BindableBase 這個類別,在這個類別內,已經把 MVVM 需要實作的 ViewModel 要處理的內容,都做好了。
  2. 其他的內容大都與 MVVM2 內的 ViewModel 一樣。
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MVVM3.ViewModels
{
    public class MainPageViewModel : BindableBase, INavigationAware
    {
        //private string _title;
        //public string Title
        //{
        //    get { return _title; }
        //    set { SetProperty(ref _title, value); }
        //}

        #region EntryText1
        private string _EntryText1;

        public string EntryText1
        {
            get
            {
                return _EntryText1;
            }
            set
            {
                SetProperty(ref _EntryText1, value);
            }
        }
        #endregion

        #region EntryText2
        private string _EntryText2;

        public string EntryText2
        {
            get
            {
                return _EntryText2;
            }
            set
            {
                SetProperty(ref _EntryText2, value);
                EntryText1 = value;
            }
        }
        #endregion

        #region LabelText1
        private string _LabelText1;

        public string LabelText1
        {
            get
            {
                return _LabelText1;
            }
            set
            {
                SetProperty(ref _LabelText1, value);
            }
        }
        #endregion

        #region Button ICommand
        public DelegateCommand PushMeCommand { protected set; get; }
        #endregion

        public MainPageViewModel()
        {
            PushMeCommand = new DelegateCommand(() =>
            {
                this.LabelText1 = $"您已經按下按鈕";
            });
        }


        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
        }
    }
}

2016/08/03

Xamarin.Forms Prism 的 Unity 容器之註冊與解析

Prism 的 Unity 容器之註冊與解析

這是我第一篇關於 Prism 方面的文章,在這篇筆記中,將會說明如何透過 Prism 容器 (Container),進行型別、物件的註冊,並且稍後可以解析方式,取得當初註冊的內容值。

擴充 RegisterTypes 方法

在 App.xaml.cs檔案內,因為,App 類別是繼承 PrismApplication 類別,因此,在 App 類別內,可以覆寫 RegisterTypes,在這個方法之內,可以註冊其他的型別到容器內。
當您僅僅使用 Xamarin.Forms 的 MVVM 架構來開發,可以在這裡簡單的使用 Container.RegisterTypeForNavigation 來註冊 View & ViewModle 的類別,當View產生的時候,會自動注入。

App.xaml.cs

    public partial class App : PrismApplication
    {
        protected override void OnInitialized()
        {
            InitializeComponent();

            NavigationService.Navigate("MainPage?title=Hello%20from%20Xamarin.Forms");
        }

        protected override void RegisterTypes()
        {
            Container.RegisterTypeForNavigation<MainPage>();
        }
    }

其他的使用方法

底下的程式碼說明了 註冊介面對應到一個類別或這具體的型別註冊一個類別或者型別,但具有 singleton 執行個體 這兩件事情,但是,不論要做哪件事情,都需要先取得系統預設的容器,您可以透過這段程式碼 :IUnityContainer myContainer = (App.Current as PrismApplication).Container;,隨時取得系統內的容器。
                IUnityContainer myContainer = (App.Current as PrismApplication).Container;

                #region 註冊介面對應到一個類別或這具體的型別
                // 註冊一個預設、沒有命名名稱的型別對應,並且使用短暫(transient)生命週期(lifetime)管理
                myContainer.RegisterType<IMyClass, MyClass>();
                // 取得新產生的執行個體
                var fooObject = myContainer.Resolve<IMyClass>();

                // 註冊一個預設、有命名名稱的型別對應,並且使用短暫(transient)生命週期(lifetime)管理
                myContainer.RegisterType<IMyClass, MyClass>("MyMapping");
                // 使用已經命名名稱,取得新產生的執行個體
                fooObject = myContainer.Resolve<IMyClass>("MyMapping");

                // 註冊一個預設、沒有命名名稱的型別對應,並且使用每個執行緒(per thread)生命週期(lifetime)管理
                myContainer.RegisterType<IMyClass, MyClass>(new PerThreadLifetimeManager());
                // 在相同執行續下,將會取得相同 singleton 執行個體,容器將會持有僅僅一個弱參考的執行個體
                // https://msdn.microsoft.com/zh-tw/library/ms404247(v=vs.110).aspx
                fooObject = myContainer.Resolve<IMyClass>();

                // 註冊一個預設、沒有命名名稱的型別對應,並且使用額外控制(externally-controlled)生命週期(lifetime)管理
                myContainer.RegisterType<IMyClass, MyClass>(new ExternallyControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會持有僅僅一個弱參考的執行個體
                fooObject = myContainer.Resolve<IMyClass>();

                // 註冊一個預設、有命名名稱的型別對應,並且使用額外控制(externally-controlled)生命週期(lifetime)管理
                myContainer.RegisterType<IMyClass, MyClass>("MyMapping", new ExternallyControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會持有僅僅一個弱參考的執行個體
                fooObject = myContainer.Resolve<IMyClass>("MyMapping");
                #endregion

                #region 註冊一個類別或者型別,但具有 singleton 執行個體
                // https://msdn.microsoft.com/en-us/library/dn507499(v=pandp.30).aspx

                // 註冊一個預設、沒有命名名稱的型別對應,並且使用 singleton 生命週期(lifetime)管理
                myContainer.RegisterType<IMyClass, MyClass>(new ContainerControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會負責接管該物件生命週期管理
                fooObject = myContainer.Resolve<IMyClass>();

                // 註冊一個預設、有命名名稱的型別對應,並且使用 singleton 生命週期(lifetime)管理
                myContainer.RegisterType<IMyClass, MyClass>("MyMapping", new ContainerControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會負責接管該物件生命週期管理
                myContainer.Resolve<IMyClass>();

                // 註冊一個沒有型別對應,並且使用 singleton 生命週期(lifetime)管理,使用容器僅建置 singletone 行為
                myContainer.RegisterType<MyClass>(new ContainerControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會負責接管該物件生命週期管理
                fooObject = myContainer.Resolve<MyClass>();

                // 註冊一個有命名名稱,但沒有型別對應,並且使用 singleton 生命週期(lifetime)管理,使用容器僅建置 singletone 行為
                myContainer.RegisterType<MyClass>("MyMapping", new ContainerControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會負責接管該物件生命週期管理
                fooObject = myContainer.Resolve<MyClass>("MyMapping");
                #endregion
底下的程式碼說明了 註冊一個存在物件,使其成為 singleton 執行個體
                IUnityContainer myContainer = (App.Current as PrismApplication).Container;

                #region 註冊一個存在物件,使其成為 singleton 執行個體
                var fooMyClassObject = new MyClass();

                // 註冊一個存在物件為預設、沒有命名名稱、使用預設容器控制器(container-controlled)生命週期管理
                myContainer.RegisterInstance<IMyClass>(fooMyClassObject);
                // 取得 singleton 執行個體,容器將會接管這個物件的生命週期管理
                var fooObject = myContainer.Resolve<IMyClass>();

                // 註冊一個存在物件為預設、有命名名稱、使用預設容器控制器(container-controlled)生命週期管理
                myContainer.RegisterInstance<IMyClass>("MySingleton", fooMyClassObject);
                // 取得 singleton 執行個體,容器將會接管這個物件的生命週期管理
                fooObject = myContainer.Resolve<IMyClass>("MySingleton");

                // 註冊一個存在物件為預設、有命名名稱、使用指定容器控制器(container-controlled) ContainerControlledLifetimeManager 生命週期管理
                myContainer.RegisterInstance<IMyClass>("MySingleton", fooMyClassObject, new ContainerControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會接管這個物件的生命週期管理
                fooObject = myContainer.Resolve<IMyClass>("MySingleton");

                // 註冊一個存在物件為預設、沒有命名名稱、使用額外控制(externally controlled)生命週期管理
                myContainer.RegisterInstance<IMyClass>(fooMyClassObject, new ExternallyControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會持有僅僅一個弱參考的物件
                fooObject = myContainer.Resolve<IMyClass>();

                // 註冊一個存在物件為預設、有命名名稱、使用額外控制(externally controlled)生命週期管理
                myContainer.RegisterInstance<IMyClass>("MySingleton", fooMyClassObject, new ExternallyControlledLifetimeManager());
                // 取得 singleton 執行個體,容器將會持有僅僅一個弱參考的物件
                fooObject = myContainer.Resolve<IMyClass>("MySingleton");

                // 註冊一個存在物件為預設、有命名名稱、使用每個執行續(per thread)生命週期管理
                myContainer.RegisterInstance<IMyClass>("MySingleton", fooMyClassObject, new PerThreadLifetimeManager());
                // 取得 singleton 執行個體,容器將會持有僅僅一個弱參考的物件
                fooObject = myContainer.Resolve<IMyClass>("MySingleton");
                #endregion

2016/08/01

如何產生 Font Awesome 的字體圖示圖片檔案

Font Awesome 的字體圖示真的很好用,因為,他可以大量取代在進行行動裝置應用程式開發過程中,需要使用到大量的圖片檔案資源的困擾,不過,有些時候,您一定需要在行動應用程式內使用到某個圖片檔案,但是,又不想要麻煩其他人來幫您製作這個簡單的圖片檔案,這個時候,您可以選擇將 Font Awesome 裡面的字型圖示,將其轉換成為一個圖片檔案。
請先開啟這個網站
http://fa2png.io/
接著依照底下數字標示說明,進行操作。
  1. 在這請輸入您想要轉換的 Font Awesome 字體的名稱,若您不太清楚有那些名字可以輸入,您可以參考 Font Awesome 的網站 : http://fontawesome.io/icons/
  2. 您可以在這裡選擇要產生的圖片檔案前景顏色,您可選擇直接輸入色碼或者點選最右方的區域,就會有調色盤出現,供您選擇您需要的顏色。
  3. 在這裡可以選擇這個圖片檔案的背景顏色。
  4. 透過這個數值,可以指定要產生的圖片檔案的常語寬的畫素有多少。
  5. 您可以指定這個圖片的邊界數值
  6. 最後,按下 Generate 這個按鈕,就可以產生並且下載這個圖片檔案囉
enter image description here

行動裝置的視覺設計指引

在進行行動裝置應用程式開發的時候,每個平台都會要求您要按照他們的使用者經驗 User Experience 開發指南,進行每個使用應用程式畫面與使用者介面的設計。不過,在台灣這裡,似乎有去看過,研究過這些文件的人,真的很少,大家大多是憑藉著自認為優異的知識與經驗和自我感覺來進行使用者視覺設計的工作。

iOS

https://developer.apple.com/ios/human-interface-guidelines/

Android

https://developer.android.com/design/index.html

Windows UWP

https://www.google.com.tw/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&cad=rja&uact=8&ved=0ahUKEwjK--WfmZ_OAhULM48KHZw7BCMQFggmMAE&url=http%3A%2F%2Fgo.microsoft.com%2Ffwlink%2Fp%2F%3FLinkId%3D626098&usg=AFQjCNH8Rm9JHHO3PIhHAoOwWkO87tdQBg&sig2=JSWLhIoPtzpw3zS1RXAWOw
https://developer.microsoft.com/zh-tw/windows/design