MixedCode

안녕하세요. 믹스드코드 운영자 강창훈입니다.

본격적으로 XAMARIN.FORMS에서의 MVVM UI Design Pattern사용방법을 알아보도록 하겠습니다.
함께하시는분들의 원할한 이해와 실습을 위해 
MVVM IN XAMARIN - 기초편 과 MVVM IN XAMARIN -심화편으로 커리큘럼을 재구성하였으며 
기초편에서는 XAMARIN.FORMS 프로젝트 구성방법과 기초적인 VIEW, VIEWMODEL, MODEL 기능 구현을 통해 MVVM의 개념과 프로세스를 이해할수 있게 구성하였습니다.

심화편에서는 기초편에서 작성한 View,ViewModel,Model 등의 기본코드를 다듬고 기능을 확장하며 MVVM 패턴에 최적화되어 사용가능한 몇몇 확장 개념들을 적용하여 코드의 재사용성과  확장성, 코딩의 효율성 고려한 XAML & MVVM 패턴의 고급 기능까지 적용해보도록 하겠습니다. 

먼저 Visual Studio 2017 과 Windows 10 기반에서 XAMARIN.FORMS 프로젝트를 생성합니다.

TOPIC1 : Xamarin.Forms 프로젝트 생성 및 빌드/실행하기

1) Visual Studio 2017를 시작합니다.


2) 파일>새로만들기>프로젝트 메뉴를 클릭하여 새 프로젝트를 생성 템플릿 팝업을 오픈합니다.


3) 좌측 Visual C# 카테고리에서 Cross-Platform 을 선택하고 해당 템플릿을 선택후 아래처럼 관련 정보를 입력후 확인 버튼을 클릭하여 자마린 프로젝트를 생성합니다.


-화면 중간 상단 .NET Framework 4.6.2 버전을 선택합니다.
-프로젝트 템플릿으로 플랫폼간 앱(Xamarin.Forms 또는 네이티브) 템플릿을 선택합니다.
-하단 프로젝트 이름에 "XamarinMVVMDemo" 라고 입력합니다.
-솔루션이 생성될 물리적 폴더 위치를 지정합니다.( D:\ )

4) 메인 페이지 템플릿 유형을  비어있는 앱을 선택하고 코드공유전략을 반드시 PCL 항목을 선택한후 확인을 클릭합니다.


-코드 공유전략?
ㄴ공유 프로젝트 :  
IOS,Android,UWP 프로젝트에서 참조가능한 공통 프로젝트로 사용됩니다.
ㄴPCL(이식 가능한 클래스 라이브러리) : 개발된 소스가 IOS,Android,UWP 프로젝트에 자동 이식됩니다.

5) 설정된 템플릿에 따라 자마린 솔루션 및 필수 프로젝트들이 자동 생성됩니다.




-Windows 10용 전용 앱 개발기능(UWP) 지원을 위한  Windows10  환경설정 팝업창이 나타나면 버전을 확인 후  확인버튼을 클릭합니다.

6) Xamarin Mac 에이전트 연결 팝업창을 닫습니다.

-IOS 개발소스의 빌드 및 배포를 위해서는 MAC O/S 가 설치되는 컴퓨터가 필요하며 해당 컴퓨터에 연결하기 위한 팝업창입니다.

7) 솔루션에는  PCL(이식가능) 프로젝트 와 Android,IOS,UWP 용 필수  프로젝트가 생성됩니다.
- Xamarin.Forms 는 PCL 프로젝트에 주요기능을 개발하고 개발된 내용은 
Android,IOS,UWP 프로젝트에 이식되어 각각의 모바일 O/S별로 맵핑되는 해당 프로젝트들이 배포되고 설치되는 방법으로 크로스 플랫폼 환경을 제공합니다.



8) 개발 및 디버깅 을 위해 안드로이드 프로젝트를 시작 프로젝트로 설정합니다.
- 안드로이드 폰 과 Windows 10 컴퓨터를 사용하는 개발자라면 Android 또는 UWP 프로젝트를 시작 프로젝트로 지정하여 배포 및 디바이스 기준 디버깅이 가능합니다.
- IOS의 디버깅 및 배포.디버깅 작업은 MAC 컴퓨터와 연결 후 가능합니다.  
- 시작 프로젝트로 설정되면 프로젝트명이 흰색 볼드체로 표기되어 나타납니다.


9) 디버깅을 위해 사전에 안드로이드 폰은 개발자모드가 활성화 되어 있어야 하며 안드로이폰을 USB로 개발 컴퓨터와 연결합니다.
-안드로이드폰과 개발컴퓨터가 USB로 연결되면 Visual Studio 2017의 디버깅모드가 해당 안드로이드폰으로 자동 설정됩니다.
-안드로이드 폰의  안드로이드 버전 확인과 개발자모드 설정은 구글링해보면 많은 정보가 제공됩니다. ^^


10) F5 키를 눌러 기본 Xamarin.Forms 프로젝트를  빌드하고 안드로이드폰에 개발결과를 배포합니다.


-실제 Xmarin.Forms의 PCL 프로젝트가 안드로이드 프로젝트에 이식되어 배포된 모습(MainPage.xaml)이며  관련 앱이 폰에 설치(XamarinMVVMDemo App)된것을 확인할수 있습니다.





1.View 구성하기 - 기초편
자동으로 생성된 PCL 프로젝트의 메인 페이지와 메인페이지의 코드 비하인드 내용을 확인해보도록 하겠습니다.

1) 디폴트 메인 페이지인 MainPage.xaml의 모습입니다.


2) 
디폴트 메인 페이지인 MainPage.xaml의 모습입니다.



3)UX 표현 방식
Forms에서는 화면을 담당하는 뷰를 표현하는 방법으로  ~.xaml 과 1:1 맵핑되는 ~.xaml.cs 코드 비하인드 클래스에 C# 언어를 이용해 프로그래밍 방식으로 UI 를 표현하는 방법과 XAML 마크업 언어를 이용해 XML 태그로 UI를 표현하는 두가지 방식을 제공합니다.

Case1 : 코드 비하인드 방식 
-개발자가 프로그래밍 방식으로 직접 UX 요소를 C#언어로 만들고 제어하는 방식을 말합니다.
-아래 코드는 메인 페이지인 MainPage.xaml 의 코드비하인 클래스 
MainPage.xaml.cs 의 생성자에서 StackLayout  UI 요소를 C#으로 생성하고 자식요소로 라벨을 추가한후  해당 StackLayout을 ContentPage의 내용으로 채웁니다.

-In CodeBehind

        public MainPage()
        {
            InitializeComponent();

            StackLayout stack = new StackLayout();
            Label label = new Label();
            label.Text = "Label Cotrol is created by code behind c#";
            stack.Children.Add(label);
            this.Content = stack;
        }

-빌드결과


-코드비하인드 클래스에서 직접 StackLayout 컨트롤을 만들고 자식요소로 라벨컨트롤을 만들어 추가한후 ContentPage로 StackLayout 요소를 C# 언어를 이용 프로그램밍 방식으로 
지정하였습니다.

Case2: XAML방식 
-XAML(재믈,재므얼)은 UI를 표현하는 방법과 UI로직을 분리하기 위한 방법으로 WPF와 함께 도입된 UX표현 및 상호작용을 지원하기 위한 UX 표현용 마크업 언어입니다.
XML 문법을 기반으로 UX를 표현하고 사용자와 상호 작용(InterAction) 할수 있는 새로운 매커니즘들을(Triggers,Converter,Behavior..) 추가로 제공합니다.
빌드과정을 통해 해당 XAML 코드는 C#언어로 변환되어 해당 UI 페이지의 코드비하인드 클래스파일 과 병합되어 하나의 뷰(View)가 디바이스에서 표현됩니다.

-XAML Syntax

<?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"
             x:Class="XamarinMVVMDemo.MainPage">

    <StackLayout>
        <Label Text="Label Cotrol is created by xaml"></Label>
    </StackLayout>

</ContentPage>

-빌드결과


<?xml version="1.0" encoding="utf-8" ?>
-xml 문서 형식 정의
-XAML문서는 기본적으로 XML언어를 이용해 표기되는 문서이기 떄문에 xml 문서형식이 xml버전 및 인코딩 유형을 정의한다.

<ContentPage></ContentPage>
-Xamarin.Forms에서 제공하는 UI컨텐츠를 담는 컨테이너 Eelement이자 컨트롤의 한종류.
-WPF에서는 Windows,Page,UserControl등 다양한 종류의 컨텐츠 컨테이너가 존재함.

xmlns="http://xamarin.com/schemas/2014/forms"
-해당 네임스페이스가 정의된 컨텐츠가 Xamarin.Form 클래스를 참조할수 있는 환경을 제공합니다.
-해당 네임스페이스가 정의된 xaml요소 및 하위요소들은 Xamarin.Forms 네임스페이스내 모든 객체 접근이 가능합니다.

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
-XAML은 접두사(앨리어스)를 통해 네임스페이스를 선언하고 XAML페이지내에서 해당 접두사를 통해 하위 클래스들을 접근하거나 사용이 가능한 환경을 제공합니다.
-.NET Framework 4.0(Winfx=2009년이전)의 XAML 언어 정의(스펙) 및 .NET Framework 4.0 내의 각종 객체를 XAML언어를 통해 접근할수 있는 환경을 제공함
-x접두사를 사용하고 접두사는 앨리어스로 사용되어 Xaml내에서 x를 통해 .NET Framework 4.0 내의 각종 객체 접근 및 xaml 문법규칙 사용이 가능함. 

xmlns:local="clr-namespace:XamarinMVVMDemo"
-해당 프로젝트의 루트를 위치를 local이라는 접두사로 앨리어스를 선언한것임
-현재 XAML 페이지가 존재하는 동일어셈블리(App1) 및 동일 어셈블리 하위 네임스페이스의 각종 객체를 local 로 XAML에서 접근가능함.
-다른 어셈블리의 특정네임스페이스를 접근할때는 하기와 같이 표기합니다.
-xmlns:kkk="clr-namespace:SDKSample.KKK;assembly=SDKSampleLibrary"

x:Class="XamarinMVVMDemo.MainPage"
ㄴpartial 클래스를 통해 코드 숨김클래스와 XAML 파일을  결합하려면 XAML 파일의 루트 요소에서 
해당 코드비하인드 클래스를 x:Class 특성으로 명명해야 합니다.


4) 화면구성의 기본단위 Page 객체 이해하기
하나의 화면을 구성하는 기본단위는 Page라는 객체를 사용합니다. 
PCL 프로젝트의 MainPage 클래스의 상속 구조는 아래 같으며 Xamarin.Forms Page 의 종류는 용도에 맞게 다양한 모습으로 제공되고 있습니다. 

MainPage : ContentPage : TemplatedPage : Page, IControlTemplated

ㅁXamarin.Forms Page 유형
-ContentPage 
-MasterDetailPage
-NavigationPage
-TabbedPage
-CarouselPage

5) 화면 레이아웃 구성하기
단일 화면내에 각종 요소를 배치하여 화면을 구성하는 레이아웃 작업을 하기 위해서 Xamarin.Forms에서는 아래와 같은 주요 Layout 컨트롤 요소를 제공합니다.
-StackLayout
-AbsoluteLayout
-RelativeLayout
-Grid
-ContentView
-ScrollView
-Frame

상기 레이아웃 요소중에 Grid 컨트롤을 이용해 향후 MVVM 예제로 사용할 간단한 사용자 등록 및 사용자 목록 화면을 구성해보도록 하겠습니다.

-in XAML(View)

<?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"
             x:Class="XamarinMVVMDemo.MainPage">

    <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 Grid.Row="0" Grid.ColumnSpan="2" Text="User Information Manager"   FontAttributes="Bold" FontSize="Large"></Label>
        <Label Grid.Row="1" Grid.Column="0" Text="UserID"  VerticalOptions="Center" HorizontalOptions="Center" FontAttributes="Bold" FontSize="Large"></Label><Entry Grid.Row="1" Grid.Column="1" HorizontalOptions="Fill"></Entry>
        <Label Grid.Row="2" Grid.Column="0" Text="UserName"  VerticalOptions="Center" HorizontalOptions="Center" FontAttributes="Bold" FontSize="Large"></Label><Entry Grid.Row="2" Grid.Column="1" HorizontalOptions="Fill"></Entry>
        <Label Grid.Row="3" Grid.Column="0" Text="Email"  VerticalOptions="Center" HorizontalOptions="Center" FontAttributes="Bold" FontSize="Large"></Label><Entry Grid.Row="3" Grid.Column="1" HorizontalOptions="Fill"></Entry>
        <Label Grid.Row="4" Grid.Column="0" Text="Telephone"  VerticalOptions="Center" HorizontalOptions="Center" FontAttributes="Bold" FontSize="Large"></Label><Entry Grid.Row="4" Grid.Column="1" HorizontalOptions="Fill"></Entry>
        <Label Grid.Row="5" Grid.Column="0" Text="RegistDate"  VerticalOptions="Center" HorizontalOptions="Center" FontAttributes="Bold" FontSize="Large"></Label><Entry Grid.Row="5" Grid.Column="1" HorizontalOptions="Fill"></Entry>

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

        <ListView Grid.Row="7" Grid.ColumnSpan="2" HorizontalOptions="Fill" VerticalOptions="FillAndExpand" BackgroundColor="Gray" >
            <TextCell />
            <TextCell />
        </ListView>

    </Grid>

</ContentPage>

-빌드결과



6) 화면 스타일 적용하기
하나의 앱은 일반적으로 여러개의 화면으로 구성됩니다.
그러나 화면별로 존재하는 각종 텍스트박스나 라벨등의 요소등은 사용자에게 보여질때 UX 일관성 유지를 위해 동일한 모습(스타일)으로 보여질 필요가 있습니다.
단일화면내 또는 모든화면의 각종 요소들에 대한 일관된 스타일 처리 및 변경 및 유지보수의 효율성을 위해 XAML에서는 ResourceDictionary라는 스타일 관리 기능을 제공합니다.

상기 XAML 코드의 각종 컨트롤마다 매번 설정되어 있는 동일 스타일 지정 코드를 공통 스타일로 빼고 해당 스타일을 요소마다 지정하는 방법을 실습해보겠습니다.
그리고 하나의 화면내 특정 요소(StackLayout)내에서 부분적으로 스타일을 적용하는 예시도 적용해보겠습니다.
  
-in Xaml(View)

<?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" 
x:Class="XamarinMVVMDemo.MainPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <Style x:Key="LabelNormalStyle" TargetType="Label">
                <Setter Property="VerticalOptions" Value="Center" />
                <Setter Property="HorizontalOptions" Value="Center" />
                <Setter Property="FontAttributes" Value="Bold" />
                <Setter Property="FontSize" Value="Large" />
            </Style>

            <Style x:Key="EntryNormalStyle" TargetType="Entry">
                <Setter Property="HorizontalOptions" Value="Fill" />
            </Style>

            <Color x:Key="BackgroundColorType1">Blue</Color>

        </ResourceDictionary>
    </ContentPage.Resources>

    <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 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" Style="{StaticResource EntryNormalStyle}"></Entry>

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

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

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

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

        <StackLayout Grid.Row="6" Grid.ColumnSpan="2" HorizontalOptions="Center" Orientation="Horizontal">
            <StackLayout.Resources>
                <ResourceDictionary>
                    <Style x:Key="NormalButtonStyle" TargetType="Button">
                        <Setter Property="BackgroundColor" Value="Gray" />
                    </Style>
                </ResourceDictionary>
            </StackLayout.Resources>

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

-빌드결과


이번에는 해당 스타일을 단일 페이지 영역이 아닌 모든 페이지에서 전역적으로 사용가능하게 <ResourceDictionary> 위치를 App.xaml페이지로 이동시켜 적용해보겠습니다.

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns=http://xamarin.com/schemas/2014/forms
 
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
x:Class="XamarinMVVMDemo.App">

<Application.Resources>
        <ResourceDictionary>
            <Style x:Key="LabelNormalStyle" TargetType="Label">
                <Setter Property="VerticalOptions" Value="Center" />
                <Setter Property="HorizontalOptions" Value="Center" />
                <Setter Property="FontAttributes" Value="Bold" />
                <Setter Property="FontSize" Value="Large" />
            </Style>

            <Style x:Key="EntryNormalStyle" TargetType="Entry">
                <Setter Property="HorizontalOptions" Value="Fill" />
            </Style>

            <Style x:Key="NormalButtonStyle" TargetType="Button">
                <Setter Property="BackgroundColor" Value="Gray" />
            </Style>

            <Color x:Key="BackgroundColorType1">Blue</Color>
        </ResourceDictionary>
</Application.Resources>
</Application>

MVVM IN XAMARIN.FORMS 기초편의 VIEW 다루는 내용은 이정도만 다루도록 하겠습니다.
MVVM IN XAMARIN.FORMS 활용편의 VIEW 토픽에서는 사용자 액션과 상호작용할수 있는 XAML에서의 기술로 Trigger,XAML요소에 바인딩된 값의 변환 처리를 담당하는 Converter,Xaml요소의 확장속성을 제공할수 있는 Attached Property 기능에 대해서 알아보도록 하겠습니다.
 
다음 MVVM IN XAMARIN.FORMS 기초편의 ViewModel 섹션에서는 XAML요소의 Bindable 속성과 모델의 속성을 바인딩할수 있는 메카니즘을 제공하는 INotifyChanged 와 Xaml요소의 이벤트가 발생했을때 뷰모델로 사용자 의도(명령)을 전달할수 구조를 제공하는 ICommand에 대해 알아보고 관련 기능을 구현해보겠습니다. 

감사합니다.

*