XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2016/11/08

Xamarin.Forms 的觸發使用方法

Xamarin.Forms 提供了底下四種觸發方法可以使用
屬性觸發 Property Trigger - 當所指定的屬性值有變更並且符合指定的條件,就會進行相關內容設定。
資料觸發 Data Trigger - 若這個屬性使用了資料繫結來綁定其屬性值,可以使用這個方式,當綁定的屬性值符合觸發條件,就可以進行相關內容設定。
事件觸發 Event Trigger - 當所指定的事件發生的時候,就會被觸發。
多重觸發 Multi Trigger - Xamarin.Forms 允許多重觸發條件的設定。
要定義觸發非常的容易,因為每個 View 都會擁有一個 Triggers 屬性,使用 Property-Element 的標示方式,就可以定義這個 View 的觸發條件與相關動作。
XFTrigger
範例專案
在這個範例中,完全沒有用到 ViewModel 與 Code Behind,就可以做到

屬性觸發 Property Trigger

在其 Triggers 屬性內,可以定義屬性觸發,例如這樣: <Trigger TargetType="Entry" Property="IsFocused" Value="True" > 這裡定義了,這個 Entry 控制項,當其 IsFocused 屬性值為 True 的時候,會進行底下的 Setter 的設定;講白話一點,就是當使用者點選到這個控制項之後,這個 Entry 控制項的三個屬性就會跟著變更;若使用者切換到其他的控制項內,則這三個屬性值,就會回到原先的設定值。
        <Entry Placeholder="請輸入名稱" FontSize="14">
            <Entry.Triggers>
                <Trigger TargetType="Entry" Property="IsFocused" Value="True" >
                    <Setter Property="BackgroundColor" Value="Black" />
                    <Setter Property="PlaceholderColor" Value="Yellow" />
                    <Setter Property="TextColor" Value="White" />
                </Trigger>
            </Entry.Triggers>
        </Entry>

資料觸發 Data Trigger

在這個範例中,需要做到當 Entry 有輸入資料的時候,該 Entry 底下的按鈕才會啟用,也就是使用者可以來點選。
要做到這樣的效果,需要在 Button 控制項內 Triggers 設定一個觸發條件,就是 DataTrigger,但是,在這裡並不使用 Value 這個屬性,而是使用了 Binding 這個屬性,裡面指定了綁定的來源;在這個範例中,透過了 Source 指定綁定來源為另外一個 Entry 控制項內的 Text.Length 這個使用。當這個屬性值為 0 的時候,就會觸發條件,設定這個按鈕為停止啟用狀態。這也就是說,當使用者在 Entry 內輸入資料的時候,這個按鈕的 IsEnabled 就會變成 True,也就是,這個按鈕可以讓使用者點選了。
        <!-- 提示: 確定 Text="" 需要有預設值,否則,會以問題 -->
        <Entry x:Name="entry" Text="" Placeholder="這個欄位需要輸入" 
                FontSize="14" TextColor="Blue" />
        <Button Text="儲存" HorizontalOptions="Center">
            <Button.Triggers>
                <DataTrigger TargetType="Button"
                     Binding="{Binding Source={x:Reference entry},
                                       Path=Text.Length}" Value="0">
                    <Setter Property="IsEnabled" Value="False" />
                </DataTrigger>
            </Button.Triggers>
        </Button>

事件觸發 Event Trigger

事件觸發則是更加簡單了,透過設定 <EventTrigger Event="TextChanged"> 設定 Event 這個屬性,當其指定的事件發生之後,就會執行 <local:NumericValidationTriggerAction /> 內的 Invoke 方法。
        <Entry Placeholder="請輸入名稱" FontSize="14">
            <Entry.Triggers>
                <EventTrigger Event="TextChanged">
                    <local:NumericValidationTriggerAction />
                </EventTrigger>
            </Entry.Triggers>
        </Entry>
NumericValidationTriggerAction 類別需要繼承 TriggerAction<T>,並且定義 Invoke 這個方法
    class NumericValidationTriggerAction : TriggerAction<Entry>
    {
        protected override void Invoke(Entry entry)
        {
            double result;
            bool isValid = Double.TryParse(entry.Text, out result);
            entry.TextColor = isValid ? Color.Default : Color.Red;
            entry.Scale = 1.4;
        }
    }

多重觸發 Multi Trigger

多重觸發看起來與屬性觸發和資料觸發類似,但是,在多重觸發中,可以設定更多的觸發條件,必須當所有設定的條件都符合的時候,才會進行觸發的 Setter 設定動作。
在底下的範例中,按鈕使用了 <MultiTrigger TargetType="Button"> ,也就是使用了多重觸發。
這裡定義了兩個觸發條件,也就是當上面兩個 Entry 都沒有輸入任何內容的時候,這個多重觸發條件就會啟動,這個時候,這個按鈕就會被設定 IsEnabled的值為 False,就是這個按鈕是無法使用的。
講白話一點,當這兩個 Entry 任何一個或者兩個,都有輸入內容的時候,這個按鈕 IsEnabled 就會為 True,就是可以被點選。
        <!--這裡為什麼無法做到兩個同時都要輸入,才能夠觸發呢?-->
        <Label Text="多重觸發 Multi Trigger(任一有輸入)" TextColor="Blue"/>
        <Entry x:Name="user" Text="" Placeholder="請輸入使用者名稱" FontSize="14" />
        <Entry x:Name="pwd" Text="" Placeholder="請輸入密碼" IsPassword="True" FontSize="14" />
        <Button x:Name="loginButton" Text="Login"
                HorizontalOptions="Center" >
            <Button.Triggers>
                <MultiTrigger TargetType="Button">
                    <MultiTrigger.Conditions>
                        <BindingCondition Binding="{Binding Source={x:Reference user},
                                   Path=Text.Length}" Value="0" />
                        <BindingCondition Binding="{Binding Source={x:Reference pwd},
                                   Path=Text.Length}" Value="0" />
                    </MultiTrigger.Conditions>
                    <Setter Property="IsEnabled" Value="False" />
                </MultiTrigger>
            </Button.Triggers>
        </Button>
在上面的多重觸發應用中,若我們想要做到這兩個 Entry 都有輸入值的時候,按鈕才可以使用,那麼,這樣如何做到呢?這個時候,就要看底下的範例。
在這裡,還是一樣要設定 Button.Triggers 的 MultiTrigger 條件,不過,在這裡,按鈕預設是被停用狀態。
在進行設定 BindingCondition 條件的時候,透過了數值轉換器來將字串的長度,轉換成為布林值。
所有,兩個多重觸發的條件,都是當使用者有輸入內容的時候( Text.Length > 0) ,其條件就會成立。
請記得多重觸發是 必須當所有設定的條件都符合的時候,才會進行觸發的 Setter 設定動作 這樣的規則,因此,在此就可以做到,兩個 Entry 都有輸入內容的時候,按鈕才會啟用。
        <!--這裡需要兩個文字輸入盒同時都要有輸入,按鈕才會啟用-->
        <Label Text="多重觸發 Multi Trigger(都要有輸入)" TextColor="Blue"/>
        <Entry x:Name="user1" Text="" Placeholder="請輸入使用者名稱" FontSize="14" />
        <Entry x:Name="pwd1" Text="" Placeholder="請輸入密碼" IsPassword="True" FontSize="14" />
        <Button x:Name="login1Button" Text="Login"
                FontSize="Large"
                HorizontalOptions="Center"
                IsEnabled="false">

            <Button.Triggers>
                <MultiTrigger TargetType="Button">
                    <MultiTrigger.Conditions>
                        <BindingCondition Binding="{Binding Source={x:Reference user1},
                                               Path=Text.Length,
                                               Converter={StaticResource dataHasBeenEntered}}"
                                           Value="true" />
                        <BindingCondition Binding="{Binding Source={x:Reference pwd1},
                                               Path=Text.Length,
                                               Converter={StaticResource dataHasBeenEntered}}"
                                           Value="true" />
                    </MultiTrigger.Conditions>

                    <Setter Property="IsEnabled" Value="True" />
                </MultiTrigger>
            </Button.Triggers>
        </Button>
這個 MultiTriggerConverter 數值轉換器,會收到一個整數數值,若該數值大於 0,則會回傳 True,否則,會回傳 False。這表示了,當使用者在 Entry 有輸入內容的時候,會經過數值轉換器得到 True 的值。
    class MultiTriggerConverter : IValueConverter
    {
        public object Convert(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            if ((int)value > 0)
                return true;    // data has been entered
            else
                return false;   // input is empty
        }

        public object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }

完整的 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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             xmlns:local="clr-namespace:XFTrigger"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFTrigger.Views.MainPage"
             Title="MainPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:MultiTriggerConverter x:Key="dataHasBeenEntered" />
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
        <Label Text="屬性觸發 Property Trigger" TextColor="Blue"/>
        <Entry Placeholder="請輸入名稱" FontSize="14">
            <Entry.Triggers>
                <Trigger TargetType="Entry" Property="IsFocused" Value="True" >
                    <Setter Property="BackgroundColor" Value="Black" />
                    <Setter Property="PlaceholderColor" Value="Yellow" />
                    <Setter Property="TextColor" Value="White" />
                </Trigger>
            </Entry.Triggers>
        </Entry>


        <Label Text="資料觸發 Data Trigger"/>
        <!-- 提示: 確定 Text="" 需要有預設值,否則,會以問題 -->
        <Entry x:Name="entry" Text="" Placeholder="這個欄位需要輸入" 
                FontSize="14" TextColor="Blue" />
        <Button Text="儲存" HorizontalOptions="Center">
            <Button.Triggers>
                <DataTrigger TargetType="Button"
                     Binding="{Binding Source={x:Reference entry},
                                       Path=Text.Length}" Value="0">
                    <Setter Property="IsEnabled" Value="False" />
                </DataTrigger>
            </Button.Triggers>
        </Button>

        <Label Text="事件觸發 Event Trigger" TextColor="Blue"/>
        <Entry Placeholder="請輸入名稱" FontSize="14">
            <Entry.Triggers>
                <EventTrigger Event="TextChanged">
                    <local:NumericValidationTriggerAction />
                </EventTrigger>
            </Entry.Triggers>
        </Entry>

        <!--這裡為什麼無法做到兩個同時都要輸入,才能夠觸發呢?-->
        <Label Text="多重觸發 Multi Trigger(任一有輸入)" TextColor="Blue"/>
        <Entry x:Name="user" Text="" Placeholder="請輸入使用者名稱" FontSize="14" />
        <Entry x:Name="pwd" Text="" Placeholder="請輸入密碼" IsPassword="True" FontSize="14" />
        <Button x:Name="loginButton" Text="Login"
                HorizontalOptions="Center" >
            <Button.Triggers>
                <MultiTrigger TargetType="Button">
                    <MultiTrigger.Conditions>
                        <BindingCondition Binding="{Binding Source={x:Reference user},
                                   Path=Text.Length}" Value="0" />
                        <BindingCondition Binding="{Binding Source={x:Reference pwd},
                                   Path=Text.Length}" Value="0" />
                    </MultiTrigger.Conditions>
                    <Setter Property="IsEnabled" Value="False" />
                </MultiTrigger>
            </Button.Triggers>
        </Button>

        <!--這裡需要兩個文字輸入盒同時都要有輸入,按鈕才會啟用-->
        <Label Text="多重觸發 Multi Trigger(都要有輸入)" TextColor="Blue"/>
        <Entry x:Name="user1" Text="" Placeholder="請輸入使用者名稱" FontSize="14" />
        <Entry x:Name="pwd1" Text="" Placeholder="請輸入密碼" IsPassword="True" FontSize="14" />
        <Button x:Name="login1Button" Text="Login"
                FontSize="Large"
                HorizontalOptions="Center"
                IsEnabled="false">

            <Button.Triggers>
                <MultiTrigger TargetType="Button">
                    <MultiTrigger.Conditions>
                        <BindingCondition Binding="{Binding Source={x:Reference user1},
                                               Path=Text.Length,
                                               Converter={StaticResource dataHasBeenEntered}}"
                                           Value="true" />
                        <BindingCondition Binding="{Binding Source={x:Reference pwd1},
                                               Path=Text.Length,
                                               Converter={StaticResource dataHasBeenEntered}}"
                                           Value="true" />
                    </MultiTrigger.Conditions>

                    <Setter Property="IsEnabled" Value="True" />
                </MultiTrigger>
            </Button.Triggers>
        </Button>
    </StackLayout>
</ContentPage>

2016/11/07

Xamarin.Forms 自訂綁定屬性 Bindable Property

在這個範例中,將會說明如何客製化 Entry 控制項,使其多了一個可綁訂定屬性,您可以透過這個綁定屬性設定這個 Entry 控制項的呈現樣貌。
自訂綁定屬性
首先您需要先產生一個類別,該類別要繼承 Entry,並且在新產生的類別中,定義一個綁定屬性,您可以使用靜態方法 BindableProperty.Create 來產生這個綁定屬性。
在定義綁定屬性的當時,定義 propertyChanged 引數,用於設定當該綁定屬性值有異動的時候,需要呼叫的委派方法;在這個方法內,依據新設定的綁定屬性值,設定這個 Entry 的浮水印、字體大小、輸入鍵盤的屬性。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace BindProp
{
    public class DoggyEntry : Entry
    {
        // https://developer.xamarin.com/guides/xamarin-forms/xaml/bindable-properties/
        public static readonly BindableProperty EntryTypeProperty =
            BindableProperty.Create("EntryType", // 屬性名稱 
                typeof(string), // 回傳類型 
                typeof(DoggyEntry), // 宣告類型 
                "None", // 預設值 
                propertyChanged: OnEntryTypeChanged);

        private static void OnEntryTypeChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var fooEntry = bindable as DoggyEntry;
            var foooldValue = (oldValue as string).ToLower();
            var foonewValue = (newValue as string).ToLower();

            switch (foonewValue)
            {
                case "None":
                    break;
                case "email":
                    fooEntry.SetValue(PlaceholderProperty, "請輸入電子郵件");
                    fooEntry.Keyboard = Keyboard.Email;
                    fooEntry.FontSize = 20;
                    break;
                case "phone":
                    fooEntry.SetValue(PlaceholderProperty, "請輸入電話號碼");
                    fooEntry.Keyboard = Keyboard.Telephone;
                    fooEntry.FontSize = 20;
                    break;
                case "number":
                    fooEntry.SetValue(PlaceholderProperty, "請輸入數值");
                    fooEntry.Keyboard = Keyboard.Numeric;
                    fooEntry.FontSize = 20;
                    break;
                default:
                    break;
            }
        }

        public DoggyEntry()
        {

        }

        public string EntryType
        {
            set
            {
                SetValue(EntryTypeProperty, value);
            }
            get
            {
                return (string)GetValue(EntryTypeProperty);
            }
        }
    }
}
當要引用這個客製化的 Entry 控制項內新增加的綁定屬性,可參考底下用法:
在這裡,您需要先定義使用這個自訂控制項的 XAML 命名空間,您可以在根項目內,使用 xmlns:CustomControl="clr-namespace:BindProp"來加入新可使用的 XAML 命名空間。
此時,您就可以在 XAML 頁面中,直接使用這個新建立的自訂控制項 : <CustomControl:DoggyEntry 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:CustomControl="clr-namespace:BindProp"
             x:Class="BindProp.Views.MainPage"
             Title="MainPage">
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <Label Text="自訂綁定屬性 Bindable Property" />
    <CustomControl:DoggyEntry EntryType="Email" />
    <CustomControl:DoggyEntry EntryType="Phone" />
    <CustomControl:DoggyEntry EntryType="Number" />
  </StackLayout>
</ContentPage>