2017/8/24

Xamarin.Forms 教學系列文(二十三.參)Behaviors - Visual Element 手動附加功能


學習目標
  • Behavior- 元件的自定義附加行為
  • 在 Behavior 內加入 Bindable Property

Trigger 和 Behavior 其實滿像的,所有 Trigger 可以做的事情都能用 Behavior 完成,

但,Behavior 要寫的程式碼比 Trigger 多,

所以,能用 Trigger 解決的問題就不要用 Behavior !!!
Behavior 就像是 Trigger 的加強版,當然也可以在 XAML 重複使用。

等等,所以 Behavior 到底是什麼??

可以想成:
幫 Visual Element 手動附加新功能。

來看一下範例,
當 Entry 輸入非數字時,文字會變成紅色,

本範例就是附加 判斷是否為數字  的新功能
先來看 Trigger 寫法 
.cs
namespace Xamarin.FormsBook.Toolkit
{
    public class NumericValidationAction : TriggerAction
    {
        protected override void Invoke(Entry entry)
        {
            double result;
            bool isValid = Double.TryParse(entry.Text, out result);
            entry.TextColor = isValid ? Color.Default : Color.Red;
        }
    }
}
XAML
<Entry Placeholder="Enter a System.Double">
    <Entry.Triggers>
        <EventTrigger Event="TextChanged">
            <toolkit:NumericValidationAction />
        </EventTrigger>
    </Entry.Triggers>

再來看 Behavior
Behavior 有兩個方法要覆寫,OnAttachedTo OnDetachingFrom

OnAttachedTo - 當 Behavior 附加於 Visual Element 時被呼叫。
//通常會委派新的事件 (handler) 給物件 。

OnDetachingFrom - 要移除 OnAttachedTo 做的所有事情
//即使這件事通常只發生在程式關閉時,你還是得寫好移除的動作。
namespace Xamarin.FormsBook.Toolkit
{
    public class NumericValidationBehavior : Behavior< Entry>
    {
        protected override void OnAttachedTo(Entry entry)
        {
            base.OnAttachedTo(entry);
            //委派新事件
            entry.TextChanged += OnEntryTextChanged;
        }
        
        protected override void OnDetachingFrom(Entry entry)
        {
            base.OnDetachingFrom(entry);
            //移除事件
            entry.TextChanged -= OnEntryTextChanged;
        }

        void OnEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            //若輸入非數字,則將文字改成紅色
            double result;
            bool isValid = Double.TryParse(args.NewTextValue, out result);
            ((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
        }
    }
}

*要注意 Behavior<T> 會指定附加時物件的型別,本範例就是 Entry

來看 XAML 怎使用:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
 xmlns:toolkit="clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="BehaviorEntryValidation.BehaviorEntryValidationPage"
             Padding="50">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Entry">
                
                <!--跟 Trigger 有那麼一點像吧-->
                <Style.Behaviors>
                    <toolkit:NumericValidationBehavior />
                </Style.Behaviors>
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    
    <StackLayout>
        <Entry Placeholder="Enter a System.Double" />
        <Entry Placeholder="Enter a System.Double" />
        <Entry Placeholder="Enter a System.Double" />
        <Entry Placeholder="Enter a System.Double" />
    </StackLayout>
</ContentPage>

執行結果:
因為寫在 Style 內的關係,畫面上四個 Entry 都被賦予新的 Behavior


雖然與 Trigger 比起來程式碼多了許多,

但 Behavior 給予我們更彈性的寫法甚至是建立 可綁定的屬性!!

Behavior 與可綁定屬性

在 Behavior 內,我們可以自行新增 可綁定屬性 (Bindable Property)

Bindable Property 的觀念在十一章,
//不過我沒寫,呵呵。

總之,若 XAML 要與某屬性 Binding,那個屬性就必須為 可綁定的 (Bindable)

在 Behavior 內新增 可綁定屬性 的用意就是,當我們事件邏輯處理完後,可以把結果綁回 XAML 進行畫面上的控制。

例如底下要做的 Email 驗證,若 Entry 輸入非 Email 格式,那 Button 就不能點擊。

底下我們利用 Behavior 來實作 Email 驗證
並加入可綁定的 IsValid 屬性:
namespace Xamarin.FormsBook.Toolkit
{
    public class ValidEmailBehavior : Behavior
    {
        //加入可綁定屬性的寫法
        static readonly BindablePropertyKey IsValidPropertyKey =
            BindableProperty.CreateReadOnly("IsValid",
                                            typeof(bool),
                                            typeof(ValidEmailBehavior),
                                            false);

        public static readonly BindableProperty IsValidProperty = 
            IsValidPropertyKey.BindableProperty;

        public bool IsValid
        {
            //可綁定屬性記得加上 SetValue 和 GetValue
            private set { SetValue(IsValidPropertyKey, value); }
            get { return (bool)GetValue(IsValidProperty); }
        }

        protected override void OnAttachedTo(Entry entry)
        {
            entry.TextChanged += OnEntryTextChanged;
            base.OnAttachedTo(entry);
        }
        protected override void OnDetachingFrom(Entry entry)
        {
            entry.TextChanged -= OnEntryTextChanged;
            base.OnDetachingFrom(entry);
        }

        void OnEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            Entry entry = (Entry)sender;
            IsValid = IsValidEmail(entry.Text);
        }

        //判斷是否為 Email
        bool IsValidEmail(string strIn)
        {
            if (String.IsNullOrEmpty(strIn))
                return false;
            try
            {
                // from https://msdn.microsoft.com/en-us/library/01escwtf(v=vs.110).aspx
                return Regex.IsMatch(strIn,
                @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|" +
                @"[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)" +
                @"(?<=[0-9a-z])@))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|" +
                @"(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$",
                RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
            }
            catch (RegexMatchTimeoutException)
            {
                return false;
            }
        }
    }
}

將 IsValid 綁定於 Button 的 IsEnabled:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="EmailValidationConverter.EmailValidationConverterPage"
             Padding="50">
    <StackLayout>
        <StackLayout Orientation="Horizontal">
            <Entry Placeholder="Enter email address"
                   HorizontalOptions="FillAndExpand">
                <!--加入 Behavior-->
                <Entry.Behaviors>
                    <toolkit:ValidEmailBehavior x:Name="validEmail" />
                </Entry.Behaviors>
            </Entry>
        </StackLayout>
        
        <!--綁定 IsValid-->
        <Button Text="Send!"
                FontSize="Large"
                HorizontalOptions="Center"
                IsEnabled="{Binding Source={x:Reference validEmail},
                                    Path=IsValid}" />
    </StackLayout>
</ContentPage>


最後要提醒的是,

若 Behavior 內 有可綁定屬性,就不能寫在 Style 內讓大家一起共用...

必須於每個 Visual Element 加入自己的 Behavior,

原因是,
可綁定屬性是一種 狀態,而這個狀態是依據每個 Visual Element所輸入的值而有所不同,

當然不能跟其他物件一起共用那個狀態拉!




沒有留言:

張貼留言