MixedCode

안녕하세용. 믹스드코드입니다.
MVVM In Xamarin.Forms 기초편 - View 구성하기에 이어 ViewModel의 기본적인 내용에 대해 코딩을 통해 알아보도록 하겠습니다.

2.View Model 구성하기 - 기초편

1) 데이터 표시방식 
Case1 : 코드비하인드 방식
-코드비하인드에서 직접적으로 XAML 요소의 속성값을 표시할수 있습니다.
-코드 비하인드에서 XAML에 있는 특정요소의 속성에 데이터를 표시하기 위해서는 먼저 ~.xaml 뷰 페이지 안에 해당 요소에 x:Name 속성을 이용해 고유한 아이디를 지정해줘야합니다.

-in XAML(View)
<Label x:Name="lblTitle" Grid.Row="0" Grid.ColumnSpan="2" Text="User Information Manager"   FontAttributes="Bold" FontSize="Large"></Label>

in CodeBehind
-this.lblTitle.Text = "페이지 제목입니다.";

CASE2: 바인딩방식
-데이터를 표시하고 ViewModel과 상호 작용하는 UI 요소를 만드는 메커니즘입니다
-바인딩 또는 데이터 바인딩은 UI (XAML)와 데이터 개체(모델,뷰모델) 사이에 정보를 연결하는 메카니즘 및 프로세스를 제공합니다.
-UI요소값을 사용자가 변경했을때 또는 바인딩 되어 있는 데이터모델의 상태가 변경되면 자동으로 상호 알림을 발생시켜 상호 정보가 자동갱신되는 메카니즘이 제공됩니다. (별도의 이벤트 및 이벤트 핸들러 처리가 필요없어짐.)
-바인딩 방식을 사용하려면 뷰에 존재하는 XAML 요소 속성이 일반속성이 아닌 바인딩 가능한 속성으로 구현되어 있어야 하며 해당 속성과 바인딩 되는 뷰모델(모델) 클래스는 INotifyChanged을 상속받고
관련 모델의 속성에 PropertyChangedEvent가  등록되어 있어야합니다.

뷰모델을 하나 만들고 직접 관련 내용을 코딩해보도록 하겠습니다.

2) 뷰모델 생성하기
PCL 프로젝트 루트에 ViewModel 폴더를 하나 만들고 폴더내에 MainPage.xaml(뷰) 에서 사용할 MainPageViewModel.cs(뷰모델) 클래스를 하나 만듭니다.
그리고 해당 뷰모델 클래스 INotifyPropertyChanged 인터페이스를 상속받게 한후 해당 인터페이스에서 요구하는 PropertyChanged 이벤트 핸들러를 뷰모델에 구현합니다.





using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ComponentModel;

namespace XamarinMVVMDemo.ViewModel
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
    public MainPageViewModel()
        {
       
        }

        public event PropertyChangedEventHandler PropertyChanged;

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


3) 뷰모델 바인딩 속성 정의하기
-뷰모델에서 뷰(xaml)내의 특정요소의 바인딩가능한 속성과 바인딩하기 위한 뷰모델 데이터 속성을 정의합니다.
-뷰모델 생성자에서 정의된 데이터 속성의 기본값을 세팅해줍니다.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ComponentModel;

namespace XamarinMVVMDemo.ViewModel
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
    public MainPageViewModel()
    {
              this.UserID = "Eddy.Kang";
            this.UserName = "강창훈";
            this.Email = "admin@signalsoft.co.kr";
            this.Telephone = "010-2760-5246";
            this.RegistDate = DateTime.Now;
        }


        private string _UserID;
        public string UserID
        {
            get
            {
                return _UserID;
            }

            set
            {
                if (_UserID == value) return;
                _UserID = value;
                OnPropertyChanged("UserID");
            }
        }

        private string _UserName;
        public string UserName
        {
            get
            {
                return _UserName;
            }

            set
            {
                if (_UserName == value) return;
                _UserName = value;
                OnPropertyChanged("UserName");
            }
        }

        private string _Email;
        public string Email
        {
            get
            {
                return _Email;
            }

            set
            {
                if (_Email == value) return;
                _Email = value;
                OnPropertyChanged("Email");
            }
        }

        private string _Telephone;
        public string Telephone
        {
            get
            {
                return _Telephone;
            }

            set
            {
                if (_Telephone == value) return;
                _Telephone = value;
                OnPropertyChanged("Telephone");
            }
        }

        private DateTime? _RegistDate;
        public DateTime? RegistDate
        {
            get
            {
                return _RegistDate;
            }

            set
            {
                if (_RegistDate == value) return;
                _RegistDate = value;
                OnPropertyChanged("RegistDate");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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



4) 해당 뷰(MainPage.xaml) 에서 뷰모델 접근용 네임스페이스 지정 및 뷰에서 사용할  특정 ViewModel 바인딩 지정  
-뷰 전용 데이터 모델로서 뷰모델이 준비되었으면 해당 뷰모델을 사용할 뷰(xaml)에서 뷰모델을 사용할수 있게 네임스페이스를 추가하고 최상위 요소인 ContentPage의 DataContext 값으로 지정합니다. 

<?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:XamarinMVVMDemo"
             xmlns:vm="clr-namespace:XamarinMVVMDemo.ViewModel"
             x:Class="XamarinMVVMDemo.MainPage">

    <ContentPage.Resources>
    </ContentPage.Resources>

    <ContentPage.BindingContext>
        <vm:MainPageViewModel />
    </ContentPage.BindingContext>

    .................

</ContentPage>



5) 뷰(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:XamarinMVVMDemo"
             xmlns:vm="clr-namespace:XamarinMVVMDemo.ViewModel"
             x:Class="XamarinMVVMDemo.MainPage">

    <ContentPage.Resources>
    </ContentPage.Resources>

    <ContentPage.BindingContext>
        <vm:MainPageViewModel />
    </ContentPage.BindingContext>

    <Grid Margin="5,5,5,5">
        <Grid.RowDefinitions>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

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

        <Label x:Name="lblTitle" Grid.Row="0" Grid.ColumnSpan="2" Text="User Information Manager"   FontAttributes="Bold" FontSize="Large"></Label>

        <Label Grid.Row="1" Grid.Column="0" Text="UserID"  Style="{StaticResource LabelNormalStyle}"></Label>
        <Entry Grid.Row="1" Grid.Column="1" Text="{Binding UserID,Mode=TwoWay}" Style="{StaticResource EntryNormalStyle}"></Entry>

        <Label Grid.Row="2" Grid.Column="0" Text="UserName"  Style="{StaticResource LabelNormalStyle}"></Label>
        <Entry Grid.Row="2" Grid.Column="1" Text="{Binding UserName,Mode=TwoWay}" Style="{StaticResource EntryNormalStyle}"></Entry>

        <Label Grid.Row="3" Grid.Column="0" Text="Email"  Style="{StaticResource LabelNormalStyle}"></Label>
        <Entry Grid.Row="3" Grid.Column="1" Text="{Binding Email,Mode=TwoWay}" Style="{StaticResource EntryNormalStyle}"></Entry>

        <Label Grid.Row="4" Grid.Column="0" Text="Telephone"  Style="{StaticResource LabelNormalStyle}"></Label>
        <Entry Grid.Row="4" Grid.Column="1" Text="{Binding Telephone,Mode=TwoWay}" Style="{StaticResource EntryNormalStyle}"></Entry>

        <Label Grid.Row="5" Grid.Column="0" Text="RegistDate"  Style="{StaticResource LabelNormalStyle}"></Label>
        <Entry Grid.Row="5" Grid.Column="1" Text="{Binding RegistDate,Mode=TwoWay}" Style="{StaticResource EntryNormalStyle}"></Entry>

        <StackLayout Grid.Row="6" Grid.ColumnSpan="2" HorizontalOptions="Center" Orientation="Horizontal">
            <Button Text="REGIST" Style="{StaticResource NormalButtonStyle}"></Button>
            <Button Text="MODIFY" Style="{StaticResource NormalButtonStyle}"></Button>
            <Button Text="DELETE" Style="{StaticResource NormalButtonStyle}"></Button>
        </StackLayout>

        <ListView Grid.Row="7" Grid.ColumnSpan="2" HorizontalOptions="Fill" VerticalOptions="FillAndExpand" BackgroundColor="{StaticResource BackgroundColorType1}" >
        </ListView>

    </Grid>
</ContentPage>

-배포결과



6) 사용자 상호작용 방식

Case1 : Event와 코드비하인드 Event핸들러 사용
-XAML 요소가 이벤트를 발생시키면 해당 이벤트 처리기를 코드비하인드에 구현하는 전통적인 MVP 방식의 코딩 스타일입니다.

-in Xaml
  <Button x:Name="btnRegist" Clicked="btnRegist_Clicked" Text="REGIST" Style="{StaticResource NormalButtonStyle}"></Button>

-in CodeBehid
 private void btnRegist_Clicked(object sender, EventArgs e)
 {
    this.lblTitle.Text = "Regist 버튼 클릭 이벤트가 발생헀습니다.";
 }

Case2: Event와 Command 조합 유형
-뷰에서 UI Eelement(요소) 들은 이벤트가 발생하면 Command라는 매커니즘을 통해 뷰모델과 상호작용을 할수 있습니다.
-Command 는 ICommand 인터페이스를 상속받아 구현되며 뷰모델에서 각종 Command를 정의하고 정의된 커맨드는 사용자로부터 상호 작용을 할수 있는 뷰의 XAML UI 요소에 바인딩 처리할수 있습니다. 
-Command는 XAML(View)에서 ViewModel간 사용자 액션을 처리하는 이벤트와 연계한 상호작용처리를 위해 도입된 기능이며 RelayCommand or Delegate Command라고도 합니다.
-Command는 뷰모델에서 정의하고 뷰에서는 뷰모델의 데이터 속성처럼 뷰에서 관련 UI요소에서 바인딩 기법을 통해 커맨드를 바인딩하여 사용할수 있으며 이벤트가 발생시 해당 커맨드로 각종 파라메터를 전달할수 있습니다. 파라메터가 전달되는 커맨드는 뷰모델에서 커맨드정의시 매개변수를 전달받을수 있게 정의 되어야합니다. 


7) Xamarin.Forms Command 객체 활용하기
- Xamarin.Forms에서는 ICommand를 상속받아 구현된 Command객체를 기본제공하고 있습니다.
해당 커맨드 객체를 이용해 뷰와 뷰모델간 상호작용을 처리해 보겠습니다.

-in ViewModel

public class MainPageViewModel : INotifyPropertyChanged
{
        public MainPageViewModel()
        {

            RegistCommand = new Command(Regist);
            ModifyCommand = new Command<string>(Modify);
            DeleteCommand = new Command(Delete);

            this.UserID = "Eddy.Kang";
            this.UserName = "강창훈";
            this.Email = "admin@signalsoft.co.kr";
            this.Telephone = "010-2760-5246";
            this.RegistDate = DateTime.Now;
        }


        public ICommand RegistCommand{ get; private set; }

        public ICommand ModifyCommand { get; private set; }

        public ICommand DeleteCommand { get; private set; }


        private void Regist()
        {

        }

        private void Modify(string commandPrameter)
        {
            var test = commandPrameter;
        }

        private void Delete()
        {

        }
................

}


-in Xaml

   <StackLayout Grid.Row="6" Grid.ColumnSpan="2" HorizontalOptions="Center" Orientation="Horizontal">
            <Button x:Name="btnRegist" Text="Regist" Command="{Binding RegistCommand}" Style="{StaticResource NormalButtonStyle}"></Button>
            <Button Text="MODIFY" Command="{Binding ModifyCommand}" CommandParameter="M" Style="{StaticResource NormalButtonStyle}"></Button>
            <Button Text="DELETE" Command="{Binding DeleteCommand}" Style="{StaticResource NormalButtonStyle}"></Button>
   </StackLayout>


- 버튼을 클릭할때마다 지정된 Command를 통해  뷰모델의 관련 함수가 호출되는것을 확인할수 있으며 특히 MODIFY 버튼을 클릭하면 커맨드 파라메터로 M이라는 글자가 뷰모델의 관련함수로 값이 전달되는것을 확인할수 있습니다.
- 뷰에서 xaml요소가 이벤트가 발생하면 특정 Command를 실행시킬수 있으며 CommandParameter를 통해 단순한 텍스트 에서 부터 UI Element 까지 다양한 정보와 객체를 뷰모델에 전달할수 있습니다.

지금까지 간단히 뷰모델을 하나 만들어보고 MVVM 패턴 구현의 주요 기능인 뷰모델에서 INotifyPropertyChanged 를 상속받아 ViewModel에 정의된 데이터 속성값의 변경이 발생할때 뷰에서 자동감지하고 뷰의 정보를
갱신하고 뷰 요소의 값이 변경되면 자동의 뷰모델의 관련 바인딩 뷰모델 속성의 값이 변경되는것을 실습해보았습니다.

또한 커맨드라는 개념을 통해 뷰에서 이벤트가 발생했을때 어떻계 뷰모델에 정의된 함수를 실행시키고 관련 뷰에서의 파라메터를 뷰모델에 전달하는지도 실습해보았습니다.
뷰모델 심화편에서는 관련 기능을 Application Architecture 관점에서 기능을 분리하고 좀더 재사용가능한 코드 구조로 솔루션의 과 코드의 구조를 변경해 보도록 하겠습니다.

다음시간은 MVVM에서의 Model부분에 대한 기초적인 실습을 진행해보겠습니다.

감사합니다.


*