전체 페이지뷰

2017년 5월 12일 금요일

Chapter 10. XAML markup extensions, part 1

코드로 작성을 할 때, 여러분은 다양한 방법으로 프로퍼티를 설정할 수 있습니다.

triangle.Angle1 = 45
triangle.Angle1 = 180 * radians / Math.PI; 
triangle.Angle1 = angles[i]; 
triangle.Angle1 = animator.GetCurrentAngle();
cs

Angle이라는 프로퍼티가 double 값이라고 할 때, 필요한 것은 double 내지는 double로 변환할 수 있는 숫자 값 뿐입니다.

그러나 마크업에서는 double 유형의 프로퍼티는 일반적으로 Double.Parse가 처리할 수 있는 문자열로만 설정할 수 있습니다. 지금까지 본 유일한 예외는 FontSize 프로퍼티처럼 TypeConverter attribute로 플래그가 지정되는 경우입니다.

XAML이 텍스트 문자열 외의 소스로부터 프로퍼티를 설정할 수 있다면 훨씬 좋을 것입니다. 예를 들어, Hue, SaturationLuminosity 값을 사용하지만 x:FactoryMethod element를 사용하는 번거로움 없이 Color 유형의 프로퍼티를 설정하는 방법을 정의한다고 가정합시다. 얼핏 생각하기에 가능할 것 같지 않습니다. XAML 파서는 Color 유형의 attribute에 설정된 모든 값이 ColorTypeConverter 클래스에서 허용되는 문자열일 것이라고 기대합니다.

XAML markup extension의 목적은 이런 명백한 제약을 우회하는 것입니다. XAML 태그 확장은 XML에 대한 확장이 아니므로 안심하십시오. XAML은 어디까지나 적법한  XML입니다. XAML 마크업 확장은 attribute 설정 방법을 확장한다는 의미에만 국한됩니다. 마크업 확장은 반드시 값을 텍스트로 표현하지 않고도 특정 타입의 값을 제공합니다.

the code infrastructure

엄밀히 말해서 XAML 마크업 확장은 public 인터페이스인 IMarkupExtension을 구현하는 클래스인데, 이 인터페이스는 Xamarin.Forms.Core 어셈블리에 정의되어 있으며 다만 Xamarin.Forms.Xaml 네임스페이스를 사용합니다.

public interface IMarkupExtension 
    object ProvideValue(IServiceProvider serviceProvider); 
}
cs

이름에서 알 수 있듯이 ProvideValue는 XAML attribute의 값을 제공하는 메소드입니다. IServiceProvider는 .NET의 기본 클래스 라이브러리의 일부이며 System 네임스페이스에 정의되어 있습니다.

public interface IServiceProvider 
    object GetService(Type type); 
}
cs

이것만 가지고는 마크업 확장을 어떻게 하는지 알 수가 없습니다(곧 예를 보시게 될것이며, 이 책 후반부에서 또다른 예시가 등장합니다). 다행히도 Xamarin.Forms는 몇 가지 유용한 마크업 확장을 제공하며, 크게 세 가지 부류로 이루어져 있습니다.


  • XAML 2009 사양의 일부인 마크업 확장. 이것은 XAML 파일에서 x 접두사와 함께 나타납니다.
         x:Static
         x:Reference
         x:Type
         x:Null
         x:Array

이것들은 마크업 확장의 이름에 Extension이라는 단어를 덧붙인 클래스로 표현되어 있습니다(예:  StaticExtension, ReferenceExtension 클래스). 이 클래스들은 Xamarin.Forms.Xaml 어셈블리 내에 정의되어 있습니다.


  • 다음 마크업 확장은 WPF(Windows Presentation Foundation)에서 비롯되었으며(DynamicResource는 제외) 마이크로소프트 사의 다른 XAML 구현(Silverlight, 윈도우폰 7,8, 윈도우 8,10 같은)에 의해 지원됩니다.

         StaticResource
         DynamicResource
         Binding

이것들은 public StaticResourceExtension, DynamicResourceExtension, BindingExtension 클래스에서 구현됩니다.


  • Xamarin.Forms에 고유한 마크업 확장은 RelativeLayout과 관련된 ConstraintExpression 클래스 하나뿐입니다.


코드 상에서 public 마크업 확장 클래스를 사용하는 것도 가능하지만, 실제로는 XAML에서만 의미가 있습니다.



Accessing static members

IMarkupExtension의 가장 간단하고 유용한 구현은 StaticExtension 클래스에 캡슐화되어 있습니다. 이는 원래 XAML 사양의 일부이므로 일반적으로 XAML 내에서 x 접두사와 함께 나타납니다. StaticExtensionstring 타입의 Member라는 프로퍼티 하나만을 정의하며, 이것은 public 상수, 정적 프로퍼티, 정적 필드, 열거형 멤버의 클래스와 멤버 이름 설정에 쓰입니다.

어떻게 적용하는 것인지 살펴봅시다. 다음은 보통 XAML에서 볼 수 있는 6개의 프로퍼티를 가지는 Label입니다.

<Label Text="Just some text" 
       BackgroundColor="Accent" 
       TextColor="Black" 
       FontAttributes="Italic" 
       VerticalOptions="Center" 
       HorizontalTextAlignment="Center" />
cs

이러한 attribute들 중 다섯 가지는 결국 다양한 정적 속성, 필드, 열거형 멤버를 참조하는 텍스트 문자열로 설정되지만, 이러한 텍스트 문자열의 변환은 type converter 및 열거형의 표준 XAML 파싱을 통해 이루어집니다.

이 attribute를 더 명시적으로 설정하고 싶은 경우, property element 태그 내에 x:StaticExtension을 사용할 수 있습니다.

<Label Text="Just some text">
    <Label.BackgroundColor>
        <x:StaticExtension Member="Color.Accent" />
    </Label.BackgroundColor>
    <Label.TextColor>
        <x:StaticExtension Member="Color.Black" />
    </Label.TextColor>
    <Label.FontAttributes>
        <x:StaticExtension Member="FontAttributes.Italic" />
    </Label.FontAttributes>
    <Label.VerticalOptions>
        <x:StaticExtension Member="LayoutOptions.Center" />
    </Label.VerticalOptions>
    <Label.HorizontalTextAlignment>
        <x:StaticExtension Member="TextAlignment.Center" />
    </Label.HorizontalTextAlignment>
</Label>
cs

Color.Accent는 정적 프로퍼티이고, Color.BlackLayoutOptions.Center는 정적 필드입니다. FontAttributes.ItalicTextAlignment.Center는 열거형 멤버입니다.

이 속성들을 그냥 텍스트 문자열로 설정하는 것이 쉽다는 것을 고려해볼 때 StaticExtension을 사용하는 접근법은 멍청한 짓으로 것처럼 보이지만, 이것은 범용의 메커니즘이라는 것을 주지하시기 바랍니다. 타입이 일치하기만 하면 StaticExtension 태그에서 정적 프로퍼티, 필드, 열거형 멤버를 사용할 수 있게 됩니다.

관례적으로 IMarkupExtension을 구현하는 클래스는 Extension이라는 단어를 이름끝에 붙이지만 XAML에서는 이 태그 확장을 보통 x:StaticExtension이 아닌 x:Static이라고 칭합니다. 아래의 마크업은 전의 것보다 조금 짧습니다.

<Label Text="Just some text">
    <Label.BackgroundColor>
        <x:Static Member="Color.Accent" />
    </Label.BackgroundColor>
    <Label.TextColor>
        <x:Static Member="Color.Black" />
    </Label.TextColor>
    <Label.FontAttributes>
        <x:Static Member="FontAttributes.Italic" />
    </Label.FontAttributes>
    <Label.VerticalOptions>
        <x:Static Member="LayoutOptions.Center" />
    </Label.VerticalOptions>
    <Label.HorizontalTextAlignment>
        <x:Static Member="TextAlignment.Center" />
    </Label.HorizontalTextAlignment>
</Label>
cs

이제 마크업 길이를 획기적으로 줄여 봅니다. property-element가 사라지고 풋프린트를 많이 줄어들게 합니다. XAML 마크업 확장 시에는 거의 모든 경우에 중괄호 안에 마크업 확장 이름과 인수가 들어가는 형식을 취합니다.

<Label Text="Just some text" 
       BackgroundColor="{x:Static Member=Color.Accent}" 
       TextColor="{x:Static Member=Color.Black}" 
       FontAttributes="{x:Static Member=FontAttributes.Italic}" 
       VerticalOptions="{x:Static Member=LayoutOptions.Center}" 
       HorizontalTextAlignment="{x:Static Member=TextAlignment.Center}" />
cs

이 중괄호를 사용하는 구문은 널리 쓰이기 때문에 개발자들은 이 중괄호 구문을 마크업 확장과 같은 뜻으로 여기기도 합니다. 그리고 실제 중괄호가 나타나면 항상 XAML 태그 확장이 이루어졌다는 것을 알 수가 있어 편할 수가 있습니다.

중괄호 안에는 따옴표가 없습니다. 이 중괄호 안에는 완전히 다른 문법이 적용됩니다. 그 안에서 StaticExtension 클래스의 Member 프로퍼티는 더 이상 XML attribute가 아닙니다. XML의 경우 중괄호 안의 전체 문장은 attribute 값이며 중괄호 안의 인수에는 따옴표가 쓰이지 않습니다.

element와 마찬가지로 마크업 확장은 ContentProperty attribute를 가질 수 있습니다. 속성이 하나 뿐인 마크업 확장은 프로퍼티를 content 프로퍼티로 표시합니다. 중괄호 구문의 마크업 확장 시에는 멤버 프로퍼티 이름과 등호 역시 생략될 수 있습니다.

<Label Text="Just some text" 
       BackgroundColor="{x:Static Color.Accent}" 
       TextColor="{x:Static Color.Black}" 
       FontAttributes="{x:Static FontAttributes.Italic}" 
       VerticalOptions="{x:Static LayoutOptions.Center}" 
       HorizontalTextAlignment="{x:Static TextAlignment.Center}" />
cs

이것이 x:Static 마크업 확장의 일반적 형태입니다.

물론 이 특별한 프로퍼티에 x:Static을 사용하는 것은 불필요합니다만, 앱 전역 상수를 구현하기 위해 정적 멤버를 이런 식으로 정의할 수 있고 XAML 파일 내에서 참조할 수도 있습니다. 그 과정을 SharedStatics 프로그램을 통해 보여 드리겠습니다.

SharedStatics 프로젝트에는 텍스트 서식 지정에 사용할 수있는 몇 가지 상수와 정적 필드를 정의하는 AppConstants라는 클래스가 있습니다.

using Xamarin.Forms;
namespace SharedStatics
{
    static class AppConstants
    {
        public static Color LightBackground = Color.Yellow;
        public static Color DarkForeground = Color.Blue;
        public static double NormalFontSize = 18;
        public static double TitleFontSize = NormalFontSize * 1.4;
        public static double ParagraphSpacing = 10;
        public const FontAttributes Emphasis = FontAttributes.Italic;
        public const FontAttributes TitleAttribute = FontAttributes.Bold;
        public const TextAlignment TitleAlignment = TextAlignment.Center;
    }
}
cs

만약 플랫폼마다 다르게 지정하고 싶으시다면 Device.OnPlatform을 사용하면 됩니다.

그 다음 XAML 파일은 이것들을 이용하기 위해 18개의 x:Static 마크업 확장을 사용합니다. XML 네임스페이스 정의에서 local 접두사를 프로젝트의 네임스페이스와 연결하는 방법을 눈여겨 보시기 바랍니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:SharedStatics2"
             x:Class="SharedStatics2.MainPage"
             BackgroundColor="{x:Static local:AppConstants.LightBackground}">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    
    <StackLayout Padding="10, 0" Spacing="{x:Static local:AppConstants.ParagraphSpacing}">
        <Label Text="The SharedStatics Program" 
               TextColor="{x:Static local:AppConstants.DarkForeground}" 
               FontSize="{x:Static local:AppConstants.TitleFontSize}" 
               FontAttributes="{x:Static local:AppConstants.TitleAttribute}" 
               HorizontalTextAlignment="{x:Static local:AppConstants.TitleAlignment}" />
        
        <Label TextColor="{x:Static local:AppConstants.DarkForeground}"
               FontSize="{x:Static local:AppConstants.NormalFontSize}">            
            <Label.FormattedText>
                <FormattedString>
                    <Span Text="Through use of the " />
                    <Span Text="x:Static" 
                          FontSize="{x:Static local:AppConstants.NormalFontSize}" 
                          FontAttributes="{x:Static local:AppConstants.Emphasis}" />
                    <Span Text= 
                          " XAML markup extension, an application can maintain a collection of
                          common property settings defined as constants, static properties or fields,
                          or enumeration members in a separate code file. These can then be 
                          referenced within the XAML file." />
                </FormattedString>
            </Label.FormattedText>
        </Label>
        <Label TextColor="{x:Static local:AppConstants.DarkForeground}" 
               FontSize="{x:Static local:AppConstants.NormalFontSize}">
            <Label.FormattedText>
                <FormattedString>
                    <Span Text= "However, this is not the only technique to share property settings. 
                          You'll soon discover that you can store objects in a " />
                    <Span Text="ResourceDictionary" 
                          FontSize="{x:Static local:AppConstants.NormalFontSize}" 
                          FontAttributes="{x:Static local:AppConstants.Emphasis}" />
                    <Span Text=" and access them through the " />
                    <Span Text="StaticResource" 
                          FontSize="{x:Static local:AppConstants.NormalFontSize}" 
                          FontAttributes="{x:Static local:AppConstants.Emphasis}" />
                    <Span Text= " markup extension, and even encapsultate multiple property settings in a " />
                    <Span Text="Style" 
                          FontSize="{x:Static local:AppConstants.NormalFontSize}" 
                          FontAttributes="{x:Static local:AppConstants.Emphasis}" />
                    <Span Text=" object." />
                </FormattedString>
            </Label.FormattedText>
        </Label>
    </StackLayout>
</ContentPage>
cs

FontAttributes 설정을 가지는 각 Span 개체는 Label 자체에 설정된 FontSize 설정을 반복하는데, 이는 다른 글꼴 관련 설정이 적용될 때 Span 개체가 Label에서 설정을 상속받지 않기 때문입니다.

결과는 다음과 같습니다.


이 기술을 사용하면 여러 페이지에서 공통 프로퍼티 설정을 사용할 수 있으며, 값을 변경해야하는 경우엔 AppSettings 파일만 변경하면 됩니다.

x:Static을 사용하여 외부 라이브러리의 클래스에 정의된 정적 프로퍼티와 필드를 사용할 수도 있습니다. SystemStatics라는 이름의 예제는 다음 예제를 봅시다. 이 프로그램은 ButtonBorderWidthMath 클래스에 정의된 PI static 필드와 같게 설정하고, 텍스트의 줄바꿈에는 static Environment.NewLine 프로퍼티를 사용합니다.

MathEnvironment 클래스는 모두 .NET System 네임스페이스에 정의되어 있으므로 System에 대해 접두어(예컨대 sys같은)를 정의하려면 새 XML 네임 페이스 선언이 필요합니다. 이 네임스페이스 선언은 CLR 네임스페이스를 System으로 지정하지만 어셈블리는 mscorlib로 지정한다는 점을 주의하시기 바랍니다(mscorlib: 한 때 Microsoft Common Object Runtime Library의 약자였지만 현재는 Multilanguage Standard Common Object Runtime Library를 뜻함).

<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="SystemStatics.MainPage">
    <StackLayout>
        <Button Text=" Button with #x03C0; border width "
                BorderWidth="{x:Static sys:Math.PI}"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand">
            <Button.BackgroundColor>
                <OnPlatform x:TypeArguments="Color"
                            Android="#404040" />
            </Button.BackgroundColor>
            <Button.BorderColor>
                <OnPlatform x:TypeArguments="Color"
                            Android="White"
                            WinPhone="Black" />
            </Button.BorderColor>
        </Button>
        <Label VerticalOptions="CenterAndExpand"
               HorizontalOptions="Center"
               FontSize="Medium">
            <Label.FormattedText>
                <FormattedString>
                    <Span Text="Three lines of text" />
                    <Span Text="{x:Static sys:Environment.NewLine}" />
                    <Span Text="separated by" />
                    <Span Text="{x:Static sys:Environment.NewLine}" />
                    <Span Text="Environment.NewLine"
                          FontSize="Medium"
                          FontAttributes="Italic"/>
                    <Span Text=" strings" />
                </FormattedString>
            </Label.FormattedText>
        </Label>
    </StackLayout>
</ContentPage>
cs

안드로이드와 윈도우 폰에서는 배경색이 지정되지 않으면 버튼 테두리가 보이지 않으므로 디폴트가 아닌 값을 설정해줘야 하고, 따라서 이 문제 해결을 위해 추가적인 마크업이 필요합니다. 반면 iOS 플랫폼에서는 버튼 테두리를 설정하면 내부 텍스트가 지저분하게 보일 수 있으므로, 텍스트 처음과 끝 부분에 공백을 추가해 줍니다.

시각적으로만 판단해야 한다는 어려움이 있지만 버튼의 테두리는 분명 3.14 unit 넓이이고, 줄바꿈은 확실히 눈으로 확인 가능합니다.


중괄호가 사용되면 그 안에 들어있는 내용은 텍스트가 아닌 마크업 확장이라는 뜻입니다.

<Label Text="{Text in curly braces}" />
cs

따라서 이것은 작동되지 않을 겁니다. 문자열 내부에 들어가는 것은 상관없습니다만, 중괄호로 시작되면 문자열이 아니게 됩니다.

그러나 정말로 필요한 경우 빈 좌우 중괄호 세트를 먼저 오게 하여 XAML 마크업 확장이 아니라고 인식하게 할 수 있습니다.
<Label Text="{}{Text in curly braces}" />
cs

이렇게 하면 중괄호를 포함한 문자열이 표시됩니다.

댓글 없음:

댓글 쓰기