2016년 10월 26일 수요일

Xamarin.Forms XAML Basics Part 5, From Data Bindings to MVVM

Xamarin.Forms에서의 XAML 사용이 궁금해서 아래 링크를 보며 대충 필요한 것만 정리함.
https://developer.xamarin.com/guides/xamarin-forms/xaml/

Bindings to MVVM

: https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/

Model-View-ViewModel(MVVM) architectural pattern은 XAML과 함께 정의 되었음.
MVVM pattern은 XAML UI (the View)를 data (the Model)에서 분리하고 중간 매개 역할의 View and Model (the ViewModel)을 통해 접근하는 방법임.
View와 ViewModel은 XAML에 정의된 data binding을 통해서 연결되고 일반적으로 View의 BindingContext는 ViewModel의 instance로 볼 수 있다.

A Simple ViewModel
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/#A_Simple_ViewModel

먼저 System namespace를 사용하기 위해 다음과 같이 XML namespace를 정의 함.
xmlns:sys="clr-namespace:System;assembly=mscorlib"
현재 날짜와 시간을 위해 staic DateTime.Now property를 binding한다.
<StackLayout BindingContext="{x:Static sys:DateTime.Now}">
BindingContext는 좀 특별한 property라서 하위 자식들이 상속되어 StackLayout의 하위 자식들이 동일한 BindingContext를 사용할 수 있음.

<?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:sys="clr-namespace:System;assembly=mscorlib"
             x:Class="XamlSamples.OneShotDateTimePage"
             Title="One-Shot DateTime Page">

  <StackLayout BindingContext="{x:Static sys:DateTime.Now}"
               HorizontalOptions="Center"
               VerticalOptions="Center">

    <Label Text="{Binding Year, StringFormat='The year is {0}'}" />
    <Label Text="{Binding StringFormat='The month is {0:MMMM}'}" />
    <Label Text="{Binding Day, StringFormat='The day is {0}'}" />
    <Label Text="{Binding StringFormat='The time is {0:T}'}" />

  </StackLayout>
</ContentPage>
MVVM 단어를 생각해 보면 Model과 ViewModel은 class이므로 전반적으로 code로 짜여야 할 것 같아 보이고 View는 주로 XAML 파일로 구성되어 ViewModel의 property를 data-binding으로 참조할 것으로 유추할 수 있다.

잘 작성된 Model은 ViewModel를 모르고 잘 작성된 ViewModel은 View를 알 수 없어야 한다. 하지만 대부분의 개발자들은 ViewMode에서 data type을 노출하고 해당 data type은 UI와 밀접한 관게를 가지도록 구성한다. 예를 들면 Model은 ASCII 문자열을 가지고 있는 Database를 접근하고 ViewModel에서는 이 ASCII string을 UI에서 필요한 Unicode string으로 변환해야하는 상황을 들 수 있다. (??)

아래 예제에서는 Model이 없이 View와 ViewModel간의 data binding을 보여줄 것이다.
using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XamlSamples
{
    class ClockViewModel : INotifyPropertyChanged
    {
        DateTime dateTime;

        public event PropertyChangedEventHandler PropertyChanged;

        public ClockViewModel()
        {
            this.DateTime = DateTime.Now;

            Device.StartTimer(TimeSpan.FromSeconds(1), () =>
                {
                    this.DateTime = DateTime.Now;
                    return true;
                });
        }

        public DateTime DateTime
        {
            set
            {
                if (dateTime != value)
                {
                    dateTime = value;

                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this,
                            new PropertyChangedEventArgs("DateTime"));
                    }
                }
            }
            get
            {
                return dateTime;
            }
        }
    }
}
ViewModel은 일반적으로 INotifyPropertyChanged interface를 상속 받는다. 그리고 class에서는 property가 변경 될 때 마다 PropertyChanged event를 발생 시킨다. 그럼 Xamarin.Forms의 data binding 은 해당 event를 위해 handler를 붙여 변경사항을 추적하여 새로운 값으로 업데이트되도록 한다.

<?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:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.ClockPage"
             Title="Clock Page">

  <Label Text="{Binding DateTime,
                        StringFormat='{0:T}'}"
         FontSize="Large"
         HorizontalOptions="Center"
         VerticalOptions="Center">
    <Label.BindingContext>
      <local:ClockViewModel />
    </Label.BindingContext>
  </Label>
</ContentPage>
Lavel에서 BindingContext로 ClockViewModel을 지정하고 있다. 다른 방법으로 ClockViewModel을 Resource collection으로 명시 하여 StaticResource markup extension을 사용하여 BindingContext로 지정할 수 있고 아니면 code-behind file이 VieModel을 정의 할 수 도 있다.


Interactive MVVM
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/#Interactive_MVVM

MVVM은 주로 interactive view를 위해 two-way data binding과 함께 사용된다.
using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace XamlSamples
{
    public class HslViewModel : INotifyPropertyChanged
    {
        double hue, saturation, luminosity;
        Color color;

        public event PropertyChangedEventHandler PropertyChanged;

        public double Hue
        {
            set
            {
                if (hue != value)
                {
                    hue = value;
                    OnPropertyChanged("Hue");
                    SetNewColor();
                }
            }
            get
            {
                return hue;
            }
        }

        public double Saturation
        {
            set
            {
                if (saturation != value)
                {
                    saturation = value;
                    OnPropertyChanged("Saturation");
                    SetNewColor();
                }
            }
            get
            {
                return saturation;
            }
        }

        public double Luminosity
        {
            set
            {
                if (luminosity != value)
                {
                    luminosity = value;
                    OnPropertyChanged("Luminosity");
                    SetNewColor();
                }
            }
            get
            {
                return luminosity;
            }
        }

        public Color Color
        {
            set
            {
                if (color != value)
                {
                    color = value;
                    OnPropertyChanged("Color");

                    this.Hue = value.Hue;
                    this.Saturation = value.Saturation;
                    this.Luminosity = value.Luminosity;
                }
            }
            get
            {
                return color;
            }
        }

        void SetNewColor()
        {
            this.Color = Color.FromHsla(this.Hue,
                                        this.Saturation,
                                        this.Luminosity);
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

<?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:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.HslColorScrollPage"
             Title="HSL Color Scroll Page">
  <ContentPage.BindingContext>
    <local:HslViewModel Color="Aqua" />
  </ContentPage.BindingContext>

  <StackLayout Padding="10, 0">
    <BoxView Color="{Binding Color}"
             VerticalOptions="FillAndExpand" />

    <Label Text="{Binding Hue,
                      StringFormat='Hue = {0:F2}'}"
           HorizontalOptions="Center" />

    <Slider Value="{Binding Hue, Mode=TwoWay}" />

    <Label Text="{Binding Saturation,
                      StringFormat='Saturation = {0:F2}'}"
           HorizontalOptions="Center" />

    <Slider Value="{Binding Saturation, Mode=TwoWay}" />

    <Label Text="{Binding Luminosity,
                      StringFormat='Luminosity = {0:F2}'}"
           HorizontalOptions="Center" />

    <Slider Value="{Binding Luminosity, Mode=TwoWay}" />
  </StackLayout>
</ContentPage>
Label들은 값을 표시하기 때문에 기본적으로 binding 방법은 OneWay이다. 하지만 Slider는 초기값에 따라 Slider가 설정되어야 하므로 TwoWay binding을 해야 한다.


Commanding with ViewModels
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/#Commanding_with_ViewModels

많은 경우 MVVM pattern은 ViewModel의 View parallel data object이고 UI object인 data item을 직접 조작하는 것을 제한하고 있다.
종종 View는 다양한 동작들을 처리할 수 있는 button을 ViewModel의 button을 필요로할 수 있지만 ViewModel은 특정 UI와 강결합 될 수 있는 Clicked handler들을 포함해서는 안된다.
ViewModel에서 특정 UI object와 독립적이고 ViewModel에서 호출 할 수 있도록 하는 방법은 command interface를 사용하는 것이다. 이 command interface를 지원하는 Xamarin.Forms element들은 다음과 같다.


  • Button
  • MenuItem
  • ToolbarItem
  • SearchBar
  • TextCell (and hence also ImageCell )
  • ListView
  • TapGestureRecognizer

SearchBar, ListView element는 예외적으로 다음 두가지 property를 가진다.


  • Command of type System.Windows.Input.ICommand
  • CommandParameter of type Object

비슷하게 SearchBar는 SearchCommand와 SearchCommandParameter property들을 정의하고 있고 ListView는 ICommand type의 RefershCommand property를 정의하고 있다.

ICommand interface는 다음 두개의 method와 한개의 event를 정의하고 있다.


  • void Execute(object arg)
  • bool CanExecute(object arg)
  • event EventHandler CanExecuteChanged


ViewModel은 하나 이상의 ICommand type의 property들을 가지고 있다. 이 property들은 각 Button의 Command property에 바인딩되어 있다. CommandParameter property는 부가적으로 ViewModel property에 바인딩된 Button을 확인하기 위해 사용된다. Button은 사용자가 Button을 터치할 때 마다 CommandParameter와 함께 Execute를 호출한다.

CanExecute method와 CanExecuteChanged event는 Button이 사용가능한지 아닌지 확인할 때 사용된다. 초기에 Command property가 설정되고 CanExecuteChanged event가 발생될 때 Button은 CanExecute method를 호출한다.

ViewModel에 command를 추가할 때 ICommand: Command와 Command<T>를 구현해야 한다.These two classes define a bunch of constructors plus a ChangeCanExecute method that the ViewModel can call to force the Command object to fire the CanExecuteChanged event.

다음은 전화번호를 입력하기 위한 simple keypad의 ViewModel이다. 생성자에서 Execute와 CanExecute가 lambda 함수로 정의 된것을 볼 수 있다.
using System;
using System.ComponentModel;
using System.Windows.Input;
using Xamarin.Forms;

namespace XamlSamples
{
    class KeypadViewModel : INotifyPropertyChanged
    {
        string inputString = "";
        string displayText = "";
        char[] specialChars = { '*', '#' };

        public event PropertyChangedEventHandler PropertyChanged;

        // Constructor
        public KeypadViewModel()
        {
            this.AddCharCommand = new Command<string>((key) =>
                {
                    // Add the key to the input string.
                    this.InputString += key;
                });

            this.DeleteCharCommand = new Command((nothing) =>
                {
                    // Strip a character from the input string.
                    this.InputString = this.InputString.Substring(0,
                                        this.InputString.Length - 1);
                },
                (nothing) =>
                {
                    // Return true if there's something to delete.
                    return this.InputString.Length > 0;
                });
        }

        // Public properties
        public string InputString
        {
            protected set
            {
                if (inputString != value)
                {
                    inputString = value;
                    OnPropertyChanged("InputString");
                    this.DisplayText = FormatText(inputString);

                    // Perhaps the delete button must be enabled/disabled.
                    ((Command)this.DeleteCharCommand).ChangeCanExecute();
                }
            }

            get { return inputString; }
        }

        public string DisplayText
        {
            protected set
            {
                if (displayText != value)
                {
                    displayText = value;
                    OnPropertyChanged("DisplayText");
                }
            }
            get { return displayText; }
        }

        // ICommand implementations
        public ICommand AddCharCommand { protected set; get; }

        public ICommand DeleteCharCommand { protected set; get; }

        string FormatText(string str)
        {
            bool hasNonNumbers = str.IndexOfAny(specialChars) != -1;
            string formatted = str;

            if (hasNonNumbers || str.Length < 4 || str.Length > 10)
            {
            }
            else if (str.Length < 8)
            {
                formatted = String.Format("{0}-{1}",
                                          str.Substring(0, 3),
                                          str.Substring(3));
            }
            else
            {
                formatted = String.Format("({0}) {1}-{2}",
                                          str.Substring(0, 3),
                                          str.Substring(3, 3),
                                          str.Substring(6));
            }
            return formatted;
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
        }
    }
}

ViewModel은 AddCharCommand property가 여러 버튼의 Command propert로 binding되었고 각각이 CommandParameter로 구분됨을 알 수 있다. 이 버튼들은 displayText property 에 보여지는 전화번호 문자열인 InputString property에 문자를 추가한다.

두번째로 ICommand 형식인 DeleteCharCommand property가 있는데 한 문자씩 지우는 버튼이고 문자열이 없는 경우 disable 되어야 한다.

다음은 keypad를 구성하는 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:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.KeypadPage"
             Title="Keypad Page">

    <Grid HorizontalOptions="Center"
          VerticalOptions="Center">
      <Grid.BindingContext>
        <local:KeypadViewModel />
      </Grid.BindingContext>

      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>

      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>

      <!-- Internal Grid for top row of items -->
      <Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <Frame Grid.Column="0"
               OutlineColor="Accent">
          <Label Text="{Binding DisplayText}" />
        </Frame>

        <Button Text="⇦"
                Command="{Binding DeleteCharCommand}"
                Grid.Column="1"
                BorderWidth="0" />
      </Grid>

      <Button Text="1"
              Command="{Binding AddCharCommand}"
              CommandParameter="1"
              Grid.Row="1" Grid.Column="0" />

      <Button Text="2"
              Command="{Binding AddCharCommand}"
              CommandParameter="2"
              Grid.Row="1" Grid.Column="1" />

      <Button Text="3"
              Command="{Binding AddCharCommand}"
              CommandParameter="3"
              Grid.Row="1" Grid.Column="2" />

      <Button Text="4"
              Command="{Binding AddCharCommand}"
              CommandParameter="4"
              Grid.Row="2" Grid.Column="0" />

      <Button Text="5"
              Command="{Binding AddCharCommand}"
              CommandParameter="5"
              Grid.Row="2" Grid.Column="1" />

      <Button Text="6"
              Command="{Binding AddCharCommand}"
              CommandParameter="6"
              Grid.Row="2" Grid.Column="2" />

      <Button Text="7"
              Command="{Binding AddCharCommand}"
              CommandParameter="7"
              Grid.Row="3" Grid.Column="0" />

      <Button Text="8"
              Command="{Binding AddCharCommand}"
              CommandParameter="8"
              Grid.Row="3" Grid.Column="1" />

      <Button Text="9"
              Command="{Binding AddCharCommand}"
              CommandParameter="9"
              Grid.Row="3" Grid.Column="2" />

      <Button Text="*"
              Command="{Binding AddCharCommand}"
              CommandParameter="*"
              Grid.Row="4" Grid.Column="0" />

      <Button Text="0"
              Command="{Binding AddCharCommand}"
              CommandParameter="0"
              Grid.Row="4" Grid.Column="1" />

      <Button Text="#"
              Command="{Binding AddCharCommand}"
              CommandParameter="#"
              Grid.Row="4" Grid.Column="2" />
    </Grid>
</ContentPage>

첫 button의 Command property는 DeleteCharCommand에 바인딩되어 있고 나머지는 AddCharCommand에 각 버튼의 Text와 같은 CommandParameter과 함께 바인딩되어 있다.




Invoking Asynchronous Methods


Command들도 비동기적으로 호출 될 수 있다. 이는 Execute method를 정의할 때 async와 await를 사용해서 처리할 수 있다.
DownloadCommand = new Command (async () => await DownloadAsync ());

이는 DownloadAsync method가 Task이고 method 실행이 일시 정지 될수 있음(await)을 알려준다.
async Task DownloadAsync ()
{
  await Task.Run (() => Download ());
}

void Download ()
{
  ...
}















2016년 10월 10일 월요일

Xamarin.Forms XAML Basics Part 4, Data Binding Basics

Xamarin.Forms에서의 XAML 사용이 궁금해서 아래 링크를 보며 대충 필요한 것만 정리함.
https://developer.xamarin.com/guides/xamarin-forms/xaml/

Part 4. Data Binding Basics


Data binding은 source, target 두 객체의 property들을 연결한다. 

이를 위해 두단계의 과정이 필요하다.
먼저 target의 BidningContext property가 source로 지정되어야 하고
SetBinding method을 target에서 호출 하여
source의 property와 binding 해야 한다. (뭔소리임?)

즉 Target property는 bindable property 여야 하는데 이를 위해 target은 BindableObject를 상속 받아야 한다.

XAML에서는 Binding markup extension이 SetBinding call과 Binding class를 대신한다는 것을 제외 하고 동일핟.
다만 BindingContext를 지정하는 방법이 있는게 아니라 code-behind file(XAML의 cs파일)에서 지정 하는 방법이나 StaticResource, x:Static markup extension을 사용하는 방법, BindingContext property-element tag를 사용하는 방법을 사용할 수 있다.


View-to-View Bindings
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_binding_basics/#View-to-View_Bindings

동일 page 내에서 view들을 binding하고자 할 경우 target object에 BindingContext를 x:Reference markup extension을 사용하여 지정할 수 있다.

아래는 Slider와 두개의 Label가 binding 되어 있는 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"
             x:Class="XamlSamples.SliderBindingsPage"
             Title="Slider Bindings Page">

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

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

    <Label BindingContext="{x:Reference slider}"
          Text="{Binding Value,
                          StringFormat='The angle is {0:F0} degrees'}"
          FontAttributes="Bold"
          FontSize="Large"
          HorizontalOptions="Center"
          VerticalOptions="CenterAndExpand" />
  </StackLayout>
</ContentPage>

BindingContext에서 x:Refernce가 slider로만 되어 있는데 이는 대상 object의 x:Name이다.

BindingContext="{x:Reference Name=slider}"
BindingContext="{x:Reference slider}"

Binding markup extension은 BindingBase, Binding과 같은 여러 property를 가지고 있다.
Binding 의 ContentProperty로 지정된 proeprty는 Path이므로 Binding markup extension에서 처음 item일 경우 "Path=" 을 명시적으로 지정하지 않아도 된다.

Rotation="{Binding Path=Value}"
Text="{Binding Value,
               StringFormat='The angle is {0:F0} degrees'}"

함께 StringFormat이 사용되어 있는데 이는 Xamarin.Forms에서 implicit type conversions을 수행하지 않기 때문에 non-string인 Value 값을 string으로 변환하기 위해 사용되었다.

함께 알고 있어야 할 것은 StringFormat은 static String.Format method를 사용하는데 내부적으로 {}을 사용하고 있어 XAML parser에서의 혼동과 같은 문제를 야기할 수 있으므로 string formatting시 single quotation marks('')을 사용하여 표시해야 한다.

Text="{Binding Value,
               StringFormat='The angle is {0:F0} degrees'}"

Backwards Bindings
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_binding_basics/#Backwards_Bindings

하나의 view는 여러 property들에 대해서 data binding이 가능하다. 하지만 각 view는 하나의 BindingContext을 가지므로 multiple data binding 시 동일 object에 대해서 여러 reference를 가져야 한다.

이 같은 제약을 해결하기 위해서 종종 OneWayToSource나 TwoWay mode를 사용하여 view-to-view binding을 사용하기도 한다.

4개의 Slider를 사용하여 Label의 Scale, Rotate, RotateX, RotateY를 조절하고자 할 경우 Label의 BindingContext가 하나이므로 Label에서 각 Slider로 binding하는 것은 어렵다. 이러한 점을 회피하고자 binding을 반대로 Slider들의 BindingContext를 Label로 지정하고 Slider의 Value property를 binding하는 방법을 사용한다.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderTransformsPage"
             Title="Slider Transforms Page">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>

    <StackLayout Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">

      <!-- Scaled and rotated Label -->
      <Label x:Name="label"
             Text="TEXT"
             HorizontalOptions="Center"
             VerticalOptions="CenterAndExpand" />

    </StackLayout>

    <!-- Slider and identifying Label for Scale -->
    <Slider x:Name="scaleSlider"
            BindingContext="{x:Reference label}"
            Grid.Row="1" Grid.Column="1"
            Maximum="10"
            Value="{Binding Scale, Mode=TwoWay}" />

    <Label BindingContext="{x:Reference scaleSlider}"
           Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
           Grid.Row="1" Grid.Column="0"
           VerticalTextAlignment="Center" />

    <!-- Slider and identifying Label for Rotation -->
    <Slider x:Name="rotationSlider"
            BindingContext="{x:Reference label}"
            Grid.Row="2" Grid.Column="1"
            Maximum="360"
            Value="{Binding Rotation, Mode=OneWayToSource}" />

    <Label BindingContext="{x:Reference rotationSlider}"
           Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
           Grid.Row="2" Grid.Column="0"
           VerticalTextAlignment="Center" />

    <!-- Slider and identifying Label for RotationX -->
    <Slider x:Name="rotationXSlider"
            BindingContext="{x:Reference label}"
            Grid.Row="3" Grid.Column="1"
            Maximum="360"
            Value="{Binding RotationX, Mode=OneWayToSource}" />

    <Label BindingContext="{x:Reference rotationXSlider}"
           Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
           Grid.Row="3" Grid.Column="0"
           VerticalTextAlignment="Center" />

    <!-- Slider and identifying Label for RotationY -->
    <Slider x:Name="rotationYSlider"
            BindingContext="{x:Reference label}"
            Grid.Row="4" Grid.Column="1"
            Maximum="360"
            Value="{Binding RotationY, Mode=OneWayToSource}" />

    <Label BindingContext="{x:Reference rotationYSlider}"
           Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
           Grid.Row="4" Grid.Column="0"
           VerticalTextAlignment="Center" />
  </Grid>
</ContentPage>

초기 예제는 Label들에서 slider의 Value property로 binding 하였으나
Label  --Value--   slider    --Value--   Label

아래와 같이
label의 Scale, Rotation, RotationX, RotationY를 각 slider에서 binding하고
각 slider들의 Value property를 각 Label들에서 binding하는 형태이다.

label <--  Scale  -->   scaleSlider       --Value--   Label
      <--Rotation--     rotationSlider    --Value--   Label
      <--RotationX--   rotationXSlider   --Value--   Label
      <--RotationY--   rotationYSlider   --Value--   Label

Scale property는 Twoway로 binding되는데 이는 Scale property의 초기값이 1인 관계로 이를 scaleSlider에도 반영하기 위해서 이다. OneWayToSource로 binding하게 되면 Scale property는 Slider의 기본 값인 0으로 설정되어 Label이 보이지 않게 된다.

또한 명세에서 보면 column 0에 Label을 column 1에 Slider를 배치하였지만 작성 순서는 Slider를 먼저 그다음에 Label을 배치하였음 이유는 Slider의 값을 Label에서 OneWay binding 하고 있어 Slider가 우선 정의 되어야 Label에서 값을 참조할 수 있음.


Bindings and Collections
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_binding_basics/#Bindings_and_Collections


Templated ListView를 사용할 때 XAML과 data binding feature가 상당히 유용하다.
ListView는 IEnumerable를 구현하고 있는 ItemSource property를 가지고 있어 이를 item을 표시할 때 사용한다. ListView collection은 Cell을 상속한 template를 사용함으로 써 사용자가 원하는 형태로 보여줄 수 있다. Template는 ListView의 개별 item들을 위해 clone되고 각 clone들을 설정하기 위해 data를 binding 한다.

주로 Custom Cell을 만들기 위해 ViewCell class를 사용하여 coding하는 방법이 꽤나 지저분하지만 XAML에서는 상당히 간단하다.

아래 sample project에서는 NamedColor class를 include한다. NamedColor는 Name과 FriendlyName, color를 가지고 있고 내부적으로 static read-only color 값의 목록을 포함하고 있다.

<?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:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.ListViewDemoPage"
             Title="ListView Demo Page">

  <ListView ItemsSource="{x:Static local:NamedColor.All}" />

</ContentPage>

Item의 template을 지정하려면 ItemTemplate property에 ViewCell을 포함한 DataTemplate를 지정해야 한다.

<ListView ItemsSource="{x:Static local:NamedColor.All}">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <ViewCell.View>
            <Label Text="{Binding FriendlyName}" />
          </ViewCell.View>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>

좀 더 꾸미기 위해 page의 resource dictionary를 정의해서 사용할 수 있다.

<?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:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.ListViewDemoPage"
             Title="ListView Demo Page">

  <ContentPage.Resources>
    <ResourceDictionary>
      <OnPlatform x:Key="boxSize"
                  x:TypeArguments="x:Double"
                  iOS="50"
                  Android="50"
                  WinPhone="75" />

      <!-- This is only an issue on the iPhone; Android and
           WinPhone auto size the row height to the contents. -->
      <OnPlatform x:Key="rowHeight"
                  x:TypeArguments="x:Int32"
                  iOS="60"
                  Android="60"
                  WinPhone="85" />

      <local:DoubleToIntConverter x:Key="intConverter" />

    </ResourceDictionary>
  </ContentPage.Resources>

  <ListView ItemsSource="{x:Static local:NamedColorGroup.All}"
            RowHeight="{StaticResource rowHeight}">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <ViewCell.View>
            <StackLayout Padding="5, 5, 0, 5"
                         Orientation="Horizontal"
                         Spacing="15">

              <BoxView WidthRequest="{StaticResource boxSize}"
                       HeightRequest="{StaticResource boxSize}"
                       Color="{Binding Color}" />

              <StackLayout Padding="5, 0, 0, 0"
                           VerticalOptions="Center">

                <Label Text="{Binding FriendlyName}"
                       FontAttributes="Bold"
                       FontSize="Medium" />

                <StackLayout Orientation="Horizontal"
                             Spacing="0">
                  <Label Text="{Binding Color.R,
                                   Converter={StaticResource intConverter},
                                   ConverterParameter=255,
                                   StringFormat='R={0:X2}'}" />
                  <Label Text="{Binding Color.G,
                                   Converter={StaticResource intConverter},
                                   ConverterParameter=255,
                                   StringFormat=', G={0:X2}'}" />
                  <Label Text="{Binding Color.B,
                                   Converter={StaticResource intConverter},
                                   ConverterParameter=255,
                                   StringFormat=', B={0:X2}'}" />
                </StackLayout>
              </StackLayout>
            </StackLayout>
          </ViewCell.View>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

Xamarin.Forms의 Color의 type은 0-1값을 가지는 double형이므로 int형으로 형변환이 필요하고 또한 Anroid RGB color 범위에 맞춰서 값 변환이 필요하다.

이는 외부 binding converter를 통해서 가능하다.

using System;
using System.Globalization;
using Xamarin.Forms;

namespace XamlSamples
{
    class DoubleToIntConverter : IValueConverter
    {
        public object Convert(object value, Type targetType,
                              object parameter, CultureInfo culture)
        {
            double multiplier;

            if (!Double.TryParse(parameter as string, out multiplier))
                multiplier = 1;

            return (int)Math.Round(multiplier * (double)value);
        }

        public object ConvertBack(object value, Type targetType,
                                  object parameter, CultureInfo culture)
        {
            double divider;

            if (!Double.TryParse(parameter as string, out divider))
                divider = 1;

            return ((double)(int)value) / divider;
        }
    }
}

ListView의 item이 동적으로 변경되는 것을 처리하기 위해서는 INotifyCollectionChanged interface를 구현하고 있는 ObservableCollection을 사용해서 CollectionChanged event handler를 통해서 처리 가능하다.
또한 property들이 변경되는 것은 INotifyPropertyChanged interface를 구현해서 PropertyChanged event handler를 통해서 처리 가능함.