如何使用 On_PropertyName_Changed 與 附加屬性 Attached Properties 和行為 Behavior,來得知 Entry 與 Editor 已經輸入了多少文字
當在進行 Xamarin.Forms 專案開發的時候,有些時候需要得知 Entry 或者 Editor 這兩種檢視或者稱為控制向,使用者已經輸入了多少內容或者字數,通常可以透過這兩個控制項的 Entry.TextChanged 事件或者 Editor.TextChanged 事件來得知使用者已經輸入了甚麼內容,這樣就可以計算出使用者已經輸入多少文字內容,可是,若要使用事件的話,就需要使用該頁面的 Code Behind 的方式來撰寫 C# 事件委派方法,讓使用者輸入文字的時候,可以觸發這個事件,以便進行計算字數工作。
在使用 Prism 這類 MVVM 開發框架的設計模式的時候,幾乎所有的商業邏輯都會撰寫到 View (頁面) 的 ViewModel (檢視模型) 上,如此,要如何得知使用者已經在 Entry / Editor 檢視中輸入甚麼內容呢?這裡將會提供底下方式來解決此一需求。
這篇文章的範例程式原始碼,可以從 GitHub 取得。
PropertyChanged.Fody 套件提供的 屬性 變更觸發事件
在 PropertyChanged.Fody 的網頁中,將會有一篇關於 On_PropertyName_Changed 文章,告訴 Xamarin.Forms 開發者如何在 ViewModel 中來撰寫出當所綁定的屬性值有異動的時候,將會觸發一個 ViewModel 上的委派方法。
在此應用中,將會在頁面中使用底下 XAML 進行宣告一個 Entry 檢視,其中,當使用者輸入的文字內容,將會自動綁定到 ViewModel 類別內的 C# Property 屬性 EntryChangedInputText 上,而 ViewModel 類別內的 C# Property 屬性 EntryChangedInputTextLength 將會是該輸入文字的字數數值。
<Label Text="{Binding EntryChangedInputTextLength}"/>
<Entry
Text="{Binding EntryChangedInputText}"/>
現在,請在該頁面的檢視模型 ViewModel 中,定義一個方法,其方法名稱必須為 On屬性名稱Changed ,在這裡將會定義一個 OnEntryChangedInputTextChanged 方法,當這個 Xamarin.Forms 專案執行的時候,使用者在該 Entry 輸入任何文字,將會觸發 OnEntryChangedInputTextChanged 方法執行,在此方法內,將會撰寫符合需求的計算字數商業邏輯程式碼,計算完成之後,就可以儲存到 EntryChangedInputTextLength 屬性內,如此,就可以在螢幕上看到該輸入字數數量了。
public string EntryChangedInputText { get; set; }
public int EntryChangedInputTextLength { get; set; }
public void OnEntryChangedInputTextChanged()
{
if(string.IsNullOrEmpty(EntryChangedInputText))
{
EntryChangedInputTextLength = 0;
}
else
{
EntryChangedInputTextLength = EntryChangedInputText.Length;
}
}
使用 Prism 提供的事件 To 命令的行為擴充功能
Prism 這個 MVVM 開發框架,提供了一個 Xamarin.Forms Behavior 行為的物件,其中一個加值行為就是可以設定當某個事件被觸發的時候,需要執行某個命令;如此,使用這個方法將會把相關商業處理邏輯寫道 ViewModel 上,而不在需要撰寫 Code Behind 程式碼了。
首先,請先在該 ContentPage 節點上宣告一個新的命名空間
xmlns:behavior="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
,這裡將會宣告一個前置詞 Prefix , behavior ,指向 Prims 行為擴充功能組件上,現在,可以在這個頁面上,加入一個 Label 與 Entry,其中 Entry 需要使用 Event.Behaviors 的屬性項目 Property Element 方式,指定新加入的行為物件;這裡將會使用 behavior:EventToCommandBehavior 這個型別,來使用 EventName 這個屬性來指定觸發事件的名稱與 Command 來指定要綁定到 ViewModel 中的一個 DelegateCommand 屬性物件。 <Label Text="{Binding EntryBehaviorInputTextLength}"/>
<Entry
Text="{Binding EntryBehaviorInputText}">
<Entry.Behaviors>
<behavior:EventToCommandBehavior
EventName="TextChanged" Command="{Binding EntryChangedCommand}"/>
</Entry.Behaviors>
</Entry>
在 ViewModel 內,會在建構函式內建立一個 DelegateCommand 型別的物件到 EntryChangedCommand 屬性上,在建立這個 EntryChangedCommand 屬性的時候,將會傳入一個委派方法,這個委派方法將會用來轉寫計算使用者輸入字數的相關程式碼。
public string EntryBehaviorInputText { get; set; }
public int EntryBehaviorInputTextLength { get; set; }
public DelegateCommand EntryChangedCommand { get; set; }
public MainPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
EntryChangedCommand = new DelegateCommand(() =>
{
EntryBehaviorInputTextLength = EntryBehaviorInputText is null ? 0 : EntryBehaviorInputText.Length;
});
}
使用附加屬性來計算使用輸入的文字數量
對於想要針對某個控制項加入可以綁定的屬性,讓 ViewModel 可以根據 View 的屬性異動,進行相關的商業邏輯處理,第一個就是使用原來檢視上的屬性來做處理,若有特殊需求,需要額外的屬性,可以建立一個新類別,繼承原來使用的檢視類別,就可以在這個新控制項中加入一個新的可綁定屬性;第二個方式,就是使用附加屬性,針對想要使用該附加屬性來附加這些附加屬性。
最後一個,就是要建立兩個附加屬性,第一個是 EnableCharCount ,這是用來設定是否要啟用自動計算字數的附加屬性,第二個是 EditorInputTextLength 用來儲存使用者輸入字數的數量值,底下為設計出來的附加屬性 XAML 用法。
由於等下要設計的附加屬性將會限制這兩個附加屬性僅能夠套用到 Entry 或者 Editor 上,當這兩個附加屬性附加到其他檢視的時候,將不會有任何效果的。
這裡有一點要特別注意,對於 CharCountAttachedProperty.CharNumber 這個附加屬性需要在資料綁定延伸標記語法內,要使用
Mode=TwoWay
,這樣才會形成雙向綁定運作方式。 <Label Text="{Binding EditorInputTextLength}"/>
<Label Text="{Binding EntryInputTextLength}"/>
<Editor
Text="{Binding EditorInputText}"
AutoSize="TextChanges"
local:CharCountAttachedProperty.EnableCharCount="True"
local:CharCountAttachedProperty.CharNumber="{Binding EditorInputTextLength, Mode=TwoWay}"
/>
<Entry
Text="{Binding EntryInputText}"
local:CharCountAttachedProperty.EnableCharCount="True"
local:CharCountAttachedProperty.CharNumber="{Binding EntryInputTextLength, Mode=TwoWay}"/>
現在,可以建立一個類別,在此類別上建立兩個附加屬性,在此,建立的類別名稱為 CharCountAttachedProperty,接著,在此類別內,使用程式碼片段
xfAttachedProperty
自動產生附加屬性的相關程式碼。
第一個附加屬性為 EnableCharCountProperty ,在這裡附加屬性將會用來控制是否要進行輸入文字內容的計算工作,在呼叫 BindableProperty.CreateAttached 方法的時候,將會使用 declaringType 來指定這個附加屬性可以附加到那些 XAML 項目上,這裡指定的是 typeof(View),代表任何使用者控制項都可以使用這個附加屬性。
在 EnableCharCountProperty 內最為重要的工作那就是要定義 propertyChanged 這個參數值,傳入一個委派方法 OnEnableCharCountChanged,在此委派方法終將會使用
if (bindable is Entry || bindable is Editor)
來檢查現在該附加屬性是否用在 Entry 或者 Editor 這兩個檢視上,若不是的話,則不會做任何處理。所附加的檢視為 Entry 或者 Editor ,將會檢查是否要啟用這個自動計算字數的屬性值,若為 True,則會訂閱 Entry.TextChanged 事件或者 Editor.TextChanged 事件,否則若為 False,則會取消 TextChanged 的事件訂閱。記得,當訂閱視覺項目的事件的時候,一定要規劃解除訂閱事件的程式碼,否則,會有可能產生 記憶體遺失 Memory Leak 的問題。
在 OnEntryTextChanged 方法內,將會進行該自動計算使用者輸入文字的字數計算,將結果儲存到 CharNumberProperty 這個附加屬性上。
最後,還是使用相同的程式碼片段,
xfAttachedProperty
自動建立 CharNumberProperty 附加屬性程式碼,而在這個 CharNumberProperty 附加屬性上,並不需要加入額外的其他城市碼。public class CharCountAttachedProperty
{
#region EnableCharCount 附加屬性 Attached Property
public static readonly BindableProperty EnableCharCountProperty =
BindableProperty.CreateAttached(
propertyName: "EnableCharCount", // 屬性名稱
returnType: typeof(bool), // 回傳類型
declaringType: typeof(View), // 宣告類型
defaultValue: false, // 預設值
propertyChanged: OnEnableCharCountChanged // 屬性值異動時,要執行的事件委派方法
);
public static void SetEnableCharCount(BindableObject bindable, bool entryType)
{
bindable.SetValue(EnableCharCountProperty, entryType);
}
public static bool GetEnableCharCount(BindableObject bindable)
{
return (bool)bindable.GetValue(EnableCharCountProperty);
}
private static void OnEnableCharCountChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is Entry || bindable is Editor)
{
bool isEnable = (bool)newValue;
if (isEnable)
{
if (bindable is Entry)
{
(bindable as Entry).TextChanged += OnEntryTextChanged;
}
else if (bindable is Editor)
{
(bindable as Editor).TextChanged += OnEntryTextChanged;
}
}
else
{
if (bindable is Entry)
{
(bindable as Entry).TextChanged -= OnEntryTextChanged;
}
else if (bindable is Editor)
{
(bindable as Editor).TextChanged -= OnEntryTextChanged;
}
}
}
else
{
}
}
#endregion
private static void OnEntryTextChanged(object sender, TextChangedEventArgs e)
{
int foo = CharCountAttachedProperty.GetCharNumber(sender as BindableObject);
int length = e.NewTextValue.Length;
CharCountAttachedProperty.SetCharNumber(sender as BindableObject, length);
(sender as BindableObject).SetValue(CharNumberProperty, length);
int foo2 = CharCountAttachedProperty.GetCharNumber(sender as BindableObject);
}
#region CharNumber 附加屬性 Attached Property
public static readonly BindableProperty CharNumberProperty =
BindableProperty.CreateAttached(
propertyName: "CharNumber", // 屬性名稱
returnType: typeof(int), // 回傳類型
declaringType: typeof(View), // 宣告類型
defaultValue: 0, // 預設值
propertyChanged: OnCharNumberChanged // 屬性值異動時,要執行的事件委派方法
);
public static void SetCharNumber(BindableObject bindable, int entryType)
{
bindable.SetValue(CharNumberProperty, entryType);
}
public static int GetCharNumber(BindableObject bindable)
{
return (int)bindable.GetValue(CharNumberProperty);
}
private static void OnCharNumberChanged(BindableObject bindable, object oldValue, object newValue)
{
}
#endregion
}