2017/5/23

Xamarin.Forms 教學系列文(十六.壹)Data Binding


學習目標
  • BindingContext 寫法
  • binding 物件寫法

這是個重量級的章節... 直接影響到第 18 章的 MVVM...雖然不用 MVVM 程式還是能動,但實際使用後,程式上的維護會方便很多 (少了參數傳遞及顯示處理)。

Events event handler 在 Xamarin 的互動介面中是很重要的一件事,但其實 參數傳遞 以及將 資料顯示於畫面,是一件相當繁瑣的事情。

舉例來說,今天我要將 Slider 的值顯示於 Label,必須控制 Slider 的 ValueChanged 方法,再取出 Slider 的值塞進 Label.Text,道道程序後才能完成這件事,相當繁瑣啊 !!!

有鑑於此,Xamairn 提供了一種強力的解決方案 -- Data Binding


能夠將 兩個物件的屬性 連結起來,不需要再 手動處理 物件之間的傳遞與顯示
data binding 一般來說都寫在 XAML 內。

DataBinding 最重要的概念就是 來源 (Source) 和 目標 (Target)
Target ← Source
來源 (Source) 是某個物件的屬性,其值改變時,同步去更新 目標 (Target) 屬性的值。


Binding basics

目標 (Target) 在 DataBinding 時其類別必須為 BindableProperty ObjectVisual Element 就是 BindableProperty Object

來源 (Source) 除了是 物件 外,也可以是 C# 屬性若為 C#  提供的來源,等值更改時要用 INotifyPropertyChanged 通知目標 (Target),也就是18 章要介紹的 MVVM

Binding 會用到的類別、屬性和方法:
  • 類別 - Binding
  • 屬性 - BindingContext
  • 方法 - SetBinding
  • 類別 - BindableObjectExtension

XAML 會用到的擴充:
  • BindingExtension
  • ReferenceExtension

介面:
  • INotifyPropertyChanged ( 定義於 System.ComponentModel namespace),用在屬性改變時發生通知。
  • IValueConverter (定義於 Xamarin.Forms namespace),屬性之間數值型態轉換用。

不要緊張~ 以下會一一介紹~

Code & XAML

DataBinding 的 來源 (Source) 寫法有兩種:
  1. 利用 BindingContext 屬性
  2. 利用 bingding 物件

-- BindingContext --

.cs 寫法:
來源 (Source) 為 Slider 的 Value,目標 (Target) 為 Lable 的 Opacity,當拖曳 Slider 時會去同步更改 Lable 的透明度
    public class OpacityBindingCodePage : ContentPage
    {
        public OpacityBindingCodePage()
        {
            Label label = new Label
            {
                Text = "Opacity Binding Demo",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.Center
            };

            Slider slider = new Slider
            {
                VerticalOptions = LayoutOptions.CenterAndExpand
            };

            // 建立 Target 的 BindingContext -- 將 Source 放在一個準備區
            label.BindingContext = slider;

            // 用 SetBinding 指定 Target 要更改的屬性 & 來源屬性名
            label.SetBinding(Label.OpacityProperty, "Value");

            // Construct the page. 
            Padding = new Thickness(10, 0);
            Content = new StackLayout
            {
                Children =
                {
                    label,
                    slider
                }
            };
        }
    }


執行結果:


XAML 寫法:
兩個新的擴充標籤出現!
  • x:Reference - 設定 BindingContext 
  • Binding - 綁定要更改的屬性
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="OpacityBindingXaml.OpacityBindingXamlPage" 
             Padding="10, 0">

    <StackLayout>
        <Label Text="Opacity Binding Demo" 
               FontSize="Large" 
               VerticalOptions="CenterAndExpand" 
               HorizontalOptions="Center" 
               BindingContext="{x:Reference Name=slider}" 
               Opacity="{Binding Path=Value}" />

        <Slider x:Name="slider" 
                VerticalOptions="CenterAndExpand" />

    </StackLayout>
</ContentPage>

可以將 XAML 縮短,省略 Name 和 Path
        <Label Text="Opacity Binding Demo" 
               FontSize="Large" 
               VerticalOptions="CenterAndExpand" 
               HorizontalOptions="Center" 
               BindingContext="{x:Reference slider}" 
               Opacity="{Binding Value}" />


-- binding 物件 --

.cs 寫法:
原理為先建立 binding 物件 (包含 Source 和 Path),再用 SetBinding() 綁入目標 (Target)。
    public class BindingSourceCodePage : ContentPage
    {
        public BindingSourceCodePage()
        {
            Label label = new Label
            {
                Text = "Opacity Binding Demo",
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.Center
            };

            Slider slider = new Slider
            {
                VerticalOptions = LayoutOptions.CenterAndExpand
            };

            // 設定 binding 物件,並初始化 Source 和 屬姓名
            Binding binding = new Binding { Source = slider, Path = "Value" };

            // 將此 binding 物件綁入 Target
            label.SetBinding(Label.OpacityProperty, binding);

            // Construct the page. 
            Padding = new Thickness(10, 0);
            Content = new StackLayout
            {
                Children = { label, slider }
            };
        }
    }

宣告 binding 物件還有以下三種方法:
1. 設定 Binding Mode (下一小節介紹 )
Binding binding = new Binding("Value", BindingMode.Default, null, null, null, slider);

2. 簡易寫法 (注意來源屬性放在前面 )
Binding binding = new Binding("Value", source: slider);

3. Create 泛型方法
Binding binding = Binding.Create<slider>(src => src.Value); 
binding.Source = slider;

XAML 寫法:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="BindingSourceXaml.BindingSourceXamlPage" 
             Padding="10, 0">

    <StackLayout>
        <Label Text="Binding Source Demo" 
               FontSize="Large" 
               VerticalOptions="CenterAndExpand" 
               HorizontalOptions="Center" 
               Opacity="{Binding Source={x:Reference Name=slider}, Path=Value}" />

        <Slider x:Name="slider" VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

寫法上可將 Name 省略:
Opacity="{Binding Source={x:Reference slider}, Path=Value}"


為什麼 來源 (Source) 會有兩種寫法?

最大的差異為
  • BindingContext 底下的 子項目 能 共享 同一個 Source舉例來說,在 StackLayout 內設定 BindingContext,所有 StackLayout 內的子項目都可以綁定這個 Source。

  • binding 物件可針對 Target 的 單一屬性 做 獨立 Binding ~


共用 BindingContext 範例:

本範例會用到 WebView,WebView 類別可以在 App 內瀏覽網頁,也可以顯示 HTML,有兩個屬性可以設定,UrlWebVIewSource HtmlWebViewSource

WebView 定義了兩個方法,GoBack GoForward,可以拿來實作上一頁下一頁的按鈕。

本範例的功能就是要控制 上下頁 的按鈕能否使用 (最後一頁時下一頁按鈕就不行按)

可借助 WebView 兩個屬性,CanGoBack CanGoForward 作為 Source,達到我們的目的 。

XAML:
*Oplatform 寫法已更改,請參考此篇文章
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="WebViewDemo.WebViewDemoPage">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" 
                    iOS="10, 20, 10, 0" 
                    Android="10, 0" 
                    WinPhone="10, 0" />
    </ContentPage.Padding>

    <StackLayout>
        <Entry Keyboard="Url" 
               Placeholder="web address" 
               Completed="OnEntryCompleted" />

        <!--BindingContext-->
        <StackLayout Orientation="Horizontal" 
                     BindingContext="{x:Reference webView}">

            <!--Binding CanGoBack-->
            <Button Text="&#x21D0;" 
                    FontSize="Large" 
                    HorizontalOptions="FillAndExpand" 
                    IsEnabled="{Binding CanGoBack}" 
                    Clicked="OnGoBackClicked" />

            <!--Binding CanGoForward-->
            <Button Text="&#x21D2;"
                    FontSize="Large" 
                    HorizontalOptions="FillAndExpand" 
                    IsEnabled="{Binding CanGoForward}"
                    Clicked="OnGoForwardClicked" />
        </StackLayout>

        <WebView x:Name="webView" 
                 VerticalOptions="FillAndExpand"
                 Source="https://xamarin.com" />
    </StackLayout>
</ContentPage>


.cs:
    public partial class WebViewDemoPage : ContentPage
    {
        public WebViewDemoPage()
        {
            InitializeComponent();
        }
        void OnEntryCompleted(object sender, EventArgs args)
        {
            webView.Source = ((Entry)sender).Text;
        }
        void OnGoBackClicked(object sender, EventArgs args)
        {
            webView.GoBack();
        }
        void OnGoForwardClicked(object sender, EventArgs args)
        {
            webView.GoForward();
        }
    }

執行結果:








沒有留言:

張貼留言