Artykuł

freeimages.com freeimages.com
paź 20 2016
0

DataTemplateSelector w Xamarin.Forms

ListView to jedna z najważniejszych kontrolek w Xamarin.Forms. Jak sama jej nazwa wskazuje, jest ona odpowiedzialna za ogarnięcie tematu listy. Listy można spotkać w praktycznie każdej aplikacji, począwszy choćby od najprostszych programów typu TODO poprzez rozbudowane aplikacje wyświetlające video, obsługujące naszą pocztę, czy dbające o temat komunikacji.

Kontrolka którą dostarcza Xamarin.Forms, posiada wiele przydatnych elementów (temat na inny wpis to historia jak w praktyce działa ta kontrolka, a warto dodać że wymaga ona sporej dawki cierpliwości od programisty..), a jedną z nich jest możliwość ustawienia szablonu elementu wyświetlanego na liście.

Domyślnie na ListView, możemy wyświetlać elementy tylko w jednym layoucie, który oczywiście można modyfikować odpowiednimi wartościami modelu i dobrze napisanym XAMLem. W praktyce jednak, nie zawsze jest to najlepsze rozwiązanie. Dużo lepszą opcją będzie zastosowanie klasy DataTemplateSelector, która na podstawie właściwości zawartych w modelu, jest w stanie dostarczyć odpowiedni układ, dopasowany do aktualnych danych.

Klasa DataTemplateSelector

Klasa DataTemplateSelector została wprowadzona w Xamarin.Forms w wersji 2.1 i od tego momentu można z niej korzystać w swoich rozwiązaniach. W wielkim skrócie - cały proces sprowadza się do realizacji kilku kroków:

  • Należy przygotować klasę modelu
  • Należy przygotować pożądaną liczbę DataTemplates operujących na tej klasie modelu
  • Należy odziedziczyć z klasy DateTemplateSelector i nadpisać metodę OnSelectTemplate
  • Należy wpiąć DataTemplateSelctor jako ItemTemplate naszej kontrolki ListView

W praktyce nie jest to więc przesadnie skomplikowany proces, co pokaże przykład praktyczny umieszczony poniżej.

Przykład praktyczny

Nasz przykład praktyczny będzie maksymalnie prosty. Zbudujemy jedną klasę modelu, która będzie przechowywała informacje na temat imienia, nazwiska oraz orientacji widoku. Na podstawie modelu danych zbudujemy dwa DataTemplates, która będą wyświetlać dane odpowiednio w trybie horyzontalnym i wertykalnym. W kolejnym kroku, utworzymy DataTemplateSelector, który w zależności od wskazanej w modelu orientacji wybierze odpowiedni DataTemplate.

Zwieńczeniem całego przykładu będzie strona główna, która dostarczy nam UI a także dane zapisane w code-behind. Zaczynajmy:)

Na pierwszy ogień model oraz niezbędny w tym przypadku enum:

public enum TemplateOrientation
{
    Horizontal,
    Vertical
}

public class TemplateModel
{
    public TemplateOrientation TemplateOrientation { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

W kolejnym kroku, tworzymy DataTemplates. Visual Studio nie dostarcza możliwości utworzenia DataTemplate w pliku XAML, dlatego też utworzyłem dwie nowe strony XAMLowe i zmieniłem ich typy bazowe na ViewCell (w taki sposób można utworzyć również np. ResourceDictionary itp.). Poniżej kod XAML dla horyzontalnej cellki:

<?xml version="1.0" encoding="utf-8" ?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataTemplateSelectorXamarinForms.DataTemplateH">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Label Text="{Binding FirstName}" />
    <Label Text="{Binding LastName}" Grid.Column="1" />
  </Grid>
</ViewCell>

Analogiczne rozwiązanie dla cellki wertykalnej:

<?xml version="1.0" encoding="utf-8" ?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataTemplateSelectorXamarinForms.DataTemplateV">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>
    <Label Text="{Binding FirstName}" />
    <Label Text="{Binding LastName}" Grid.Row="1" />
  </Grid>
</ViewCell>

W momencie gdy posiadamy oba DataTemplates, to doszliśmy do momentu w którym możemy napisać nasz DataTemplateSelector:

public class MyDataTemplateSelector : DataTemplateSelector
{
    private readonly DataTemplate _dataTemplateH = null;
    private readonly DataTemplate _dataTemplateV = null;

    public MyDataTemplateSelector()
    {
        this._dataTemplateH = new DataTemplate(typeof(DataTemplateH));
        this._dataTemplateV = new DataTemplate(typeof(DataTemplateV));
    }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        var templateModel = item as TemplateModel;
        if (templateModel == null)
        {
            return null;
        }
        return templateModel.TemplateOrientation == TemplateOrientation.Horizontal ?
            this._dataTemplateH : this._dataTemplateV;
    }
}

Warto tutaj zwrócić uwagę na jeden istotny fakt. Obiekty typu DataTemplate trzymamy jako pola klasy, które dzięki temu reużywane są wielokrotnie przy renderowaniu kolejnych obiektów z listy.

Mając wszystkie składniki, możemy przejść do ostatniego etapu, czyli utworzenia strony głównej. Część XAML jest stosunkowo prosta i wygląda ona następująco:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataTemplateSelectorXamarinForms;assembly=DataTemplateSelectorXamarinForms"
             x:Class="DataTemplateSelectorXamarinForms.MainPage">
  <ContentPage.Resources>
    <ResourceDictionary>
      <local:MyDataTemplateSelector x:Key="MyDataTemplateSelector"></local:MyDataTemplateSelector>
    </ResourceDictionary>
  </ContentPage.Resources>
  
  <ListView ItemsSource="{Binding Items}" ItemTemplate="{StaticResource MyDataTemplateSelector}" />
</ContentPage>

Głównym wyzwaniem w tym przypadku było zaimportowanie lokalnej przestrzeni nazw i wpięcie DataTemplateSelectora w miejscu ItemTemplate.

W przypadku pliku code-behind musimy:

  • Ustawić BindingContext
  • Utworzyć obiekt ObservableCollection
  • Zasilić go danymi

Poniżej kod realizujący to zadanie:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        this.Items = new ObservableCollection<TemplateModel>()
        {
            new TemplateModel { FirstName = "Jan", LastName = "Kowalski", TemplateOrientation = TemplateOrientation.Horizontal },
            new TemplateModel { FirstName = "Jan", LastName = "Nowak", TemplateOrientation = TemplateOrientation.Horizontal },
            new TemplateModel { FirstName = "Stefan", LastName = "Kowalczyk", TemplateOrientation = TemplateOrientation.Vertical },
            new TemplateModel { FirstName = "Agnieszka", LastName = "Nowak", TemplateOrientation = TemplateOrientation.Horizontal },
            new TemplateModel { FirstName = "Paulina", LastName = "Nowaczyk", TemplateOrientation = TemplateOrientation.Vertical },
            new TemplateModel { FirstName = "Katarzyna", LastName = "Kowalska", TemplateOrientation = TemplateOrientation.Vertical },
            new TemplateModel { FirstName = "Marek", LastName = "Nowicki", TemplateOrientation = TemplateOrientation.Vertical },
            new TemplateModel { FirstName = "Anna", LastName = "Nowicka", TemplateOrientation = TemplateOrientation.Vertical },
            new TemplateModel { FirstName = "Grzegorz", LastName = "Wasilewski", TemplateOrientation = TemplateOrientation.Horizontal },
            new TemplateModel { FirstName = "Barbara", LastName = "Wasilewska", TemplateOrientation = TemplateOrientation.Horizontal }
        };
        this.BindingContext = this;
    }

    public ObservableCollection<TemplateModel> Items { get; private set; }
}

Z przyczyn technicznych, ograniczyłem przykład tylko do Androida. Nie ma tutaj jednak żadnej różnicy w przypadku innych systemów, więc w praktyce wystarczy tylko dodać nowe system do tego projektu, by uzyskać multi-platformowy efekt.

Cały kod projektu dostępny jest na Githubie.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus