5부.Part 5. 데이터 바인딩에서 MVVM까지From Data Bindings to MVVM

샘플 다운로드 샘플 다운로드Download Sample Download the sample

MVVM (모델 뷰-ViewModel) 아키텍처 패턴은 XAML을 염두에 두어야 합니다. 패턴은 세 가지 소프트웨어 계층 (XAML 사용자 인터페이스 (뷰 라고 함)을 구분 합니다. 모델 이라고 하는 기본 데이터 ViewModel 이라고 하는 뷰와 모델 간의 중개자입니다. 뷰와 ViewModel은 일반적으로 XAML 파일에 정의 된 데이터 바인딩을 통해 연결 됩니다. 뷰의 BindingContext는 일반적으로 ViewModel의 인스턴스입니다.The Model-View-ViewModel (MVVM) architectural pattern was invented with XAML in mind. The pattern enforces a separation between three software layers — the XAML user interface, called the View; the underlying data, called the Model; and an intermediary between the View and the Model, called the ViewModel. The View and the ViewModel are often connected through data bindings defined in the XAML file. The BindingContext for the View is usually an instance of the ViewModel.

간단한 ViewModelA Simple ViewModel

ViewModels을 소개 하 고, 먼저 프로그램이 없는 프로그램을 살펴보겠습니다.As an introduction to ViewModels, let’s first look at a program without one. 이전에는 XAML 파일이 다른 어셈블리의 클래스를 참조할 수 있도록 새 XML 네임 스페이스 선언을 정의 하는 방법을 살펴보았습니다.Earlier you saw how to define a new XML namespace declaration to allow a XAML file to reference classes in other assemblies. System 네임 스페이스에 대 한 XML 네임 스페이스 선언을 정의 하는 프로그램은 다음과 같습니다.Here’s a program that defines an XML namespace declaration for the System namespace:

xmlns:sys="clr-namespace:System;assembly=mscorlib"

프로그램은 x:Static를 사용 하 여 정적 DateTime.Now 속성에서 현재 날짜 및 시간을 가져오고 해당 DateTime 값을 StackLayoutBindingContext로 설정할 수 있습니다.The program can use x:Static to obtain the current date and time from the static DateTime.Now property and set that DateTime value to the BindingContext on a StackLayout:

<StackLayout BindingContext="{x:Static sys:DateTime.Now}" …>

BindingContext은 특수 한 속성입니다. 요소에 대 한 BindingContext를 설정 하면 해당 요소의 모든 자식에 의해 상속 됩니다.BindingContext is a special property: When you set the BindingContext on an element, it is inherited by all the children of that element. 즉, StackLayout의 모든 자식은이 BindingContext동일 하 고 해당 개체의 속성에 대 한 단순 바인딩을 포함할 수 있습니다.This means that all the children of the StackLayout have this same BindingContext, and they can contain simple bindings to properties of that object.

일회성 DateTime 프로그램에서 두 개의 자식에는 해당 DateTime 값의 속성에 대 한 바인딩이 포함 되어 있지만 두 개의 다른 자식에는 바인딩 경로가 누락 된 것으로 보이는 바인딩이 포함 되어 있습니다.In the One-Shot DateTime program, two of the children contain bindings to properties of that DateTime value, but two other children contain bindings that seem to be missing a binding path. 이는 DateTime 값 자체가 StringFormat에 사용 됨을 의미 합니다.This means that the DateTime value itself is used for the StringFormat:

<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>

문제는 페이지를 처음 빌드할 때 날짜와 시간을 한 번 설정 하 고 변경 하지 않는다는 것입니다.The problem is that the date and time are set once when the page is first built, and never change:

XAML 파일에는 항상 현재 시간을 표시 하는 클록이 표시 될 수 있지만 도움이 되는 코드가 필요 합니다. MVVM 측면에서 생각할 때 모델 및 ViewModel은 완전히 코드로 작성 된 클래스입니다.A XAML file can display a clock that always shows the current time, but it needs some code to help out. When thinking in terms of MVVM, the Model and ViewModel are classes written entirely in code. 뷰는 대개 데이터 바인딩을 통해 ViewModel에 정의 된 속성을 참조 하는 XAML 파일입니다.The View is often a XAML file that references properties defined in the ViewModel through data bindings.

적절 한 모델은 ViewModel을 무시 하 고 적절 한 ViewModel은 뷰를 무시 합니다.A proper Model is ignorant of the ViewModel, and a proper ViewModel is ignorant of the View. 그러나 프로그래머는 ViewModel에서 노출 하는 데이터 형식을 특정 사용자 인터페이스와 관련 된 데이터 형식으로 맞춤화 하기도 합니다.However, often a programmer tailors the data types exposed by the ViewModel to the data types associated with particular user interfaces. 예를 들어 모델에서 8 비트 문자 ASCII 문자열을 포함 하는 데이터베이스에 액세스 하는 경우 ViewModel은 사용자 인터페이스에서 유니코드의 단독 사용을 수용 하기 위해 이러한 문자열을 유니코드 문자열로 변환 해야 합니다.For example, if a Model accesses a database that contains 8-bit character ASCII strings, the ViewModel would need to convert between those strings to Unicode strings to accommodate the exclusive use of Unicode in the user interface.

MVVM의 간단한 예 (예: 여기에 표시 된 것)에서는 모델이 전혀 없는 경우가 많으며 패턴에는 데이터 바인딩과 연결 된 뷰 및 ViewModel만 포함 됩니다.In simple examples of MVVM (such as those shown here), often there is no Model at all, and the pattern involves just a View and ViewModel linked with data bindings.

다음은 DateTime라는 단일 속성만 있는 clock의 ViewModel 이며,이는 1 초 마다 DateTime 속성을 업데이트 합니다.Here’s a ViewModel for a clock with just a single property named DateTime, which updates that DateTime property every second:

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;
            }
        }
    }
}

ViewModels은 일반적으로 INotifyPropertyChanged 인터페이스를 구현 합니다. 즉, 해당 속성 중 하나가 변경 될 때마다 클래스에서 PropertyChanged 이벤트를 발생 시킵니다.ViewModels generally implement the INotifyPropertyChanged interface, which means that the class fires a PropertyChanged event whenever one of its properties changes. Xamarin.ios의 데이터 바인딩 메커니즘은이 PropertyChanged 이벤트에 처리기를 연결 하 여 속성이 변경 되 고 대상이 새 값으로 업데이트 된 상태로 유지 될 때이를 알릴 수 있도록 합니다.The data binding mechanism in Xamarin.Forms attaches a handler to this PropertyChanged event so it can be notified when a property changes and keep the target updated with the new value.

이 ViewModel을 기반으로 하는 clock은 다음과 같이 간단할 수 있습니다.A clock based on this ViewModel can be as simple as this:

<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>

ClockViewModel 속성 요소 태그를 사용 하 여 Label BindingContext 설정 되는 방법을 확인 합니다.Notice how the ClockViewModel is set to the BindingContext of the Label using property element tags. 또는 Resources 컬렉션에서 ClockViewModel을 인스턴스화하고 StaticResource 태그 확장을 통해 BindingContext로 설정할 수 있습니다.Alternatively, you can instantiate the ClockViewModel in a Resources collection and set it to the BindingContext via a StaticResource markup extension. 또는 코드 숨겨진 파일이 ViewModel을 인스턴스화할 수 있습니다.Or, the code-behind file can instantiate the ViewModel.

LabelText 속성에 Binding 태그 확장은 DateTime 속성을 지정 합니다.The Binding markup extension on the Text property of the Label formats the DateTime property. 표시 되는는 다음과 같습니다.Here’s the display:

속성을 마침표로 구분 하 여 ViewModel의 DateTime 속성에 대 한 개별 속성에 액세스할 수도 있습니다.It’s also possible to access individual properties of the DateTime property of the ViewModel by separating the properties with periods:

<Label Text="{Binding DateTime.Second, StringFormat='{0}'}" … >

대화형 MVVMInteractive MVVM

MVVM는 기본 데이터 모델을 기반으로 하는 대화형 보기에 양방향 데이터 바인딩과 함께 사용 되는 경우가 많습니다.MVVM is often used with two-way data bindings for an interactive view based on an underlying data model.

다음은 Color 값을 Hue, SaturationLuminosity 값으로 변환 하는 HslViewModel 라는 클래스 이며 그 반대의 경우도 마찬가지입니다.Here’s a class named HslViewModel that converts a Color value into Hue, Saturation, and Luminosity values, and vice versa:

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");

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

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

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Hue, SaturationLuminosity 속성을 변경 하면 Color 속성이 변경 되 고 Color에 대 한 변경으로 인해 다른 세 가지 속성이 변경 됩니다.Changes to the Hue, Saturation, and Luminosity properties cause the Color property to change, and changes to Color causes the other three properties to change. 속성이 변경 되지 않은 경우 클래스가 PropertyChanged 이벤트를 호출 하지 않는다는 점을 제외 하면 무한 루프 처럼 보일 수 있습니다.This might seem like an infinite loop, except that the class doesn't invoke the PropertyChanged event unless the property has changed. 이렇게 하면 제어할 수 없는 피드백 루프에 끝이 배치 됩니다.This puts an end to the otherwise uncontrollable feedback loop.

다음 XAML 파일에는 Color 속성이 ViewModel의 Color 속성에 바인딩된 BoxViewLabel, HueSaturation속성에 바인딩된 3 개의 Slider 및 세 개의 Luminosity 뷰가 포함 되어 있습니다.The following XAML file contains a BoxView whose Color property is bound to the Color property of the ViewModel, and three Slider and three Label views bound to the Hue, Saturation, and Luminosity properties:

<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에 대 한 바인딩은 기본 OneWay입니다.The binding on each Label is the default OneWay. 값을 표시 하기만 하면 됩니다.It only needs to display the value. 하지만 각 Slider에 대 한 바인딩은 TwoWay됩니다.But the binding on each Slider is TwoWay. 이를 통해 Slider를 ViewModel에서 초기화할 수 있습니다.This allows the Slider to be initialized from the ViewModel. ViewModel을 인스턴스화할 때 Color 속성이 Aqua로 설정 됩니다.Notice that the Color property is set to Aqua when the ViewModel is instantiated. 그러나 Slider를 변경 하는 경우 ViewModel의 속성에 새 값을 설정 하 여 새 색을 계산 해야 합니다.But a change in the Slider also needs to set a new value for the property in the ViewModel, which then calculates a new color.

ViewModels 사용 하 여 명령Commanding with ViewModels

대부분의 경우 MVVM 패턴은 데이터 항목을 조작 하는 것으로 제한 됩니다. 사용자 인터페이스 개체는 ViewModel의 병렬 데이터 개체 보기에 있습니다.In many cases, the MVVM pattern is restricted to the manipulation of data items: User-interface objects in the View parallel data objects in the ViewModel.

그러나 뷰에서 여러 작업을 트리거하는 단추를 뷰에 포함 해야 하는 경우도 있습니다.However, sometimes the View needs to contain buttons that trigger various actions in the ViewModel. 그러나 viewmodel은 특정 사용자 인터페이스 패러다임에 ViewModel을 연결 하므로 단추에 대 한 Clicked 처리기를 포함 해서는 안 됩니다.But the ViewModel must not contain Clicked handlers for the buttons because that would tie the ViewModel to a particular user-interface paradigm.

ViewModels을 특정 사용자 인터페이스 개체와 독립적으로 사용할 수 있지만 ViewModel 내에서 메서드를 호출할 수 있도록 허용 하려면 명령 인터페이스가 있습니다.To allow ViewModels to be more independent of particular user interface objects but still allow methods to be called within the ViewModel, a command interface exists. 이 명령 인터페이스는 Xamarin.ios의 다음 요소에서 지원 됩니다.This command interface is supported by the following elements in Xamarin.Forms:

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

SearchBarListView 요소를 제외 하 고, 이러한 요소는 두 가지 속성을 정의 합니다.With the exception of the SearchBar and ListView element, these elements define two properties:

  • 형식의 Command System.Windows.Input.ICommandCommand of type System.Windows.Input.ICommand
  • 형식의 CommandParameter ObjectCommandParameter of type Object

SearchBarSearchCommandSearchCommandParameter 속성을 정의 하 고 ListViewRefreshCommand 형식의 ICommand속성을 정의 합니다.The SearchBar defines SearchCommand and SearchCommandParameter properties, while the ListView defines a RefreshCommand property of type ICommand.

ICommand 인터페이스는 두 가지 메서드와 하나의 이벤트를 정의 합니다.The ICommand interface defines two methods and one event:

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

ViewModel은 ICommand형식의 속성을 정의할 수 있습니다.The ViewModel can define properties of type ICommand. 그런 다음 이러한 속성을 각 Button 또는 다른 요소의 Command 속성 또는이 인터페이스를 구현 하는 사용자 지정 보기에 바인딩할 수 있습니다.You can then bind these properties to the Command property of each Button or other element, or perhaps a custom view that implements this interface. 선택적으로 CommandParameter 속성을 설정 하 여이 ViewModel 속성에 바인딩된 개별 Button 개체 또는 다른 요소를 식별할 수 있습니다.You can optionally set the CommandParameter property to identify individual Button objects (or other elements) that are bound to this ViewModel property. 내부적으로 Button는 사용자가 Button를 탭 할 때마다 Execute 메서드를 호출 하 여 CommandParameterExecute 메서드에 전달 합니다.Internally, the Button calls the Execute method whenever the user taps the Button, passing to the Execute method its CommandParameter.

CanExecute 메서드와 CanExecuteChanged 이벤트는 Button 탭이 현재 유효 하지 않을 수 있는 경우에 사용 되며,이 경우 Button 자체를 사용 하지 않도록 설정 해야 합니다.The CanExecute method and CanExecuteChanged event are used for cases where a Button tap might be currently invalid, in which case the Button should disable itself. ButtonCommand 속성이 처음으로 설정 될 때와 CanExecuteChanged 이벤트가 발생 될 때마다 CanExecute를 호출 합니다.The Button calls CanExecute when the Command property is first set and whenever the CanExecuteChanged event is fired. CanExecute에서 false를 반환 하는 경우 Button 자체를 사용 하지 않도록 설정 하 고 Execute 호출을 생성 하지 않습니다.If CanExecute returns false, the Button disables itself and doesn’t generate Execute calls.

ViewModels 명령 추가에 대 한 도움말을 보려면 Xamarin.ios는 ICommand을 구현 하는 두 개의 클래스를 정의 합니다 Command<T> Command. 여기서 TExecuteCanExecute에 대 한 인수의 형식입니다.For help with adding commanding to your ViewModels, Xamarin.Forms defines two classes that implement ICommand: Command and Command<T> where T is the type of the arguments to Execute and CanExecute. 이러한 두 클래스는 여러 생성자를 정의 하 고 Command 개체가 CanExecuteChanged 이벤트를 발생 시키기 위해 ViewModel이 호출할 수 있는 ChangeCanExecute 메서드를 정의 합니다.These two classes define several constructors plus a ChangeCanExecute method that the ViewModel can call to force the Command object to fire the CanExecuteChanged event.

전화 번호를 입력 하기 위한 간단한 키패드의 ViewModel은 다음과 같습니다.Here is a ViewModel for a simple keypad that is intended for entering telephone numbers. ExecuteCanExecute 메서드는 생성자에서 바로 람다 함수로 정의 됩니다.Notice that the Execute and CanExecute method are defined as lambda functions right in the constructor:

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()
        {
            AddCharCommand = new Command<string>((key) =>
                {
                    // Add the key to the input string.
                    InputString += key;
                });

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

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

                    // Perhaps the delete button must be enabled/disabled.
                    ((Command)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)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

이 ViewModel은 AddCharCommand 속성이 여러 단추 (또는 명령 인터페이스를 포함 하는 다른 모든 항목)의 Command 속성에 바인딩되어 있다고 가정 합니다. 각 단추는 CommandParameter으로 식별 됩니다.This ViewModel assumes that the AddCharCommand property is bound to the Command property of several buttons (or anything else that has a command interface), each of which is identified by the CommandParameter. 이러한 단추는 InputString 속성에 문자를 추가 합니다. 그러면 DisplayText 속성의 전화 번호로 형식이 지정 됩니다.These buttons add characters to an InputString property, which is then formatted as a phone number for the DisplayText property.

또한 DeleteCharCommand이라는 ICommand 형식의 두 번째 속성이 있습니다.There is also a second property of type ICommand named DeleteCharCommand. 이는 배경 간격 단추에 바인딩되므로 삭제할 문자가 없으면 단추를 사용할 수 없습니다.This is bound to a back-spacing button, but the button should be disabled if there are no characters to delete.

다음 키패드는 다음과 같이 시각적으로 복잡 하지 않습니다.The following keypad is not as visually sophisticated as it could be. 대신 명령 인터페이스 사용을 보다 명확 하 게 보여 주기 위해 태그가 최소한으로 줄었습니다.Instead, the markup has been reduced to a minimum to demonstrate more clearly the use of the command interface:

<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="80" />
            <ColumnDefinition Width="80" />
            <ColumnDefinition Width="80" />
        </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="&#x21E6;"
                    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>

이 태그에 표시 되는 첫 번째 ButtonCommand 속성은 DeleteCharCommand에 바인딩되어 있습니다. 나머지는 Button 면에 표시 되는 문자와 동일한 CommandParameter를 사용 하 여 AddCharCommand에 바인딩됩니다.The Command property of the first Button that appears in this markup is bound to the DeleteCharCommand; the rest are bound to the AddCharCommand with a CommandParameter that is the same as the character that appears on the Button face. 작동 중인 프로그램은 다음과 같습니다.Here’s the program in action:

비동기 메서드 호출Invoking Asynchronous Methods

명령에서 비동기 메서드를 호출할 수도 있습니다.Commands can also invoke asynchronous methods. 이는 Execute 메서드를 지정할 때 asyncawait 키워드를 사용 하 여 수행 됩니다.This is achieved by using the async and await keywords when specifying the Execute method:

DownloadCommand = new Command (async () => await DownloadAsync ());

이는 DownloadAsync 메서드가 Task 이며 대기 되어야 함을 나타냅니다.This indicates that the DownloadAsync method is a Task and should be awaited:

async Task DownloadAsync ()
{
    await Task.Run (() => Download ());
}

void Download ()
{
    ...
}

탐색 메뉴 구현Implementing a Navigation Menu

이 문서 시리즈의 모든 소스 코드를 포함 하는 Xamlsamples 프로그램은 홈 페이지에 ViewModel을 사용 합니다.The XamlSamples program that contains all the source code in this series of articles uses a ViewModel for its home page. 이 ViewModel은 각 샘플 페이지의 형식, 제목 및 간단한 설명을 포함 하는 Type, TitleDescription 라는 세 개의 속성을 포함 하는 간단한 클래스의 정의입니다.This ViewModel is a definition of a short class with three properties named Type, Title, and Description that contain the type of each of the sample pages, a title, and a short description. 또한 ViewModel은 프로그램에 있는 모든 페이지의 컬렉션인 All 이라는 정적 속성을 정의 합니다.In addition, the ViewModel defines a static property named All that is a collection of all the pages in the program:

public class PageDataViewModel
{
    public PageDataViewModel(Type type, string title, string description)
    {
        Type = type;
        Title = title;
        Description = description;
    }

    public Type Type { private set; get; }

    public string Title { private set; get; }

    public string Description { private set; get; }

    static PageDataViewModel()
    {
        All = new List<PageDataViewModel>
        {
            // Part 1. Getting Started with XAML
            new PageDataViewModel(typeof(HelloXamlPage), "Hello, XAML",
                                  "Display a Label with many properties set"),

            new PageDataViewModel(typeof(XamlPlusCodePage), "XAML + Code",
                                  "Interact with a Slider and Button"),

            // Part 2. Essential XAML Syntax
            new PageDataViewModel(typeof(GridDemoPage), "Grid Demo",
                                  "Explore XAML syntax with the Grid"),

            new PageDataViewModel(typeof(AbsoluteDemoPage), "Absolute Demo",
                                  "Explore XAML syntax with AbsoluteLayout"),

            // Part 3. XAML Markup Extensions
            new PageDataViewModel(typeof(SharedResourcesPage), "Shared Resources",
                                  "Using resource dictionaries to share resources"),

            new PageDataViewModel(typeof(StaticConstantsPage), "Static Constants",
                                  "Using the x:Static markup extensions"),

            new PageDataViewModel(typeof(RelativeLayoutPage), "Relative Layout",
                                  "Explore XAML markup extensions"),

            // Part 4. Data Binding Basics
            new PageDataViewModel(typeof(SliderBindingsPage), "Slider Bindings",
                                  "Bind properties of two views on the page"),

            new PageDataViewModel(typeof(SliderTransformsPage), "Slider Transforms",
                                  "Use Sliders with reverse bindings"),

            new PageDataViewModel(typeof(ListViewDemoPage), "ListView Demo",
                                  "Use a ListView with data bindings"),

            // Part 5. From Data Bindings to MVVM
            new PageDataViewModel(typeof(OneShotDateTimePage), "One-Shot DateTime",
                                  "Obtain the current DateTime and display it"),

            new PageDataViewModel(typeof(ClockPage), "Clock",
                                  "Dynamically display the current time"),

            new PageDataViewModel(typeof(HslColorScrollPage), "HSL Color Scroll",
                                  "Use a view model to select HSL colors"),

            new PageDataViewModel(typeof(KeypadPage), "Keypad",
                                  "Use a view model for numeric keypad logic")
        };
    }

    public static IList<PageDataViewModel> All { private set; get; }
}

MainPage에 대 한 XAML 파일은 ItemsSource 속성이 해당 All 속성으로 설정 되 고 각 페이지의 TextCellTitle 속성을 표시 하는 Description를 포함 하는 ListBox 정의 합니다.The XAML file for MainPage defines a ListBox whose ItemsSource property is set to that All property and which contains a TextCell for displaying the Title and Description properties of each page:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.MainPage"
             Padding="5, 0"
             Title="XAML Samples">

    <ListView ItemsSource="{x:Static local:PageDataViewModel.All}"
              ItemSelected="OnListViewItemSelected">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding Title}"
                          Detail="{Binding Description}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

페이지는 스크롤 가능한 목록으로 표시 됩니다.The pages are shown in a scrollable list:

사용자가 항목을 선택 하면 코드 숨김이 파일의 처리기가 트리거됩니다.The handler in the code-behind file is triggered when the user selects an item. 처리기는 ListBoxSelectedItem 속성을 다시 null 설정 하 고 선택 된 페이지를 인스턴스화하여 이동 합니다.The handler sets the SelectedItem property of the ListBox back to null and then instantiates the selected page and navigates to it:

private async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
{
    (sender as ListView).SelectedItem = null;

    if (args.SelectedItem != null)
    {
        PageDataViewModel pageData = args.SelectedItem as PageDataViewModel;
        Page page = (Page)Activator.CreateInstance(pageData.Type);
        await Navigation.PushAsync(page);
    }
}

비디오Video

Xamarin 진화 2016: MVVM 및 프리즘를 사용 하 여 간단한 구성Xamarin Evolve 2016: MVVM Made Simple with Xamarin.Forms and Prism

요약Summary

XAML은 특히 데이터 바인딩 및 MVVM를 사용 하는 경우 Xamarin.ios 응용 프로그램에서 사용자 인터페이스를 정의 하기 위한 강력한 도구입니다.XAML is a powerful tool for defining user interfaces in Xamarin.Forms applications, particularly when data-binding and MVVM are used. 결과적으로 코드의 모든 백그라운드 지원이 포함 된 사용자 인터페이스를 정리 하 고, 세련 되 고, 잠재적으로 표시할 수 있습니다.The result is a clean, elegant, and potentially toolable representation of a user interface with all the background support in code.

Channel 9YouTube에서 더 많은 Xamarin 비디오를 찾습니다.Find more Xamarin videos on Channel 9 and YouTube.