전체 페이지뷰

2017년 5월 18일 목요일

Chapter 10. XAML markup extensions, part 2

Resource dictionaries

Xamarin.Forms에는 객체와 값을 공유하는 두 번째 접근법도 있습니다. 이 접근법은 x:Static 마크업 확장보다 약간 우회적인 방법이며, 공유 객체와 이를 사용하는 시각적 element를 모두 XAML에서 표현할 수 있기 때문에 좀 더 다양합니다.

VisualElementResourceDictionary 타입인 Resources라는 프로퍼티를 정의하는데 이것은 string타입의 key와 object 타입의 value를 가진 dictionary입니다.

XAML에서 직접 이 dictionary에 항목을 추가할 수 있고, StaticResourceDynamicResource 마크업 확장을 사용하여 XAML에서 액세스할 수 있습니다.

x:StaticStaticResource는 약간 비슷한 이름을 가지고 있지만 매우 다릅니다. x:Static은 상수, 정적 필드, 정적 속성 또는 열거형 멤버를 참조하는 반면, StaticResourceResourceDictionary에서 객체를 검색합니다.

x:Static 마크업 확장은 XAML 고유기능이어서 x 접두사와 함께 XAML에 표시되지만, StaticResource DynamicResource 마크업 확장은 다릅니다. 이들은 WPF(Windows Presentation Foundation)의 오리지널 XAML 구현의 일부였으며, StaticResource는 Silverlight, Windows Phone 7 및 8, Windows 8 및 10에서도 지원됩니다.

StaticResource를 대부분의 용도에 사용하고, 일부 특수 앱의 경우 DynamicResource를 사용하므로 StaticResource부터 시작하겠습니다.

StaticResource for most purposes


StackLayout에 세 개의 Button을 정의했다고 가정해 봅시다.

<StackLayout>
    <Button Text=" Carpe diem " 
            HorizontalOptions="Center" 
            VerticalOptions="CenterAndExpand" 
            BorderWidth="3" 
            TextColor="Red" 
            FontSize="Large">
        <Button.BackgroundColor>
            <OnPlatform x:TypeArguments="Color" 
                        Android="#404040" />
        </Button.BackgroundColor>
        <Button.BorderColor>
            <OnPlatform x:TypeArguments="Color" 
                        Android="White" 
                        WinPhone="Black" />
        </Button.BorderColor>
    </Button>
    
    <Button Text=" Sapere aude " 
            HorizontalOptions="Center" 
            VerticalOptions="CenterAndExpand" 
            BorderWidth="3" 
            TextColor="Red" 
            FontSize="Large">
        <Button.BackgroundColor>
            <OnPlatform x:TypeArguments="Color" 
                        Android="#404040" />
        </Button.BackgroundColor>
        
        <Button.BorderColor>
            <OnPlatform x:TypeArguments="Color" 
                        Android="White" 
                        WinPhone="Black" />
        </Button.BorderColor>
    </Button>
    
    <Button Text=" Discere faciendo "
            HorizontalOptions="Center" 
            VerticalOptions="CenterAndExpand" 
            BorderWidth="3" 
            TextColor="Red" 
            FontSize="Large">
        <Button.BackgroundColor>
            <OnPlatform x:TypeArguments="Color" 
                        Android="#404040" />
        </Button.BackgroundColor>
        <Button.BorderColor>
            <OnPlatform x:TypeArguments="Color" 
                        Android="White" 
                        WinPhone="Black" />
        </Button.BorderColor>
    </Button>
</StackLayout>
cs

물론 좀 비현실적이죠. 버튼에 연결된 Clicked 이벤트도 없고, 보통 라틴어로 버튼 텍스트를 달지도 않으니까 말입니다. 어쨌거나 이건 이렇게 보일 겁니다.



텍스트 말고도 세 개의 버튼 모두 동일한 프로퍼티가 동일한 값으로 설정됩니다. 이런 반복적 마크업은 프로그래머에게 몹시 거슬립니다. 뿐만 아니라 유지, 변경하기가 어렵습니다.

결국 style을 사용하여 반복적 마크업을 줄이는 방법을 알게 될 것입니다. 그러나 지금까지 목표는 마크업을 더 짧게하는 것이 아니라 값을 한 곳에서 통합하여 TextColor 속성을 빨강에서 파랑으로 변경하려는 것과 같은 경우 세 군데가 아닌 한 군데에서 하려는 것이었습니다.

물론 코드에서 값을 정의하고 x:Static을 사용하는 것도 가능합니다. 그러나 값을 리소스 사전에 저장하여 XAML 상에서 모든 작업을 수행해 봅시다. VisualElement에서 파생되는 모든 클래스에는 ResourceDictionary 타입의 Resources 프로퍼티가 있습니다. 페이지 전체에서 사용되는 리소스는 일반적으로 ContentPageResources 컬렉션에 저장됩니다.

첫번째 단계는 ContentPageResources 프로퍼티를 property-element로 표현하는 것입니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ResourceSharing"
             x:Class="ResourceSharing.MainPage">
    <ContentPage.Resources>
        
    </ContentPage.Resources>
</ContentPage>
cs

만약 property-element 태그를 사용하여 페이지에서 Padding 프로퍼티를 정의하려 한다고 해도, 순서는 중요하지 않습니다.

Resources 프로퍼티의 디폴트는 null이므로 ResourceDictionary를 명시적으로 인스턴스화해야 합니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ResourceSharing"
             x:Class="ResourceSharing.MainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            
        </ResourceDictionary>        
    </ContentPage.Resources>
</ContentPage>
cs

ResourceDictionary 태그 사이에는 하나 이상의 개체 또는 값을 정의합니다. 사전의 각 항목은 XAML x:Key attribute로 지정한 dictionary key로 구분해줘야 합니다.

예를 들어, LayoutOptions에 horizontal 옵션을 정의했다는 key가 있는 dictionary의 구문은 다음과 같습니다.

<LayoutOptions x:Key="horzOptions">Center</LayoutOptions>
cs

이것은 LayoutOptions 값이므로, XAML 파서는 LayoutOptionsConverter 클래스에 액세스하여 태그의 내용("Center"라는 텍스트)을 변환합니다.

딕셔너리에 LayoutOptions 값을 저장하는 두 번째 방법은 XAML 파서가 구조를 인스턴스화하고 attribute로부터 LayoutOptions 프로퍼티를 설정하도록 하는 것입니다.

<LayoutOptions x:Key="vertOptions" 
               Alignment="Center" 
               Expands="True" />
cs

BorderWidth 프로퍼티는 double 타입이므로 XAML 2009 사양에 정의된 x:Double 데이터 형식 element가 이상적입니다.

<x:Double x:Key="borderWidth">3</x:Double>
cs

색상의 텍스트 표현을 content로 하여 리소스 딕셔너리에 Color 값을 저장할 수 있습니다. XAML 파서는 텍스트 변환에 일반 ColorTypeConverter를 사용합니다.

<Color x:Key="textColor">Red</Color>
cs

해시 부호 뒤에 16 진수 ARGB 값을 지정할 수도 있습니다.

get-only이므로 R,G,B 프로퍼티를 설정해서는 Color 값을 초기화할 수 없습니다. 그러나 x:Arguments를 사용하여 Color 생성자를 불러올 수도 있고 x:FactoryMethodx:Arguments를 사용하는 Color 팩토리 메서드 중 하나를 사용하여 생성자를 호출할 수도 있습니다.

<Color x:Key="textColor" 
       x:FactoryMethod="FromHsla">
    <x:Arguments>
        <x:Double>0</x:Double>
        <x:Double>1</x:Double>
        <x:Double>0.5</x:Double>
        <x:Double>1</x:Double>
    </x:Arguments>
</Color>
cs

x:Keyx:FactoryMethod attribute가 쓰였습니다.

위에 보여드린 세 개의 버튼의 BackgroundColorBorderColor 프로퍼티는 OnPlatform 클래스의 값으로 설정됩니다. 다행히 OnPlatform 객체도 딕셔너리에 넣을 수 있습니다.

<OnPlatform x:Key="backgroundColor" 
            x:TypeArguments="Color" 
            Android="#404040" />
<OnPlatform x:Key="borderColor" 
            x:TypeArguments="Color" 
            Android="White" 
            WinPhone="Black" />
cs

x:Key x:TypeArguments attribute가 쓰였습니다..

FontSize 프로퍼티의 딕셔너리 아이템은 조금 문제가 있습니다. FontSize 속성은 double 타입이므로 딕셔너리에 실제 숫자 값을 저장하는 경우는 문제가 없습니다. 그러나 "Large"라는 단어를 double로 딕셔너리에 저장할 수는 없습니다. "Large"문자열이 FontSize attribute로 설정된 경우에만 XAML 파서가 FontSizeConverter를 사용합니다. 이런 이유로, FontSize 항목은 문자열로 저장해야 합니다.

<x:String x:Key="fontSize">Large</x:String>
cs

지금까지의 전체 딕셔너리는 다음과 같습니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ResourceSharing"
             x:Class="ResourceSharing.MainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <LayoutOptions x:Key="horzOptions">Center</LayoutOptions>
            
            <LayoutOptions x:Key="vertOptions" 
                           Alignment="Center" 
                           Expands="True" />
            
            <x:Double x:Key="borderWidth">3</x:Double>
            
            <Color x:Key="textColor">Red</Color>
            
            <OnPlatform x:Key="backgroundColor" 
                        x:TypeArguments="Color" 
                        Android="#404040" />
            
            <OnPlatform x:Key="borderColor" 
                        x:TypeArguments="Color" 
                        Android="White" 
                        WinPhone="Black" />
            
            <x:String x:Key="fontSize">Large</x:String>
        </ResourceDictionary>
    </ContentPage.Resources>
</ContentPage>
cs

이것을 페이지의 resource section이라고도 부릅니다. 실제 프로그래밍에서 많은 XAML 파일이 리소스 섹션으로 시작합니다.

StaticResourceExtension에서 지원하는 StaticResource 마크업 확장을 사용하여 딕셔너리의 항목을 참조할 수 있습니다. 이 클래스는 딕셔너리의 키로서 Key라는 프로퍼티를 정의합니다. property-element 태그 내에서 StaticResourceExtension을 element로 사용하거나, StaticResourceExtension 혹은 StaticResource를 중괄호 내에서 사용할 수 있습니다. 중괄호 구문을 사용하는 경우 KeyStaticResourceExtension의 콘텐츠 속성이므로 생략할 수 있습니다.

ResourceSharing 프로젝트의 전체 XAML 파일은 그 세 가지 옵션을 잘 보여줍니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ResourceSharing"
             x:Class="ResourceSharing.MainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <LayoutOptions x:Key="horzOptions">Center</LayoutOptions>
            
            <LayoutOptions x:Key="vertOptions" 
                           Alignment="Center" 
                           Expands="True" />
            
            <x:Double x:Key="borderWidth">3</x:Double>
            
            <Color x:Key="textColor">Red</Color>
            
            <OnPlatform x:Key="backgroundColor" 
                        x:TypeArguments="Color" 
                        Android="#404040" />
            
            <OnPlatform x:Key="borderColor" 
                        x:TypeArguments="Color" 
                        Android="White" 
                        WinPhone="Black" />
            
            <x:String x:Key="fontSize">Large</x:String>
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout>
        <Button Text="Carpe diem">
            <Button.HorizontalOptions>
                <StaticResourceExtension Key="horzOptions" />
            </Button.HorizontalOptions>
            <Button.VerticalOptions>
                <StaticResourceExtension Key="vertOptions" />
            </Button.VerticalOptions>
            <Button.BorderWidth>
                <StaticResourceExtension Key="borderWidth" />
            </Button.BorderWidth>
            
            <Button.TextColor>
                <StaticResourceExtension Key="textColor" />
            </Button.TextColor>
            
            <Button.BackgroundColor>
                <StaticResourceExtension Key="backgroundColor" />
            </Button.BackgroundColor>
            
            <Button.BorderColor>
                <StaticResourceExtension Key="borderColor" />
            </Button.BorderColor>
            
            <Button.FontSize>
                <StaticResourceExtension Key="fontSize" />
            </Button.FontSize>
        </Button>
        <Button Text=" Sapere aude "
                HorizontalOptions="{StaticResource Key=horzOptions}"
                VerticalOptions="{StaticResource Key=vertOptions}" 
                BorderWidth="{StaticResource Key=borderWidth}"
                TextColor="{StaticResource Key=textColor}" 
                BackgroundColor="{StaticResource Key=backgroundColor}" 
                BorderColor="{StaticResource Key=borderColor}" 
                FontSize="{StaticResource Key=fontSize}" />
        <Button Text=" Discere faciendo " 
                HorizontalOptions="{StaticResource horzOptions}" 
                VerticalOptions="{StaticResource vertOptions}" 
                BorderWidth="{StaticResource borderWidth}" 
                TextColor="{StaticResource textColor}" 
                BackgroundColor="{StaticResource backgroundColor}" 
                BorderColor="{StaticResource borderColor}" 
                FontSize="{StaticResource fontSize}" />
    </StackLayout>
</ContentPage>   
cs

세 번째 버튼에서 가장 간단한 구문이 가장 많이 사용되는데, 실제로 구문이 매우 보편적이어서 많은 XAML 개발자가 다른 형식에는 전혀 익숙하지 않을 수 있습니다. StaticResource 버전을 Key 프로퍼티와 함께 사용하는 경우에는 x 접두어를 붙이지 마십시오. x:Key attribute는 ResourceDictionary의 딕셔너리 키 정의에만 사용됩니다.

딕셔너리의 객체와 값은 모든 StaticResource 참조간에 공유됩니다. 그점이 지금의 예에서는 분명하게 나와있지는 않지만 꼭 기억해둘 내용입니다. 예를 들어 resource dictionary 내에 Button 객체를 저장한다고 가정해 봅시다.

<ContentPage.Resources>
    <ResourceDictionary>
        <Button x:Key="button" 
                Text="Shared Button?" 
                HorizontalOptions="Center" 
                VerticalOptions="CenterAndExpand" 
                FontSize="Large" />
    </ResourceDictionary>
</ContentPage.Resources>
cs

이 경우 StaticResourceExtension element 구문을 사용하여 StackLayoutChildren 컬렉션에 추가함으로써 페이지에서 Button 객체를 사용할 수 있습니다.

<StackLayout>
    <StaticResourceExtension Key="button" />
</StackLayout>
cs

그러나 StackLayout에 또 하나를 추가할 때 동일한 딕셔너리 아이템을 사용할 수 없습니다.

<StackLayout>
    <StaticResourceExtension Key="button" />
    <StaticResourceExtension Key="button" />
</StackLayout>
cs

이것은 잘못된 구문이며 이 두 요소는 동일한 Button 객체를 참조하게 됩니다. 하나의 시각 객체는 한 군데에만 존재할 수 있습니다.

이런 이유로 시각 객체는 보통 resource dictionary에 저장하지 않습니다. 같은 프로퍼티를 가지는 여러 개의 시각 요소를 한 페이지에 사용하려면 보통 챕터 12에서 설명하게 될 Style을 사용합니다.


A tree of dictionaries


ResourceDictionary 클래스는 다른 딕셔너리와 동일한 규칙이 적용됩니다: 딕셔너리의 모든 아이템은 키를 가져야 하고, 중복되는 키는 허용되지 않는다.

그러나 VisualElement의 모든 인스턴스는 잠재적으로 각자 고유의 리소스 딕셔너리를 가지고 있기 때문에, 하나의 페이지에 여러 개의 딕셔너리가 존재할 수 있으며 각 사전의 모든 키가 고유한 경우 다른 사전의 같은 키를 사용할 수 있습니다. 시각적 트리의 모든 시각적 요소는 자체 딕셔너리를 가질 수 있지만, 실제로는 리소스 딕셔너리가 여러 요소에 적용될수 있다는 의미일 뿐이어서 리소스 딕셔너리는 일반적으로 Layout 또는 Page 객체에 정의되어 있습니다.

지금 소개해 드릴 기술을 사용하면 다른 딕셔너리로부터 키들을 오버라이드하녀 딕셔너리 키들로 트리구조를 구성할 수 있습니다. 이 과정을 ResourceTrees 프로젝트로 보여 드리겠습니다. XAML 파일에서 horzOptions, vertOptions, textColor라는 키가 존재하는 리소스를 정의하는 Resources 딕셔너리가 나와 있습니다.

두 번째 Resources 사전은 textColorFontSize라는 리소스에 대해 내부 StackLayout에 연결됩니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ResourceTrees"
             x:Class="ResourceTrees.MainPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <LayoutOptions x:Key="horzOptions">Center</LayoutOptions>
            <LayoutOptions x:Key="vertOptions"
                           Alignment="Center"
                           Expands="True" />
            <OnPlatform x:Key="textColor"
                        x:TypeArguments="Color"
                        iOS="Red"
                        Android="Pink"
                        WinPhone="Blue" />
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout>
        <Button Text=" Carpe Diem "
                HorizontalOptions="{StaticResource horzOptions}"
                VerticalOptions="{StaticResource vertOptions}"
                BorderWidth="{StaticResource borderWidth}"
                TextColor="{StaticResource textColor}"
                BackgroundColor="{StaticResource backgroundColor}"
                BorderColor="{StaticResource borderColor}"
                FontSize="{StaticResource fontSize}" />
        <StackLayout>
            <StackLayout.Resources>
                <ResourceDictionary>
                    <Color x:Key="textColor">Default</Color>
                    <x:String x:Key="fontSize">Default</x:String>
                </ResourceDictionary>
            </StackLayout.Resources>
            <Label Text="the first of two labels"
                   HorizontalOptions="{StaticResource horzOptions}"
                   TextColor="{StaticResource textColor}"
                   FontSize="{StaticResource fontSize}" />
            
            <Button Text=" Sapere aude "
                    HorizontalOptions="{StaticResource horzOptions}" 
                    BorderWidth="{StaticResource borderWidth}" 
                    TextColor="{StaticResource textColor}" 
                    BackgroundColor="{StaticResource backgroundColor}" 
                    BorderColor="{StaticResource borderColor}" 
                    FontSize="{StaticResource fontSize}" />
            <Label Text="The second of two labels" 
                   HorizontalOptions="{StaticResource horzOptions}" 
                   TextColor="{StaticResource textColor}" 
                   FontSize="{StaticResource fontSize}" />
        </StackLayout>
        <Button Text=" Discere faciendo " 
                HorizontalOptions="{StaticResource horzOptions}" 
                VerticalOptions="{StaticResource vertOptions}" 
                BorderWidth="{StaticResource borderWidth}" 
                TextColor="{StaticResource textColor}" 
                BackgroundColor="{StaticResource backgroundColor}" 
                BorderColor="{StaticResource borderColor}" 
                FontSize="{StaticResource fontSize}" />
    </StackLayout>
</ContentPage>
cs

내부 StackLayoutResources 딕셔너리는 아래 스크린샷의 중간 아이템인 StackLayout 내부에만 적용됩니다.


XAML 파서가 시각적 요소의 attribute에서 StaticResource를 발견하면 딕셔너리 키에 대한 검색이 시작됩니다. 먼저 ResourceDictionary에서 해당 시각 요소를 찾고 키를 찾을 수 없는 경우 시각적 요소의 부모 ResourceDictionary에서 검색하며, 페이지의 ResourceDictionary에 도달 할 때까지 시각적 트리를 통해 거슬러 올라갑니다.

그런데 뭔가를 빠뜨린 것 같습니다! borderWidth, backgroundColor, borderColor, fontSize에 대한 페이지의 ResourceDictionary 항목은 어디에 있습니까? MainPage.xaml 파일에는 없군요.

그 항목은 다른 곳에 있습니다. 모든 응용 프로그램의 App 클래스가 파생되는 클래스인 Application에서 ResourceDictionary 유형의 Resources 프로퍼티를 정의하고 있습니다. 이는 특정 페이지나 레이아웃에 국한되는 것이 아니라 전체 앱에 적용되는 리소스를 정의할 때 유용합니다. XAML 파서는 시각적 트리를 검색하고 해당 키가 페이지의 ResourceDictionary에 없으면 Application 클래스에서 정의한 ResourceDictionary를 최종적으로 체크합니다. 거기에서도 발견되지 않는 경우에만 StaticResource key-not-found 익셉션인 XamlParseException이 발생합니다.

App 클래스의 ResourceDictionary 객체에 아이템을 추가하는 방법은 다음의 두 가지가 있습니다.

첫 번째 방법은 App 생성자의 코드에 아이템을 추가하는 것입니다. 이것은 메인 ContentPage 클래스를 인스턴스화하기 전에 이루어져야 합니다.

public partial class App : Application
{
    public App()
    {
        Resources = new ResourceDictionary();
        Resources.Add("borderWidth"3.0);
        Resources.Add("fontSize""Large");
        Resources.Add("backgroundColor",
            Device.OnPlatform(Color.Default, 
            Color.FromRgb(0x400x400x40), 
            Color.Default));
        Resources.Add("borderColor",
            Device.OnPlatform(Color.Default, 
            Color.White, 
            Color.Black));
        InitializeComponent();
        MainPage = new ResourceTrees.MainPage();
    }
    ...
}
cs
참고: 전에도 업급했다시피 현재 기준으로(2017.5.18) OnPlatform은 obsolete 처리되어 더 이상 사용하지 말고 switch(RuntimePlatform)을 써야 하지만 그냥 책과 똑같이 작성했습니다. 참고 바랍니다. 그리고 덧붙여서 안드로이드의 배경색도 아이폰이나 윈도우 폰처럼 하얀색으로 바뀌었습니다.

그러나, App 클래스도 자신만의 XAML 파일을 가질 수 있습니다...(원래는 App클래스에 기본 생성되는 XAML 파일이 없었기 때문에 여기서부터 한참 App 클래스에 XAML을 추가하는 법을 설명하고 있습니다만 현재 템플릿에서는 App.xaml이 기본 생성되므로 그 부분은 생략하도록 하겠습니다)

이 App.xaml 파일의 Application.Resources 내부의 property-element 태그 안에서 ResourceDictionary를 인스턴스화하고 아이템을 추가할 수 있습니다.

<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ResourceTrees.App">
    <Application.Resources>
        <ResourceDictionary>
            <x:Double x:Key="borderWidth">3</x:Double>
            <x:String x:Key="fontSize">Large</x:String>
            <OnPlatform x:Key="backgroundColor" 
                        x:TypeArguments="Color" 
                        Android="#404040" />
            
            <OnPlatform x:Key="borderColor" 
                        x:TypeArguments="Color" 
                        Android="White" 
                        WinPhone="Black" />
        </ResourceDictionary>
    </Application.Resources>
</Application>
cs

code-behind 파일의 생성자는 InitializeComponent를 호출하여 런타임에 App.xaml 파일을 파싱하고 아이템을 딕셔너리에 추가합니다. 이 작업은 MainPage 클래스를 인스턴스화하고 MainPage 프로퍼티로 설정하는 일반적인 작업 전에 이루어져야 합니다.

public partial class App : Application
{
    public App()
    {            
        InitializeComponent();
        MainPage = new ResourceTrees.MainPage();
    }
    protected override void OnStart()
    {
        // Handle when your app starts
    }
    protected override void OnSleep()
    {
        // Handle when your app sleeps
    }
    protected override void OnResume()
    {
        // Handle when your app resumes
    }
}
cs

라이프 사이클 이벤트를 추가하는 것은 옵션입니다.

페이지 클래스를 인스턴스화 하기 전에 InitializeComponent를 호출해야 합니다. 페이지 클래스의 생성자는 InitializeComponent를 호출하여 XAML 파일을 파싱하고 StaticResource 마크업 확장은 App 클래스의 Resources 컬렉션에 액세스해야 합니다.

모든 Resources 딕셔너리에는 스코프가 있습니다. App 클래스 딕셔너리의 경우 그 범위가 전체 응용 프로그램입니다. ContentPage의 경우는 전체 페이지입니다. 그리고 StackLayoutResources 딕셔너리의 경우는 내부의 children에 한정됩니다. 따라서 딕셔너리의 용도에 따라 정의하고 저장해야 합니다. 응용 프로그램 전체에서 리소스를 사용하려면 App 클래스의 Resources 딕셔너리를 사용해야 하고, 한 페이지 전체에서 사용하려면 ContentPageResources 사전을 이용해야 합니다. 그러나 페이지의 일부에서만 사용하고 싶은 경우 시각적 트리의 깊은 곳에 추가적으로 딕셔너리를 정의하면 됩니다.

12 장에서 보게 되겠지만, Resources 딕셔너리의 가장 중요한 아이템은 일반적으로 Style 타입의 객체입니다. 일반적인 경우, 앱 전체에 해당하는 Style 객체, 페이지의 Style 객체, 시각 트리상에서 더 작은 부분에 해당하는 Style 객체가 존재합니다.


DynamicResource for special purposes


StaticResource 대신 DynamicResource를 사용할 수도 있습니다. 위의 예제에서 StaticResource 대신 DynamicResource를 사용해도 프로그램이 동일하게 실행됩니다. 그러나 두 개의 마크업 확장은 매우 다릅니다. StaticResource는 XAML이 파싱되고 페이지가 만들어지는 동안 한 번만 딕셔너리 아이템에 액세스합니다. 그러나 DynamicResource는 딕셔너리 키와 해당 그 키에 설정된 프로퍼티 사이에 링크를 유지합니다. 키가 참조하는 리소스 딕셔너리의 아이템이 변경되면 DynamicResource는 변경 사항을 감지하고 새 값을 프로퍼티에 설정합니다.

의심스럽습니까? 그럼 시험해 봅시다. DynamicVsStatic 프로젝트에는 currentDateTime이라는 키가 있는 문자열 형식의 리소스 아이템을 정의하는 XAML 파일이 있습니다(실제 키에 해당하는 아이템은 "Not actually a DateTime"이지만..)

이 딕셔너리 아이템은 XAML 파일에서 네 번 참조되지만 참조 중 하나는 주석 처리됩니다. 처음 두 예제에서 StaticResourceDynamicResource를 사용하여 LabelText 프로퍼티를 설정합니다. 다음 두 예에서는 Span 객체의 Text 프로퍼티가 비슷하게 설정되는데 다만  DynamicResource 사용이 주석처리 되어 있습니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DynamicVsStatic"
             x:Class="DynamicVsStatic.MainPage"
             Padding="5,0">
    <ContentPage.Resources>
        <ResourceDictionary>
            <x:String x:Key="currentDateTime">Not actually a DateTime</x:String>
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout>
        <Label Text="Static Resource on Label.Text:"
               VerticalOptions="EndAndExpand"
               FontSize="Medium" />
        <Label Text="{StaticResource currentDateTime}" 
               VerticalOptions="StartAndExpand" 
               HorizontalTextAlignment="Center" 
               FontSize="Medium" />
        
        <Label Text="DynamicResource on Label.Text:"
               VerticalOptions="EndAndExpand" 
               FontSize="Medium" />
        
        <Label Text="{DynamicResource currentDateTime}" 
               VerticalOptions="StartAndExpand" 
               HorizontalTextAlignment="Center" 
               FontSize="Medium" />
        
        <Label Text="StaticResource on Span.Text:" 
               VerticalOptions="EndAndExpand"
               FontSize="Medium" />
        <Label VerticalOptions="StartAndExpand" 
               HorizontalTextAlignment="Center"
               FontSize="Medium">
            <Label.FormattedText>
                <FormattedString>
                    <Span Text="{StaticResource currentDateTime}" />
                </FormattedString>
            </Label.FormattedText>
        </Label>
        <!-- This raises a run-time exception! -->
        <!--<Label Text="DynamicResource on Span.Text:" 
               VerticalOptions="EndAndExpand" 
               FontSize="Medium" /> 
        
        <Label VerticalOptions="StartAndExpand" 
               HorizontalTextAlignment="Center" 
               FontSize="Medium">             
            <Label.FormattedText> 
                <FormattedString>
                    <Span Text="{DynamicResource currentDateTime}" /> 
                </FormattedString> 
            </Label.FormattedText> 
        </Label>-->
    </StackLayout>
</ContentPage>
cs

아마도 currentDateTime 딕셔너리 아이템에 대한 세 가지 참조가 모두 "Not actually a DateTime"으로 출력될 것으로 기대할지도 모르겠습니다. 그러나 code-behind 파일에서 타이머를 시작합니다. 매 초마다 타이머 콜백은 딕셔너리 아이템을 실제 DateTime 값을 나타내는 새 문자열로 바꿉니다.

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        Device.StartTimer(TimeSpan.FromSeconds(1),
            () =>
            {
                Resources["currentDateTime"= DateTime.Now.ToString();
                return true;
            });
    }
}
cs

결과적으로 StaticResource로 설정된 Text 프로퍼티는 동일하게 유지되지만 DynamicResource가 있는 프로퍼티는 매 초마다 변경되어 딕셔너리에 새 아이템을 반영합니다.



한가지 차이점이 더 있습니다. 사전에 지정된 키에 매칭되는 항목이 없으면 StaticResource는 런타임 예외를 발생 시키지만 DynamicResource는 발생시키지 않습니다.

DynamicVsStatic 프로젝트의 끝에서 주석 처리를 제거하면 Text 프로퍼티를 찾을 수 없다는 런타임 예외가 발생할 것입니다. 예외가 발생하는 것은 달갑지 않지만, 거기에서 우리는 매우 실제적인 차이점을 알 수 있습니다.

문제는 LabelSpanText 프로퍼티가 크게 다른 방식으로 정의되어 있으며, 그 차이가 DynamicResource에 중요하다는 것입니다. 이 차이전에 대해서는 다음 챕터 "The bindable infrastructure"에서 살펴 보겠습니다.



Lesser-used markup extensions

아래 세 개의 마크업 확장은 많이 사용되지 않습니다.

  • x:Null
  • x:Type
  • x:Array

x:Null은 프로퍼피를 null로 설정할 때 사용되며, 문법은 다음과 같습니다.
<SomeElement SomeProperty="{x:Null}" />
cs
이것은 기본값이 null이 아닌 프로퍼티에 null값을 설정해야 하는 경우 말고는 별로 의미가 없습니다. 그러나 12 장에서 보게 될 것인데, 때로 style에서 프로퍼티가 null이 아닌 값을 가질 수 있으며, x:Null은 이를 무시할 수 있는 유일한 방법입니다.

x:Type 마크업 확장은 클래스 또는 구조체의 형식을 설명하는 .NET 클래스인 Type 형식의 프로퍼티를 설정하는데 사용됩니다. 구문은 다음과 같습니다.
<AnotherElement TypeProperty="{x:Type Color}" />
cs

또한 x:Array와 연결하여 x:Type을 사용할 수 있습니다. x:Array 마크업 확장은 항상 중괄호 구문보다는 일반 element 구문과 함께 사용됩니다. x:Type 마크업 확장으로 설정하는 Type이라는 필수 인수가 배열의 element 타입을 나타냅니다. resource 딕셔너리에 배열을 정의하는 법은 아래와 같습니다.

<x:Array x:Key="array" 
         Type="{x:Type x:String}"
    <x:String>One String</x:String>
    <x:String>Two String</x:String
    <x:String>Red String</x:String>
    <x:String>Blue String</x:String
</x:Array>
cs


A custom markup extension


HslColorExtension이라는 자체 마크업 확장을 만들어 보겠습니다. 이 방법으로 hue, saturation, luminosity 값을 지정하여 Color 타입의 모든 프로퍼티를 설정할 수 있는데, 8장 "Code and XAML in harmony"에 나와있는 x:FactoryMethod 태그를 사용하는 것보다 훨씬 간단합니다.

덧붙여 이 클래스를 독립된 Portable Class Library에 넣어 여러 응용프로그램에서 사용할 수 있도록 하시기 바랍니다. 이런 라이브러리는 이 책의 다른 소스 코드에서도 찾아볼 수 있습니다. 다른 챕터 디렉토리와 같은 레벨의 Libraries라는 폴더에 저장할 것입니다. 이 PCL의 이름(그리고 그 안에 있는 클래스의 네임스페이스)은 Xamarin.FormsBook.Toolkit입니다.

여러분은 앱에 참조를 추가하는 것만으로 이 라이브러리를 사용할 수 있습니다. 그리고 XAML 파일에 이 라이브러리를 특정하기 위해 XML 네임스페이스 선언을 첨가하면 됩니다.

xmlns:toolkit="clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
cs

toolkit 접두사를 사용하면 다른 XAML 마크업 확장을 사용하는 것과 같은 방법으로 HslColorExtension 클래스를 참조할 수 있습니다.

<BoxView Color="{toolkit:HslColor H=0.67, S=1, L=0.5}" />
cs

지금까지 나온 다른 XAML 마크업 확장과는 달리 여기에는 여러 개의 프로퍼티가 있으며 중괄호 구문으로 인수로 설정하는 경우 쉼표로 구분해야 합니다

유용할 것 같지 않습니까? 먼저 응용 프로그램간에 공유하려는 클래스 라이브러리를 만드는 방법을 살펴 보겠습니다.

비주얼 스튜디오에서는 파일>새로 만들기>프로젝트를 선택하고 대화창 좌측의 Visual C#>Cross-Platform을 선택하여 목록에서 클래스 라이브러리를 선택합니다. 저장 위치를 정해 주고 이름을 붙여줍니다. 이번 PCL의 예에서 이름은 Xamarin.FormsBook.Toolkit입니다. 그리고 확인을 누릅니다. 그렇게 되면 템플릿은 Xamarin.FormsBook.Toolkit이라는 클래스가 들어있는 Xamarin.FormsBook.Toolkit.cs라는 코드 파일을 만듭니다. 그것은 우리가 만들고자 하는 클래스 이름이 아니므로 그 파일을 삭제하십시오.

Xamarin 스튜디오에서는 File 메뉴에서 , New>Solution 선택하시고, New project 창 좌측에서 Multiflatform>Library한 후 목록에서 Forms>Class Library를 선택하십시오. 저장 위치 지정하고 이름(여기서는 역시 Xamarin.FormsBook.Toolkit)을 지정한 뒤 OK를 누르시기 바랍니다. 솔루션 템플릿이 몇몇 파일을 생성하는데, 그 중 MyPage.cs를 삭제하십시오.

이제 프로젝트에 보통의 방법으로 클래스를 추가할 수 있습니다.

비주얼 스튜디오에서는 프로젝트 이름에 우클릭 하고 추가>새 항목을 선택하고 새 항목 추가 창에서, 만약 코드로만 된 클래스를 만들고자 한다면 Visual C#>코드를 좌측에서 고르고 목록에서 클래스를 선택합니다. 이름(여기서는 HslColorExtension.cs 입니다)을 작성하고 추가 버튼을 누릅니다.

자마린 스튜디오에서는 프로젝트의 툴 메뉴에서 Add>New File을 고르시고 New File 대화창 좌측에서 General을 선택한 후 목록에서 Empty Class를 선택합니다. 역시 같은 이름을 작성하고 New 버튼을 누릅니다.

Xamarin.FormsBook.Toolkit 라이브러리가 구축되고, 거기에 이 책을 읽는 동안 유용한 클래스를 모을 것입니다. 그 첫 번째 클래스가 HslColorExtension입니다. HslColorExtension.cs은 다음과 같습니다(using 지시문까지 다 포함).

using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.FormsBook.Toolkit
{
    public class HslColorExtension : IMarkupExtension
    {
        public HslColorExtension()
        {
            A = 1;
        }
        public double H { set; get; }
        public double S { set; get; }
        public double L { set; get; }
        public double A { set; get; }
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            return Color.FromHsla(H, S, L, A);
        }
    }
}
cs

그러나 이 메서드는 IServiceProvider 인수를 전혀 사용하지 않는데 외부에 대해 알 필요가 없기 때문입니다. 필요한 것은 Color 값을 생성하는 네 가지 프로퍼티 뿐이며, A 값을 설정하지 않으면 기본값 1 (완전히 불투명)이 사용됩니다.

이 Xamarin.FormsBook.Toolkit 솔루션에는 PCL 프로젝트만 들어 있습니다. 프로젝트가 PCL 어셈블리를 생성하도록 빌드 되어야 하는데, 이 어셈블리를 이용하는 어플리케이션 없이는 작동하지 않습니다.

어플리케이션이 이 어셈블리에 접근하는데는 두 가지 방법이 있습니다.


  • 응용프로그램 솔루션의 PCL 프로젝트에 라이브러리 프로젝트에서 생성된 동적 연결 라이브러리(DLL)인 라이브러리 PCL 어셈블리에 대한 참조를 추가.
  • 응용 프로그램 솔루션에 라이브러리 프로젝트에 대한 링크를 추가하고, 응용 프로그램의 PCL 프로젝트에서 해당 라이브러리 프로젝트에 대한 참조를 추가.


프로젝트 소스코드가 아닌 DLL만 있는 경우 첫 번째 옵션이 필요합니다. 아마도 라이브러리에 라이센스를 부여할 것이고 소스에 액세스 할 수 없을 것입니다. 그러나 프로젝트 자체에 액세스할 수 있는 경우는 보통 라이브러리 프로젝트에 대한 링크를 솔루션에 포함시켜 라이브러리를 변경할 수 있습니다 코드를 작성하고 라이브러리 프로젝트를 다시 빌드하십시오.

이번 챕터의 마지막 프로젝트는 CustomExtensionDemo이고, 라이브러리에서 HslColorExtension 클래스를 사용할 것입니다. 이 솔루션에는 Xamarin.FormsBook.Toolkit PCL 프로젝트에 대한 링크가 들어가 있으며, CustomExtensionDemo 프로젝트의 References 섹션에는 Xamarin.FormsBook.Toolkit 어셈블리가 나열되어 있습니다.

이제 XAML 파일 내에서 HslColorExtension 클래스를 사용하기 위해 라이브러리 프로젝트에 액세스 할 수 있게 됩니다.

하지만 먼저 한 단계를 더 거쳐야 합니다. XAML 컴파일을 사용하도록 설정하지 않은 경우 외부 라이브러리에 대한 참조를 사용할 수 없습니다. 라이브러리는 실제 코드에서 접근되어야 하기 때문에 Xamarin.FormsBook.Toolkit에 라이브러리의 초기화를 수행하는 클래스와 메소드를 넣어 주어야 합니다.

namespace Xamarin.FormsBook.Toolkit
{
    public static class Toolkit
    {
        public static void Init()
        {
        }
    }
}
cs

이 라이브러리의 내용을 사용할 때마다 App 파일에서 Init 메서드를 호출하는 습관을 들이기 바랍니다.

namespace CustomExtensionDemo
{
    public partial class App : Application
    {
        public App()
        {
            Xamarin.FormsBook.Toolkit.Toolkit.Init();
 
            InitializeComponent();
 
            MainPage = new CustomExtensionDemo.MainPage();
        }
 
        ...
    }
}
cs

다음 XAML 파일에서 Xamarin.FormsBook.Toolkit 라이브러리를 위한 XML 네임스페이스 선언과, 사용자 지정 XAML 마크업 확장에 액세스하는 세 가지 방법을 볼 수 있습니다. Color 프로퍼티에 property-element 구문을 사용하여 HslColorExtension 세트를 사용하는 방법과, HslColorExtensionHslColor 모두에 중괄호를 사용하는 보다 보편적인 방법이 그것입니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit=
                 "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="CustomExtensionDemo.MainPage">
 
    <StackLayout>
        <!-- Red -->
        <BoxView HorizontalOptions="Center"
                 VerticalOptions="CenterAndExpand">
            <BoxView.Color>
                <toolkit:HslColorExtension H="0" S="1" L="0.5" />
            </BoxView.Color>
        </BoxView>
        
        <!-- Green -->
        <BoxView HorizontalOptions="Center"
                 VerticalOptions="CenterAndExpand">
            <BoxView.Color>
                <toolkit:HslColorExtension H="0.33" S="1" L="0.5" />
            </BoxView.Color>
        </BoxView>
        
        <!-- Blue -->
        <BoxView Color="{toolkit:HslColor H=0.67, S=1, L=0.5}"
                 HorizontalOptions="Center"
                 VerticalOptions="CenterAndExpand" />
        
        <!-- Gray -->
        <BoxView Color="{toolkit:HslColor H=0, S=0, L=0.5}"
                 HorizontalOptions="Center"
                 VerticalOptions="CenterAndExpand" />
        
        <!-- Semitransparent white -->
        <BoxView Color="{toolkit:HslColor H=0, S=0, L=1, A=0.5}" 
                 HorizontalOptions="Center" 
                 VerticalOptions="CenterAndExpand" />
        
        <!-- Semitransparent black -->
        <BoxView Color="{toolkit:HslColor H=0, S=0, L=0, A=0.5}" 
                 HorizontalOptions="Center" 
                 VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>
cs

마지막 두 예제는 A 프로퍼티를 50 % 투명도로 설정했기 때문에 상자는 배경에 따라 회색 음영으로 표시되거나 플랫폼에 따라 전혀 보이지 않습니다.


XAML 마크업 확장의 두 가지 주요 용도가 아직 나오지 않았습니다. 12 장에서 Style 클래스가 등장하는데 이 클래스는 리소스 사전 사용에 있어 의심의 여지 없이 가장 인기있는 항목이며, 16 장에서는 Binding이라는 강력한 마크업 확장이 등장합니다.

댓글 없음:

댓글 쓰기