본문 바로가기

Develop/MAUI 가이드

[Xamarin] TypedDataTemplateSelector

반응형

Typed DataTemplate Selector alt="Typed DataTemplateSelector"

TypedDataTempalteSelector

WPF 프로그램을 개발하면 ItemsControl.ItemTemplateDataTemplate 을 등록해서 ItemsControl.ItemSourceViewModel 이 추가될 때마다 ViewModel 타입에 맞는 View가 추가되는데, Xamarin 는 WPF 처럼 DataTemplate.DataType을 지원하지 않아 반드시 DataTemplateSelector를 사용해야만 합니다.

문제는 DataTemplateSelectorViewModel 타입에 따라 선택하게 구현하려면 아래와 같이 클린하지 않은 코드를 만들 수 밖에 없습니다.

public class MyDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate DataTemplateA { get; set; }
    public DataTemplate DataTemplateB { get; set; }
    public DataTemplate DataTemplateC { get; set; }
    // 새로운 DataTemplate 이 생길 때마다 DataTemplate 속성을 추가합니다.

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        if(item is ViewModelA)
            return DataTemplateA;
        else if(item is ViewModelB)
            return DataTemplateB;

        // 새로운 DataTemplate 이 생길 때마다 ViewModel을 결정해줍니다.

        return null;
    }
}

XAML 코드도 마찬가지로 복잡해집니다.

<DataTemplate x:Key="DataTemplateA">
    <!-- ViewModelA로 연결하려는 ViewA 정의 -->
</DataTemplate>
<DataTemplate x:Key="DataTemplateB">
    <!-- ViewModelB로 연결하려는 ViewB 정의 -->
</DataTemplate>

<!-- 새로운 DataTemplate 를 계속 추가합니다. -->

<MyDataTemplateSelector DataTemplateA="{StaticResource DataTemplateA}"
                        DataTemplateB="{StaticResource DataTemplateB}"/>

<!-- 새로운 DataTemplate 이 생길 때마다 StaticResource를 추가합니다. -->

그래서 WPF 의 DataType 처럼 XAML 코드로 타입을 지정하면 DataTemplate이 선택되게 하는 방법을 구현하여 공유드립니다.

TypedDataTemplate

[ContentProperty(name: nameof(Template))]
public class TypedDataTemplate
{
    public Type DataType { get; set; }
    public DataTemplate Template { get; set; }
}

XAML 코드에서 사용할 TypedDataTemplate 클래스를 구현합니다.

TypedDataTemplateSelector

/// <summary>
/// XAML에서 정의된 DataType에 따라 DataTemplate을 선택합니다.
/// </summary>
public class TypedDataTemplateSelector : DataTemplateSelector
{
    // XAML 에서 TypeDataTemplates 에 들어갈 TypeDataTemplate를 정의할 겁니다.
    public IList<TypedDataTemplate> TypedDataTemplates { get; set; }


    public TypedDataTemplateSelector()
    {
        TypedDataTemplates = new ObservableCollection<TypedDataTemplate>();
        var iNotifyCollectionChanged = (INotifyCollectionChanged)TypedDataTemplates;

        // TypeDataTemplates 에 새로운 TypedDataTemplate 이 추가될 때마다,
        // TypeDataTemplate 이 제대로 정의되었는지 확인합니다.
        iNotifyCollectionChanged.CollectionChanged += OnTypedDataTemplatesChanged;
    }

    protected virtual void OnTypedDataTemplatesChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // XAML 에서 추가한 TypedDataTemplate 설정이 올바른지 검증합니다.
        var newTypedDataTemplates = e?.NewItems.Cast<TypedDataTemplate>();
        foreach (var newTypedDataTemplate in newTypedDataTemplates)
        {
            if (newTypedDataTemplate.DataType == null)
            {
                throw new InvalidOperationException($"ViewModelType 이 지정되지 않아서, ViewModel과 연결할 수 없습니다. ViewModelType을 지정해주세요.");
            }

            if (newTypedDataTemplate.Template == null)
            {
                throw new InvalidOperationException($"{newTypedDataTemplate.DataType}과 연결할 DataTemplate이 지정되지 않았습니다. DataTemplate을 지정해주세요.");
            }
        }
    }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        if (item == null) return null;

        if(TypedDataTemplates.Count < 1)
        {
            throw new NotImplementedException("TypedDataTemplateSelector에 사용할 수 있는 TypedDataTemplate이 없습니다.");
        }

        // item 타입에 맞는 DataTemplate을 찾습니다.
        var appropriateDataTemplate = TypedDataTemplates.FirstOrDefault(typedDataTemplate =>
        {
            return typedDataTemplate.DataType.IsAssignableFrom(item.GetType());
        });

        if(appropriateDataTemplate == null)
        {
            throw new NotImplementedException($"{item.GetType()}과 알치하는 TypedDataTemplate을 찾을 수 없습니다. TypedDataTemplate을 추가해주세요.");
        }

        return appropriateDataTemplate.Template;
    }
}

XAML

<TypedDataTemplateSelector x:Key="LayerDataTemplateSelector">
  <TypedDataTemplateSelector.TypedDataTemplates>
    <TypedDataTemplate DataType="{x:Type ViewModelA}">
      <DataTemplate>
        <!-- ViewModelA로 연결하려는 ViewA 정의 -->
      </DataTemplate>
    </TypedDataTemplate>
    <TypedDataTemplate DataType="{x:Type ViewModelB}">
      <DataTemplate>
        <!-- ViewModelB로 연결하려는 ViewB 정의 -->
      </DataTemplate>
    </TypedDataTemplate>
    <!-- 새로운 DataTemplate 이 생길 때마다 TypedDataTemplate 만 추가합니다. -->
  </TypedDataTemplateSelector.TypedDataTemplates>
</TypedDataTemplateSelector>

이제 XAML 에서 TypedDataTemplate 을 추가하고 DataType 만 지정하면, TypedDataTemplateSelectorDataType에 따라 DataTemplate을 선택합니다.

반응형