XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2016/10/16

Xamarin.Forms 客製化 Picker 控制項,使其具備可資料綁定能力

繼承原有控制項進行客製化,擴充原有控制項的功能與能力,是個相當重的議題,不過,這也屬於進階性的學習。

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式

當然,想要擴充原有的控制項可以有其他的選擇,例如,使用附加可綁定屬性、行為等等,在這份筆記中,將會描述如何擴充 Picker 這個控制項,使其具有可綁定的資料來源屬性、選取後的可綁定資料屬性;另外,也可以設定當有選取新的資料項目之後,可以執行所綁定的 Command 命令,這個命令當然也是支援可綁定屬性能力。這樣的話,所擴充出來的 Picker 控制項,就可以直些在 XAML 內進行宣告,與將相關商業處理邏輯寫在 ViewModel 內,也就是說,您不再需要透過 Code Behind 來做這些事情。
這份筆記的專案原始碼如下  

客製 Picker 控制項

首先,產生一個類別,使其繼承 Picker,其他的設定如底下程式碼,接下來說明這個新的類別做了甚麼事情。
  • 在 BindablePicker 類別內,產生了三個可綁定屬性,分別為 ItemsSource / SelectedItem / SelectedItemCommand
  • ItemsSource 可綁定屬性將會用來設定 Picker 要顯示的所有資料清單來源。
  • SelectedItem 這個可綁定屬性將會當使用者選取的新的資料項目之後,在 ViewModel 內可以透過這個可綁定屬性取得使用者選項的內容值。
  • SelectedItemCommand 可綁定屬性用於綁定當有新的項目被選取的時候,所要執行的 ICommand 命令,這個 ICommand 將會在 ViewModel 內實作出來。
  • 在 BindablePicker 建構式中,定義了這個 Picker 控制項的 SelectedIndexChanged 事件,也就是當這個事件被驅動的時候,就會根據當時所選取的 SelectedIndex 索引值,找到實際的項目內容值,並且設定到 SelectedItem。
  • 這個原始碼中,有許多關於可綁定屬性的定義,這部分可以參考可綁定屬性的做法相關說明。
  • 當 SelectedItem 的值有所變動的時候,會判斷當時是否有綁定 ICommand 物件,若有,則會使用SelectedItemCommand.Execute(value); 執行這個 ICommand 命令,其中,這個 ICommand 命令被呼叫的時候,會將當時選取的項目值,傳遞到 ICommand 內。
    public class BindablePicker : Picker
    {
        public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource",
                    typeof(IEnumerable), typeof(BindablePicker), null, propertyChanged: OnItemsSourceChanged);

        public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem",
                    typeof(IEnumerable), typeof(BindablePicker), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);

        public static readonly BindableProperty SelectedItemCommandProperty = BindableProperty.Create("SelectedItemCommand", 
            typeof(ICommand), typeof(BindablePicker), null);

        public BindablePicker()
        {
            SelectedIndexChanged += (o, e) =>
            {
                if (SelectedIndex < 0 || ItemsSource == null || !ItemsSource.GetEnumerator().MoveNext())
                {
                    SelectedItem = null;
                    return;
                }

                var index = 0;
                foreach (var item in ItemsSource)
                {
                    if (index == SelectedIndex)
                    {
                        SelectedItem = item;
                        break;
                    }
                    index++;
                }
            };
        }

        public ICommand SelectedItemCommand
        {
            get { return (ICommand)GetValue(SelectedItemCommandProperty); }
            set { SetValue(SelectedItemCommandProperty, value); }
        }

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public Object SelectedItem
        {
            get { return GetValue(SelectedItemProperty); }
            set
            {
                if (SelectedItem != value)
                {
                    SetValue(SelectedItemProperty, value);
                    InternalUpdateSelectedIndex();

                    if(SelectedItemCommand!=null)
                    {
                        SelectedItemCommand.Execute(value);
                    }
                }
            }
        }

        public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;

        private void InternalUpdateSelectedIndex()
        {
            var selectedIndex = -1;
            if (ItemsSource != null)
            {
                var index = 0;
                foreach (var item in ItemsSource)
                {
                    if (item != null && item.Equals(SelectedItem))
                    {
                        selectedIndex = index;
                        break;
                    }
                    index++;
                }
            }
            SelectedIndex = selectedIndex;
        }

        private static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var boundPicker = (BindablePicker)bindable;

            if (Equals(newValue, null) && !Equals(oldValue, null))
                return;

            boundPicker.Items.Clear();

            if (!Equals(newValue, null))
            {
                foreach (var item in (IEnumerable)newValue)
                    boundPicker.Items.Add(item.ToString());
            }

            boundPicker.InternalUpdateSelectedIndex();
        }

        private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var boundPicker = (BindablePicker)bindable;
            if (boundPicker.ItemSelected != null)
            {
                boundPicker.ItemSelected(boundPicker, new SelectedItemChangedEventArgs(newValue));
            }
            boundPicker.InternalUpdateSelectedIndex();
        }

    }

在 XAML 內使用新產生的 BindablePicker

想要使用這個新的自訂控制項,您需要宣告一個 XAML 命名空間,在底下,宣告了一個 xmlns:local="clr-namespace:PickerLab" 命名空間。
因此,您可以使用 ` 的方式在 XAML 宣告這個新控制項;在底下的例子中,將透過資料繫結,將上述三個新建立的可綁定屬性,綁定到 ViewModel 內的三個屬性。
例如, Picker 可以選擇的所有清單項目將會透過 ItemsSource="{Binding PickerVM}" 來取得,而使用者選擇完後的資料項目,將會透過 SelectedItem="{Binding PickerSelectedTitle}" 綁定到 ViewModel 內;而要在使用點不同資料之後,執行的 ICommand ,則使用 SelectedItemCommand="{Binding SelectedIndexChangedCommand}" 方式來宣告。
<?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:PickerLab"
             xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="PickerLab.Views.MainPage"
             Title="MainPage">
    <StackLayout HorizontalOptions="Fill" VerticalOptions="Center">
        <Label Text="{Binding Title}" HorizontalOptions="Center"/>
        <!--https://developer.xamarin.com/api/type/Xamarin.Forms.Picker/-->
        <local:BindablePicker 
            ItemsSource="{Binding PickerVM}"
            HorizontalOptions="Fill"
            HeightRequest="34"
            SelectedItem="{Binding PickerSelectedTitle}"
            SelectedItemCommand="{Binding SelectedIndexChangedCommand}">
        </local:BindablePicker>
        <Label Text="{Binding PickerSelectedTitle}" HorizontalOptions="Center"/>
    </StackLayout>
</ContentPage>

沒有留言:

張貼留言