본문 바로가기

Develop/MAUI 가이드

[Xamarin] BindableProperty 를 Override 해보자

반응형

Override BindableProperty
Override BindableProperty

BindableProperty 를 Override 해보자

Xamarin 으로 View 를 개발하다보면 재활용성을 높이기 위해 기존의 View 를 상속받아서 개발하게 됩니다. 특히 특정 스타일의 View 를 반복적으로 사용할 때 아래와 같이 작성하게 됩니다.

public class MyFrame : Frame
{
    private readonly Label _title;
    public string Title { set => _title.Text = value; }

    public MyFrame()
    {
        BackgroundColor = Color.LightPink;

        _title = new Label
        {
            TextColor = Color.Black,
            FontSize = 16,
        };

         Content = _title;
    }
}

위에서 만든 MyFrame 을 아래와 같이 사용하게 됩니다.

<ContentPage>
    <ContentPage.Resources>
        <Style x:Key="Style" TargetType="local:MyFrame">
            <Setter Property="HorizontalOptions" Value="FillAndExpand" />
            <Setter Property="BackgroundColor" Value="LimeGreen" />
        </Style>
    </ContentPage.Resources>
    <ContentPage.Content>

        <local:MyFrame Style="{StaticResource Style}"
                       Title="Hello, World!" />
    </ContentPage.Content>
</ContentPage>

이렇게 개발한 프로젝트를 실행하면 개발자는 당연히 MyFrame 의 BackgroundColor 가 LimeGreen 색으로 나타나길 기대하게 됩니다. 하지만 실제 결과물은 아래와 같은 LightPink 색으로 나오게 됩니다.

Result
Result

이처럼 Frame 를 상속받아서 개발한 MyFrame 에 Style 이 적용되지 않는 황당함을 경험하게 됩니다. 이러한 예상 외의 결과가 나타나는 이유는 Style 의 Setter 보다 MyFrame 의 생성자 메서드에서 설정한 BackgroundColor = Color.LightPink; 의 우선순위가 더 높기 때문에 발생하는 문제입니다. 이를 XAML 로 표현하면 아래와 같이 작성한 것과 동일하기 때문입니다.

<ContentPage>
    <ContentPage.Resources>
        <Style x:Key="Style" TargetType="local:MyFrame">
            <Setter Property="HorizontalOptions" Value="FillAndExpand" />
            <Setter Property="BackgroundColor" Value="LimeGreen" />
        </Style>
    </ContentPage.Resources>
    <ContentPage.Content>

        <local:MyFrame Style="{StaticResource Style}"
                       BackgroundColor="LightPink"
                       Title="Hello, World!" />
    </ContentPage.Content>
</ContentPage>

이러한 현상을 해결하려면 MyFrameBackgroundColor 라는 동일한 이름으로 기존의 BindableProperty 를 덮는 방법이 제일 무난합니다. 그래서 저는 Xamarin 소스 코드를 참고해서 다음과 같은 Extensions 메서드 코드를 추가해서 활용했습니다.

public static class BindablePropertyExtensions
{
    // BindableProperty 의 메서드를 확장하여 기존 BindableProperty 속성을 덮어쓸 수 있는 Override 메서드.
    public static BindableProperty Override(
        this BindableProperty bindableProperty,
        string propertyName = null,
        Type returnType = null,
        Type declaringType = null,
        object defaultValue = null,
        BindingMode defaultBindingMode = BindingMode.OneWay,
        BindableProperty.ValidateValueDelegate validateValue = null,
        BindableProperty.BindingPropertyChangedDelegate propertyChanged = null,
        BindableProperty.BindingPropertyChangingDelegate propertyChanging = null,
        BindableProperty.CoerceValueDelegate coerceValue = null,
        BindableProperty.CreateDefaultValueDelegate defaultValueCreator = null)
    {
        if (string.IsNullOrEmpty(propertyName))
            propertyName = bindableProperty.PropertyName;
        if (returnType == null)
            returnType = bindableProperty.ReturnType;
        if (declaringType == null)
            declaringType = bindableProperty.DeclaringType;
        if (defaultValue == null)
            defaultValue = bindableProperty.DefaultValue;

        if (propertyChanged == null)
            propertyChanged = (bindable, oldValue, newValue) =>
            {
                // 기존 BindableProperty 속성과 연동하기 위해 새로운 속성이 바뀌면 기존 속성도 변경되도록 했다.
                var property = bindableProperty.DeclaringType.GetProperty(bindableProperty.PropertyName);
                property.SetValue(bindable, newValue);
            };

        return BindableProperty.Create(propertyName, returnType, declaringType, defaultValue, defaultBindingMode,
            validateValue, propertyChanged, propertyChanging, coerceValue, defaultValueCreator);
    }
}

위 코드를 사용해서 MyFrame 을 수정하면 MainPage 에서 설정한 Style 이 정상적으로 동작하는 걸 확인하실 수 있습니다.

public class MyFrame : Frame
{
    // VisualElement 에 정의되어 있던 BackgroundColorProperty 를 덮어쓴다.
    public static new readonly BindableProperty BackgroundColorProperty = VisualElement.BackgroundColorProperty.Override();

    private readonly Label _title;
    public string Title { set => _title.Text = value; }

    public MyFrame()
    {
        BackgroundColor = Color.LightPink;

        _title = new Label
        {
            TextColor = Color.Black,
            FontSize = 16,
        };

         Content = _title;
    }
}

StackOverflow

반응형