전체 페이지뷰

2017년 3월 17일 금요일

Chapter 7. XAML vs. code, part 1

C#은 세상에서 가장 훌륭한 언어 중 하나입니다. C#만으로 자마린 프로젝트를 전부 작성할 수 있으며, 다른 어떤 언어보다도 Xamarin에 적합하다는 것을 알게 되셨을 겁니다.



그러나, 오픈 마인드를 가지시기 바랍니다. 자마린은 개발의 어떤 측면에서는 C#보다 뛰어난 대체물을 권장합니다. 이 대체물이 바로 XAML(낙타의 camel과 비슷하게 "zammel"이라고 발음합니다)이라는 Extensible Application Markup language입니다. XAML 역시 Microsoft가 개발했고 C# 몇년 후에 발표되었습니다.

이름에서 알 수 있듯이 XAML은 또다른 Extensible Markup Language인 XML의 문법을 따릅니다. 이 책은 여러분이 XML의 문법에 익숙하다는 가정하에 진행합니다.

가장 일반적인 의미에서 XAML은 개체를 인스턴스화하고 초기화하는 데 사용되는 선언적 마크 업 언어입니다. 이 정의는 지나치게 범위가 넓어 보이긴 하지만 XAML을 실제로도 매우 유연합니다. 그러나, 실제 사용시에 XAML은 그래픽 기반 프로그래밍 시에  트리 구조의 UI 설계에 주로 쓰여 왔습니다. XAML 기반의 UI 설계는 WPF(Windows Presentation Foundation)으로 시작하여 Silverlight, Windows 폰 7, 8, 10으로 계속 이어 졌습니다. 이 XAML의 구현은 플랫폼마다 시각적 element 세트 면에서 조금씩 다릅니다. 마찬가지로 Xamarin.Forms의 XAML 구현은 Label, BoxView, Frame, Button, StackLayout, ContentPage와 같은 Xamarin.Forms에 의해 정의된 시각적 요소로 이루어 집니다.

지금까지 보셨듯이, 코드로 작성된 Xamarin.Forms 응용 프로그램은 일반적으로 ContentPage에서 파생되는 클래스의 생성자에서 사용자 인터페이스의 최초 형태를 정의합니다. XAML을 사용하기로 선택하시면 마크업이 이 생성자 코드를 대체합니다. XAML은 보다 간결하고 우아한 UI 정의를 제공하며 페이지의 시각요소 트리구조를 좀 더 잘 표현해 주는 시각적 구조를 제공합니다.

XAML은 또 해당 코드의 유지와 수정이 더 쉽습니다. 또, XAML은 XML이므로 도구라는 측면에서 강력합니다. XAML은 해당 C# 코드보다 더 소프트웨어에 의해 쉽게 파싱되고 수정될 수 있습니다. 실제로 XAML의 초기 개발 이유는 프로그래머와 디자이너 간의 공동 작업을 돕는 것이었습니다. 디자이너는 XAML을 생성하는 디자인 도구를 사용할 수 있으며 프로그래머는 마크업과 상호 작용하는 코드에 집중할 수 있습니다. 그 목표는 완성되지 못했지만 XAML을 수용할 수있는 응용 프로그램을 구성하는 방법을 제시합니다. 시각적 부분에 XAML을 사용하고 로직 부분에 코드를 사용하는 방법입니다.

XAML은 단순한 분업을 뛰어 넘습니다. 이후 다른 장에서 보시게 될 텐데, UI 개체와 해당 데이터를 연결하는 바인딩을 XAML에서 바로 정의할 수도 있습니다.

XAML을 마이크로소프트 기반에서 프로그래밍할 때, 어떤 개발자들은 Microsoft Blend 같은 interactive 디자인 툴을 선호하기도 하지만, 대부분은 툴 없이 사용합니다. Xamarin.Forms에는 디자인 툴이 없으므로 직접 타이핑이 유일한 방법이고, 따라서 이 책의 예제도 전부 그렇습니다. 그러나, 디자인 툴을 사용할 떄에 역시 직접 타이핑할 수 있는 기술이 중요합니다.

XAML을 수기(handwriting)하는 것에는 또 다른 장점이 있습니다. XML은 장황하기로 악명 높지만, 이제 곧 여러분은 XAML이 해당 C#코드보다 더 간결할 수도 있다는 점을 아시게 될겁니다. XAML의 진가는 차차 드러날 것이며, 19장 "Collection views"에 가서야 명확해 질 겁니다. 그 챕터에서는 ListView에 표시된 여러 항목의 템플릿을 구성하는 데 XAML을 사용하게 됩니다.

C #과 같이 strogn type의 언어를 선호하는 프로그래머는 모든 것이 텍스트 문자열인 마크 업 언어를 회의적으로 생각하는 것도 당연합니다. 그러나 곧 XAML이 프로그래밍 코드와 매우 엄격하게 유사하다는 알게 될 것입니다. XAML 파일에 허용되는 대부분은 Xamarin.Forms application 프로그래밍 인터페이스를 구성하는 class 및 property에 의해 정의됩니다. 그래서 여러분은 심지어 XAML이 "strong typed" 언어라는 생각이 들 정도가 될 것입니다. XAML 파서는 하부 API에서 기계적으로 작업을 수행합니다. 이 장과 다음 장의 목적 중 하나는 XAML을 분석하고, XAML을 파싱할 때 발생하는 상황을 밝히는 것입니다.

그러나 코드는 프로세스를 정의하고 마크업은 상태를 정의한다는 점에서 둘은 상당히 다릅니다. 또 XAML은 마크업 언어로서의 몇 가지 한계를 가지고 있는데 그것은, 루프, 흐름 제어, 산술 연산, 이벤트 핸들러가 없다는 점입니다. 하지만 이런 결점들을 보상해줄 좋은 점들도 가지고 있는데 그것은 차차 배우시게 될 겁니다.

XAML을 사용하고 싶지 않으시다면 그것도 상관 없습니다. XAML로 할 수 있는 것은 모두 C#으로도 가능하기 때문입니다. 하지만 어떤 개발자들은 XAML을 선호하여 거의 모든 작업을 XAML로 작성하는 경우가 있습니다. 제일 좋은 것은 중용의 길을 택하는 것이고, 코드와 XAML을 interactive한 방법으로 결합하는 것이 좋습니다.

이 제 코드 스니펫들과 그에 상응하는 XAML을 살펴보고 자마린 앱에서 어떤 방식으로 어울리는지 살펴보도록 합시다.

Properties and attributes

여기 page 클래스의 생성자에서 나타날 법한, 코드로 초기화, 인스턴스화 된 Label이 있습니다.
new Label
{
    Text = "Hello form Code!",
    IsVisible = true,
    Opacity = 0.75,
    VerticalOptions = LayoutOptions.CenterAndExpand,
    TextColor = Color.Blue,
    BackgroundColor = Color.FromRgb(255128128),
    FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
    FontAttributes = FontAttributes.Bold | FontAttributes.Italic
};
cs

다음은 XAML로 작성된 해당 코드의 내용입니다. 훨씬 간결한 것을 확인할 수 있습니다.
<Label Text="Hello from XAML!" 
       IsVisible="True"
       Opacity="0.75"
       HorizontalTextAlignment="Center"
       VerticalOptions="CenterAndExpand"
       TextColor="Blue"
       BackgroundColor="#FF8080"
       FontSize="Large"
       FontAttributes="Bold,Italic"  />
cs

Label과 같은 자마린 클래스는 XAML로는 XAML element가 됩니다. Text나 IsVisible같은 프로퍼티는 XAML에선 attribute에 해당합니다.

XAML에서 인스턴스화하려면 Label과 같은 클래스에 매개변수 없는 public 생성자가 있어야 합니다.(다음 챕터에서 XAML의 생성자에 인수를 전달하는 기술이 소개되지만, 특수 용도로 사용됩니다) XAML의 프로퍼티 set에는 public set라는 접근자가 필요합니다. 일반적으로 코드에서는 등호 주변에 공백을 주지만 XAML에서는 주지 않습니다. 다만 원한다면 공백을 몇 개든 사용할 수 있습니다.

XAML의 간결함은 대부분 attribute 값의 간결함에서 비롯됩니다. 예를 들어 Device.GetNamedSize 메서드 호출보다는 "Large"라는 단어를 사용하는 것이 그 예입니다. 이런 약어들은 XAML 파서에 내장되어 있는 것이 아니고, 특별한 목적으로 정의된 컨버터 클래스들에 의해 이루어집니다.

XAML 파서가 Label element를 발견하면 리플렉션을 사용하여 Xamarin.Forms에 Label이라는 클래스가 있는지를 먼저 확인하고, 확인되면 해당 클래스를 인스턴스화합니다. 이제 객체를 초기화할 준비가 되었습니다. Text 프로퍼티는 string 형식이고 attribute 값은 간단히 프로퍼티에 할당됩니다.

XAML은 XML이기 때문에 표준 XML 문법에 의해 텍스트를 유니코드로 포함할 수 있습니다. 십진수 유니 코드 앞에는 &# (16진수의 경우 &#x)가 먼저 오고, 다음에 세미콜론을 붙입니다.
Text="Cost &#x2014; &#x20AC;123.45"
cs

이것은 대쉬, 유로화 심볼(—€)의 유니코드입니다. 줄바꿈을 하려면 행 넘김 문자인 #x000A 또는 & xA(앞에 오는 0은 생략가능), 또는 10진수로 &#10을 사용하십시오.

부등호(< >), ampersand(&), 따옴표(" ")은 XML에서 중요한 의미가 있는 문자이기 때문에 그 문자를 텍스트로 사용하려면 표준 predefined entity를 사용해야 합니다.


  • &lt; 는 <
  • &gt; 는 >
  • &amp; 는 &
  • &apos; 는 '
  • &quot; 는 "


&nbsp 같은 HTML predifined entity는 지원되지 않습니다. 대신 줄바꿈 없는 공백은 &#xA0;를 사용합니다.

추가로, 10장에서 여러분은 중괄호({ })도 XAML에서 중요한 의미가 있는 것임을 알게 되실 겁니다. 왼쪽 중괄호로 attribute 값을 시작해야하는 경우 중괄호 한쌍 ({})과 왼쪽 중괄호로 시작하십시오.

예제로 돌아가 봅시다. Label의 프로퍼티인 IsVisibleOpacity는 각각 bool형과 double형인데 사용은 역시 간단합니다. XAML 파서는 Boolean.ParseDouble.Parse라는 메소드를 사용하여 attribute 값을 변환합니다. Boolean.Parse 메소드는 대소문자를 구분하지는 않지만 보통 XAML에서는 "True", "False" 처럼 첫 글자를 대문자로 사용합니다. Double.Parse 메서드에는 CultureInfo.InvariantCulture 인수가 전달되므로, 변환과정이 프로그래머 또는 사용자의 로컬 culture에 영향을 받지 않습니다.

LabelHorizontalTextAlignment 프로퍼티는 TextAlignment 타입이며 열거형입니다. 열거형인 속성에 대해서 XAML 파서는 Enum.Parse 메소드를 써서 값을 변환합니다.

VerticalOptions 프로퍼티는 LayoutOptions 타입이며 구조체 입니다.

XAML 파서는 리플렉션을 사용하여 LayoutOptions 구조를 참조하고, 구조체 내부에 정의된 C# attribute를 찾습니다.
[TypeConverter (typeof(LayoutOptionsConverter))] 
public struct LayoutOptions 
    … 
}
cs

(주의 바랍니다. 우리는 지금 두 종류의 attribute을 논하고 있습니다. 하나는 HorizontalTextAlignment와 같은 XML attribute이고, 하나는 TypeConverter와 같은 C# attribute입니다.)

TypeConverter attribute는 TypeConverterAttribute라는 클래스에서 지원됩니다. LayoutOptions의 이 TypeConverter attribute는 LayoutOptionsConverter라는 클래스를 참조하는데, 이 클래스는 TypeConverter라는 public abstract 클래스에서 파생되었으며 LayoutConnectionFrom ConvertFrom이라는 메서드를 정의합니다. XAML 파서가 이 TypeConverter attribute를 만나면 LayoutOptionsConverter를 인스턴스화 합니다. XAML의 VerticalOptions 특성에는 "Center"라는 문자열이 할당되었는데, XAML 파서는 이 "Center"라는 문자열을 LayoutOptionsConverterConvertFrom 메서드에 전달하고, LayoutOptions 값을 내보냅니다. 이 값은 Label 객체의 VerticalOptions라는 프로퍼티에 할당됩니다.

마찬가지로 XAML 파서가 TextColor BackgroundColor 프로퍼티를 발견하면 리플렉션을 사용하여 해당 프로퍼티가 Color 타입인지 확인합니다. Color 구조체는 역시 TypeConverter attribute로 장식되어 있습니다.

[TypeConverter (typeof(ColorTypeConverter))] 
public struct Color 
    … 
}
cs

원한다면 ColorTypeConverter의 인스턴스를 만들고 코드로 실험할 수도 있습니다. 이 컨버터는 몇 가지 방식으로 색을 정의하는 것을 지원합니다: "Blue"와 같은 문자열을 Color.Blue 값으로, "Default"나 "Accent"문자열을 Color.Default, Color.Accent 값으로 변환 할 수 있습니다. 또한 “#FF8080”과 같은 R-G-B 값도 파싱할 수 있는데, 그 뜻은 red 0xFF, green 0x80, blue 0x80이라는 뜻입니다.

모든 숫자 RGB 값은 number-sign 접두사로 시작하고, 특정 색상 값을 지정하기 위해 8, 6, 4, 혹은 3개의 16 진수를 붙일 수 있습니다(alpha 채널은 있을수도 없을 수도 있습니다). 가장 긴 구문은 다음과 같습니다.
BackgroundColor="#aarrggbb"
cs

각 문자는 16진수를 의미하고, 순서는 alpha, red, green, blue의 순입니다. 알파값은 0xFF가 완전 불투명, 0x00이 완전 투명임을 기억해두시기 바랍니다. 아래는 알파채널 없는 구문입니다.
BackgroundColor="#rrggbb"
cs

이 경우 알파값은 완전 불투명인 0xFF로 설정됩니다.

색상별로 16진수 하나로 이루어진 두 개의 구문이 더 사용 가능합니다.
BackgroundColor="#argb" 
BackgroundColor="#rgb"
cs

이 경우 숫자는 같은것 두개로 처리됩니다. 에를 들어 #CF3은 RGB로 0xCC-0xFF-0x33으로 처리되는데 이런 경우는 거의 사용되지 않습니다.

LabelFontSize 프로퍼티는 double 타입입니다. 이것은 LayoutOptions, Color의 경우와는 조금 다릅니다. LayoutOptionsColor 구조체는 Xamarin.Forms의 일부이므로 C# TypeConverter attribute로 플래그를 지정할 수 있지만 폰트 사이즈용 TypeConverter attribute로 .NET Double 구조체를 플래그 지정할 수는 없습니다!

그 대신 Label 클래스 내의 FontSize 프로퍼티가 TypeConverter attribute를 가지고 있습니다.

public class Label : View, IFontElement 
{
    … 
    [TypeConverter (typeof (FontSizeConverter))]
    public double FontSize 
    {
        …
    } 
    … 
}
cs

FontSizeConverter 클래스는 전달받은 문자열이 NamedSize 열거형의 멤버인지 확인합니다. 아니라면 그 값은 double로 간주합니다.

예제의 마지막 attribute는 FontAttributes입니다. FontAttribute 프로퍼티는 열거형이고, XAML 파서가 열거형은 자동으로 처리한다고 말씀드렸습니다. 그러나 FontAttributes 열거형은 다음과 같은 C# Flags attribute를 가집니다.

[Flags] 
public enum FontAttributes 
    None = 0
    Bold = 1
    Italic = 2 
}
cs

XAML 파서는 콤마로 구분된 여러 개의 멤버 표현을 허락합니다.
FontAttributes="Bold,Italic"
cs

위의 예는 XAML 파서의 기계적인 성격잘 보여줍니다. 즉, XAML에 사용자 지정 클래스를 포함 할 수 있으며, 이 클래스는 사용자 지정 형식의 프로퍼티를 가질 수 있고, 또 이 프로퍼티는 표준 타입일 수 있지만 추가 값을 허용합니다. 여러분은 C# TypeConverter 특성을 사용하여 이런 타입이나 프로퍼티를 플래그 지정하고 TypeConverter에서 파생되는 클래스를 제공하기만 하면 됩니다.


Property-element syntax

여기 4장에서 보았던 FramedText와 유사한 C# 코드가 있습니다. 하나의 문장 내에서 FrameLabel을 인스턴스화하고 LabelFrameContent로 지정합니다.
new Frame 
    OutlineColor = Color.Accent,
    HorizontalOptions = LayoutOptions.Center,
    VerticalOptions = LayoutOptions.Center, 
    Content = new Label 
    {
        Text = "Greetings, Xamarin.Forms!" 
    }
};
cs

그러나 XAML로 이것을 똑같이 표현할 때, Content attribute을 설정하는 시점에서 조금 난해해집니다.
<Frame OutlineColor="Accent"
    HorizontalOptions="Center"
    VerticalOptions="Center"
    Content=" 여기 뭘 써야 하나요? " />
cs

Content 속성을 Label 객체로 통채로 설정하려면 어떻게 해야 합니까?

이것을 해결하는 방법이 XAML 문법의 핵심입니다. 첫 단계는 Frame 태그를 start와 end 태그로 나누어 분리하는 것입니다.
<Frame OutlineColor="Accent" 
       HorizontalOptions="Center" 
       VerticalOptions="Center"
</Frame>
cs

이 태그 내부에 두 개의 태그를 더해줍니다. 그 태그는 element(여기서는 Frame)와 프로퍼티(여기서는 Content)를 마침표(.)로 연결합니다
<Frame OutlineColor="Accent" 
       HorizontalOptions="Center" 
       VerticalOptions="Center"
    <Frame.Content
    </Frame.Content>
</Frame>
cs

다음으로 내부의 태그 안에 Label을 넣어줍니다.
<Frame OutlineColor="Accent" 
       HorizontalOptions="Center" 
       VerticalOptions="Center"
    <Frame.Content
        <Label Text="Greetings, Xamarin.Forms!" />
    </Frame.Content>
</Frame>
cs

이렇게 해서 LabelFrameContent로 지정할 수 있습니다.

이것이 XAML의 문법 규칙에 위배되는 것이 아닌가 생각할 수도 있지만 그렇지는 않습니다. XML에서 마침표는 아무런 의미도 없으며, 따라서 Frame.Content 태그는 문법에 어긋나지 않습니다. 그러나 XAML은 이 태그들에 대해서 자체 룰을 가지고 있습니다. Frame.Content 태그는 Frame 태그 내에 있어여 하며, Frame.Content 태그에 어떤 attribute도 붙어서는 안 됩니다. Content 프로퍼티에 설정된 객체는 이런 태그들의 XML 내용으로 표현됩니다.

구문이 사용되었으니 용어도 설명 드려야겠습니다. 위의 코드 스니펫에서:


  • FrameLabel은 XML element로 표현된 C# 객체이며, object element라고 부릅니다.
  • OutlineColor, HorizontalOptions, VerticalOptions, Text는 XML attribute로 표현된 C# 프로퍼티이며, property attribute라고 부릅니다.
  • Frame.Content는 XML element로 표현된 C# 프로퍼티이므로 property element라고 부릅니다.


Property element는 실제 XAML 코드에서 흔하게 사용됩니다. 앞으로 그 예를 많이 보시게 될 것이며, 여러분이 XAML 코딩을 할 때 필수적으로 사용하시게 될 겁니다. 그러나 한참 XAML을 사용하여 익숙해진 후에도 특정 개체를 프로퍼티로 설정할 수 없는 상황이 발생하곤 합니다. 그럴 때의 해결책은 대개 property element라는 기본을 잊지 마시기 바랍니다.

예를 들어 더 간단한 프로퍼티 설정에도 property element를 사용할 수 있습니다.
<Frame HorizontalOptions="Center"
    <Frame.VerticalOptions
        Center 
    </Frame.VerticalOptions>
    <Frame.OutlineColor>
        Accent
    </Frame.OutlineColor>
    <Frame.Content>
        <Label>
            <Label.Text>
                Greetings, Xamarin.Forms!
            </Label.Text
        </Label
    </Frame.Content
</Frame>
cs

이제 FrameVerticalOptions, OutlineColor 프로퍼티, LabelText 프로퍼티가 모두 property element가 되었습니다. 이러한 attribute들의 값은 항상 따옴표가 없는 property element의 content입니다.

물론 실제에서 property를 property element로 정의하는 것은 비상식적이고, 쓸데없이 장황한 코드를 만듭니다만, 작동하기는 합니다.

조금더 깊이 들어가 봅시다. HorizontalOptions를 "Center"(static 프로퍼티 LayoutOptions.Center에 해당)로 지정하는 대신 property element로 만들고 내부에 개별 프로퍼티로 LayoutOptions를 표현할 수가 있습니다.
<Frame>
    <Frame.HorizontalOptions
        <LayoutOptions Alignment="Center"
                       Expands="False" /
    </Frame.HorizontalOptions>
    <Frame.VerticalOptions>
        Center 
    </Frame.VerticalOptions>
    <Frame.OutlineColor>
        Accent 
    </Frame.OutlineColor
    <Frame.Content>
        <Label>
            <Label.Text
                Greetings, Xamarin.Forms!
            </Label.Text
        </Label>
    </Frame.Content
</Frame>
cs

또, LayoutOptions의 프로퍼티를 property element로 표현할 수도 있습니다.
<Frame>
    <Frame.HorizontalOptions>
        <LayoutOptions>
            <LayoutOptions.Alignment>
                Center
            </LayoutOptions.Alignment>
            <LayoutOptions.Expands
                False 
            </LayoutOptions.Expands>
        </LayoutOptions>
    </Frame.HorizontalOptions>
     … 
</Frame>
cs

같은 프로퍼티를 property attribute와 property element로 동시에 둘 수는 없습니다. 그것은 프로퍼티를 두번 설정하는 것이 되어 문법상 허용되지 않습니다. 그리고 property element 태그에는 아무것도 와서는 안 됩니다. 프로퍼티에 설정되는 값은 언제나 그 태그의 XML 컨텐츠여야 합니다.

자, 이제 XAML로 StackLayout을 사용하는 법을 배울 차례입니다. 먼저, Children 프로퍼티를 StackLayout.Children이라는 property element로 표현하고, children들을 property-element 태그의 XML로 표현합니다. 다음은 StackLayout의 Children으로 또 다른 가로방향 StackLayout이 오는 예입니다.

<StackLayout>
    <StackLayout.Children>
        <StackLayout Orientation="Horizontal">
            <StackLayout.Children>
                <BoxView Color="Red" />
                <Label Text="Red" VerticalOptions="Center" />
            </StackLayout.Children>
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <StackLayout.Children>
                <BoxView Color="Green" />
                <Label Text="Green" VerticalOptions="Center" />
            </StackLayout.Children>
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <StackLayout.Children>
                <BoxView Color="Blue" />
                <Label Text="Blue" VerticalOptions="Center" />
            </StackLayout.Children>
        </StackLayout>
    </StackLayout.Children>
</StackLayout>
cs

각 가로 StackLayout은 색깔 BoxView와 그 색 이름의 Label을 가집니다.

물론, 마크업이 반복해서 나오는 것이 좀 무서워 보이긴 합니다. 16색, 또는 140색을 표현한다면 어떨까요? 처음엔 복사와 붙여놓기로 해결 가능할지 몰라도 시각적으로 개선해야 한다면 이상한 모양이 될 것이 뻔합니다. 코드로 작성한다면 루프로 해결할 수 있지만 XAML에는 그런 기능이 없습니다.

마크업에서 반복작업을 해야한다면 대신 코드로 작성할 수 있습니다. XAML로 대략의 UI를 잡고, 나머지를 코드로 정의하면 합리적으로 작업을 수행할 수 있을 것입니다. 하지만 다음 챕터에서 다른 해결책도 보여드리겠습니다.


Adding a XAML page to your project

지금까지 XAML의 코드조각들만을 보았습니다. 이제 전체 프로그램의 맥락 속에서 전체 XAML 페이지를 보도록 합시다. 먼저, PCL 솔루션 템플릿을 사용해서 CodePlusXaml이라는 솔루션을 생성합니다.

주) 2017.4월 기준, 바뀐 템플릿으로는

으로 선택하면 될 것입니다.

그리고 이 PCL에 XAML ContentPage를 추가합니다. 비주얼스튜디오의 솔루션 탐색기 상에서 CodePlusXaml을 우클릭하고 추가>새 항목을 선택합니다. 새 항목 추가 창 왼쪽에서 Visual C#>Cross-Platform을 선택합니다. 그리고 가운데 리스트에서 Forms ContentView Xaml을 고르고 CodePlusXamlPage.cs로 이름을 써줍니다.

주)바뀐 템플릿에서는 이 과정이 필요없습니다. 제가 공식 문서를 찾지 못했습니다만 아마도 XAML을 더 강화한 모양인지 기본으로 Xaml 파일이 생성되고 그 하위에 C# 파일이 partial class로 선언되어 이미 포함되어 있습니다.(뭘 바꿨으면 대문짝만하고 정확하게 공지를 해 줄 것이지 왜 이렇게 막 시작한 사람을 시험에 들게 하는지 ㅠㅠ...)
솔루션 모양을 그림첨부 하겠습니다.



자마린 스튜디오에서는 솔루션 리스트의 CodePlusXaml 프로젝트 상에서 드롭다운 메뉴를 활성화 시켜 Add>New File을 선택합니다. New File 창에서 좌측 Forms를 선택하고, 가운데 목록에서 Forms ContentPage Xaml을 선택합니다. (Forms ContentView Xaml도 있으니 주의). 그리고 CodePlusXamlPage라고 이름을 정해줍니다.

각각의 경우에서 두 개의 파일이 생성됩니다:
CodePlusXamlPage.xaml이란 이름의 XAML file
CodePlusXamlPage.xaml.cs이란 이름의 C# file (확장자가 두 개라 이상하긴 합니다만...)

주) 솔루션 상에서 생성된 파일 목록도 달라졌습니다. 따로 XAML파일을 추가할 필요없이 이미 기본 생성되어 있어서 이름이 다릅니다.

파일 목록에서 두번째 파일은 첫번째 파일 하위에 들여쓰기 되어 그들의 긴밀한 관계를 알게 해 줍니다. C# 파일은 때로 XAML파일의 뒷배경 파일로 일컬어지며, 마크업을 서포트하는 코드를 담고 있습니다. 이 두 파일 모두 ContentPage를 상속받은 CodePlusXamlPage 클래스를 만드는데 사용됩니다.

그 중 먼저 코드 파일을 보겠습니다. using문을 제외하면 다음과 같습니다.

namespace CodePlusXaml
{
    public partial class CodePlusXamlPage : ContentPage
    {
        public CodePlusXamlPage()
        {
            InitializeComponent();
        }
    }
}
cs

바뀐 템플릿으로 생성한 MainPage.xaml.cs 파일은 아래와 같습니다.
namespace CodePlusXaml
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }
}
cs

더 이상 바뀐 템플릿 얘기를 하다가는 끝이 없을 것 같아 그냥 책에 있는 대로 번역을 진행하면서 바뀐 부분은 나름대로 적용하려 합니다. 만에 하나 혹 이 글을 보시는 분이 있다면 감안하여 봐주시길 바랍니다.

ContentPage로부터 파생된 CodePlusXamlPage 클래스입니다. 그러나 클래스 선언부에 partial 키워드가 들어가 전체 클래스 정의의 일부임을 알려줍니다. 그렇다면 나머지 정의는 다른 곳에 있을텐데 그곳은 어디일까요?

또 다른 궁금증은 생성자에서 호출된 InitializeComponent 메소드입니다. 문법에 의거해 생각해 보자면 이 메소드는 ContentPage에 의해 정의되거나 상속되어야 하는 것일텐데 API 문서에서 이 메소드를 찾아볼 수가 없습니다.

잠시 이 궁금증들은 제쳐두고 XAML 파일을 보도록 합시다. 자마린 스튜디오와 비주얼 스튜디오에서 이 파일은 좀 다르게 생성됩니다. 비주얼 스튜디오를 사용하고 있다면 Label의 마크업을 제거하고 ContentPage.Content property-element 태그로 대체해 봅시다(그러면 자마린 스튜디오의 것과 같아집니다).
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:CodePlusXaml"
             x:Class="CodePlusXaml.MainPage">
    <ContentPage.Content>        
    </ContentPage.Content>
</ContentPage>
cs

루트 element는 ContentPage이고 MainPage는 그로부터 파생되었습니다. 태그는 몇개의 XML 네임스페이스 선언으로부터 시작되는데 해당 URI 주소에 가 보실 필요는 없습니다. 거기에는 아무 것도 없으니까요. 이 URI는 누가 네임 스페이스를 소유하고 있으며 어떤 기능을 제공하는지를 나타낼 뿐입니다.

디폴트 네임스페이스는 Xamarin에 속한 것입니다. ContentPage 태그처럼  접두사가 없는 파일의 element에 대한 XML 네임스페이스입니다. URI에는 이 네임스페이스가 도입된 연도와 Xamarin.Forms의 약어로 사용되는 forms라는 단어가 들어갑니다.

두번째 네임 스페이스는 규칙에 따라 접두사 x와 연결되며 Microsoft에 속하는 것입니다. 이 네임스페이스는 XAML 고유의 공통 element와 attribute를 나타냅니다. winfx라는 단어는 WPF와 XAML을 도입한 .NET Framework 3.0에 사용된 이름입니다. 2009년은 특정 XAML 사양을 뜻하는데,그것은 2006년에 나온 오리지널 XAML 사양을 기반으로 만들어진 특정 attribute와 element 컬렉션입니다. 그러나 Xamarin.Forms는 2009 사양의 attribute, element 중 일부만 구현합니다.

아래에 보면 XAML 내장 attribute인 Class가 있습니다. x 접두어가 이 네임스페이스의 거의 전반에서 사용되기 때문에 이 특성은 일반적으로 x:Class라고하며 "x class"라고 읽습니다.

x:Class attribute는 XAML 파일의 root element에만 나타날 수 있습니다. .NET 네임스페이스와 파생 클래스의 이름을 지정합니다. 이 파생 클래스의 기본 클래스는 root element입니다. 다시 말해, 이 x:Class 사양은 CodePlusXaml 네임 스페이스의 MainPage 클래스가 ContentPage에서 파생되었다는 뜻입니다. 이는 MainPage.xaml.cs 파일에있는 MainPage 클래스 정의와 정확히 동일한 정보입니다.

Xaml 파일에서 이 ContentPage에 내용을 추가해 보도록 합시다. 이 작업을 수행하려면 XAML 파일에서 ContentPage.Content property-element 태그 사이에 내용을 적어 Content 프로퍼티를 설정해야 합니다. StackLayout으로 시작하여 Children 프로퍼티에 Label을 추가해 보겠습니다.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:CodePlusXaml"
             x:Class="CodePlusXaml.MainPage">
    <ContentPage.Content>
        <StackLayout>
            <StackLayout.Children>
                <Label Text="Hello from XAML!"
                       IsVisible="True"
                       Opacity="0.75"
                       HorizontalTextAlignment="Center"
                       VerticalOptions="CenterAndExpand"
                       TextColor="Blue"
                       BackgroundColor="#FF8080"
                       FontSize="Large"
                       FontAttributes="Bold,Italic" />
            </StackLayout.Children>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
cs

이것이 이번 챕터 시작할 때 보셨던 XAML Label입니다.

이제 코드로만 작업했던 때와 마찬가지로 App 클래스를 변경하여 이 페이지를 인스턴스화 해야 합니다.(바뀐 탬플릿에서는 App 클래스를 변경할 필요가 없습니다. Xaml페이지를 새로 추가한게 아니고 원래 포함되어 있었기 때문입니다)

이제 앱을 빌드하고 배포할 수 있습니다. 그런 이후에 지금까지 마주쳤던 궁금증 몇 가지를 해소할 수 있을 것입니다.

비주얼 스튜디오의 솔루션 탐색기에서 CodePlusXaml 프로젝트를 선택하고 툴 창에서 모든 파일 표시 아이콘을 찾아 켜 봅시다.

자마린 스튜디오에서는 Solution 파일 리스트에서 전체 솔루션 드랍다운 메뉴를 활성화시키고 Diplay Options > Show All files를 선택합니다.

솔루션 탐색기에서 obj 폴더를 찾고 그 안에 있는 Debug 폴더를 찾습니다. 그 안에서 CodePlusXaml.MainPage.xaml.g.cs라는 파일을 찾습니다. 파일 이름에 g가 들어 있는데, 이 g는 generated를 뜻합니다. 아래에 완전히 툴에 의해 생성(generated)되었음을 알려주는 주석이 달려 있는 것이 보일 겁니다.

//------------------------------------------------------------------------------
// <auto-generated>
//     이 코드는 도구를 사용하여 생성되었습니다.
//     런타임 버전:4.0.30319.42000
//
//     파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
//     이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------
namespace CodePlusXaml {
    using System;
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    
    
    public partial class MainPage : global::Xamarin.Forms.ContentPage {
        
        [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG""0.0.0.0")]
        private void InitializeComponent() {
            this.LoadFromXaml(typeof(MainPage));
        }
    }
}
cs


빌드 과정동안 XAML 파일은 파싱되어 이런 코드가 생성됩니다. 클래스 선언부에 partial이 선언되어 있고, ContentPage를 상속받았으며, InitializeComponent라는 메소드가 있는 것을 알 수 있습니다.

다시 말해 이 파일이 바로 CodePlusXaml.MainPage.xaml.cs 뒷 배경(code-behind) 파일에 딱 들어맞습니다. CodePlusXaml.MainPage.xaml.g.cs가 생성된 후에  두 파일은 보통 C# 파일들 처럼 하나의 파일로 컴파일 됩니다.

런타임에 App 클래스는 MainPage 클래스를 인스턴스화 합니다. MainPage 생성자는(code-behind 파일에 정의되어 있음) InitializeComponent 메소드(generated된 파일에 정의되어 있음)를 호출하고, InitializeComponentLoadFromXaml를 호출합니다. 이것은 Xamarin.Forms.Xaml 어셈블리의 Extensions 클래스에 정의된 View의 확장 메서드입니다. LoadFromXaml이 하는 일은 XAML을 컴파일할 것을 선택했느냐 아니냐에 따라 달라집니다(다음에 설명할 것입니다). 그러나 InitializeComponent 메서드가 반환될 때, MainPage 생성자의 코드에서 모든 것이 인스턴스화되고 초기화된 것과 마찬가지로 페이지 전체가 제 위치에 있게 됩니다.

code-behind 파일의 생성자에서 페이지에 내용을 계속 추가하는 것도 가능하지만, InitializeComponent 호출이 반환된 후에만 ​​가능합니다. 이 장의 앞부분에서 나온 코드를 사용하여 다른 Label을 만들어 보겠습니다.

namespace CodePlusXaml
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            Label label = new Label
            {
                Text = "Hello form Code!",
                IsVisible = true,
                Opacity = 0.75,
                HorizontalTextAlignment = TextAlignment.Center,
                VerticalOptions = LayoutOptions.CenterAndExpand,
                TextColor = Color.Blue,
                BackgroundColor = Color.FromRgb(255128128),
                FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                FontAttributes = FontAttributes.Bold | FontAttributes.Italic
            };
            (Content as StackLayout).Children.Insert(0, label);
        }
    }
}
cs

생성자는 페이지의 Content 프로퍼티로 설정된 StackLayout에 액세스하여 맨 위에 Label을 삽입합니다(다음 장에서 x:Name attribute를 사용하여 XAML 파일에서 개체를 참조 할 수 있는 더 좋은 방법을 알아볼 예정입니다). InitializeComponent 호출 이전에 Label을 만들 수는 있지만 그 시점에서 StackLayout에 추가할 수는 없는데, InitializeComponentStackLayout(및 다른 모든 XAML 요소)을 인스턴스화하기 때문입니다. 결과는 다음과 같습니다.


텍스트 내용을 제외하면 두 개의 버튼은 동일합니다.

XAML 파서가 생성한 generated 코드 파일을 조사하는데 많은 시간을 할애할 필요는 없습니다만, 런타임이나 빌드 과정에서 XAML 파일이 어떻게 동작하는가를 이해하는데는 큰 도움이 됩니다. 또한, 때로 XAML 파일은 LoadFromXaml이 호출될 때, 런타임 익셉션을 일으키는데, 그러면 여러분은 generated 코드를 들여다보아야 할 것이므로 그것이 무엇인지 정도는 알고 있어야 할 것입니다.

The XAML complier

빌드 과정에서 XAML을 컴파일 할 것인지를 결정할 수 있습니다. XAML을 컴파일하면 빌드 프로세스 중에 유효성 검사를 수행하고, 실행 파일의 크기를 줄이며, 로드 시간을 향상시킵니다. 그러나 비 컴파일 방식보다 다소 새로운 것이므로 때로 문제를 일으킬 수 있습니다.

어플리케이션 내의 모든 XAML 파일을 컴파일하도록 지정하려면 코드 파일의 어딘가에 다음 어셈블리 attribute를 넣어줘야 합니다. 가장 편리한 장소는 프로젝트의 Properties 폴더에 있는 Assembly.cs 파일입니다.
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
cs

다른 C# 파일 내에 넣을 수도 있지만, 이것은 어샘블리 attribute이기 때문에 namespace 블럭 안에 있어서는 안 됩니다. 그 후 Xamarin.Forms.Xaml을 사용하기 위해 using 지시문이 필요합니다.

또, 특정 클래스의 XAML 파일을 컴파일하도록 지정할 수 있습니다.
namespace CodePlusXaml
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            ...
        }
    }
}
cs

XamlCompilationOptions 열거형에는 Compile, Skip의 두 가지 멤버가 있어서 어셈블리 attribute으로 XamlCompilation을 사용하여 프로젝트의 모든 클래스에 대해 XAML 컴파일을 허용하고, Skip 멤버를 사용하여 개별 클래스에 대해 XAML 컴파일을 건너뛰게 만들 수 있습니다.

XAML을 컴파일하도록 선택하지 않으면 전체 XAML 파일이 4 장의 BlackCat 프로그램에 있는 Edgar Allan Poe 이야기와 마찬가지로 embeded resource로서 실행 파일에 바인딩됩니다. 실제로 GetManifestResourceStream 메서드를 사용하여 런타임에 XAML 파일에 액세스 할 수 있습니다. 이는 InitializeComponent에서 LoadFromXam을 호출하는 것과 비슷합니다.  XAML 파일을 로드하고 파싱하여 이미 존재하는 루트 요소를 제외한 XAML 파일의 모든 요소를 ​​인스턴스화하고 초기화합니다.

XAML을 컴파일 하도록 선택하면 이 과정이 다소 간소화되지만, LoadFromXaml 메서드는 여전히 모든 요소를 ​​인스턴스화하고 시각적 트리를 작성해야만 합니다.

댓글 없음:

댓글 쓰기