w tym poście chciałbym zaprezentować prosty i skuteczny sposób na kojarzenie dwóch tytułowych elementów w jeden. Słowem wstępu - najpierw kilka informacji o działaniu widoków w Xamarin.Forms:
Widoki możemy tworzyć na dwa sposoby: używając XAMLa oraz całkowicie w kodzie c#. Osoby korzystające z Xamarina lub WPFa pewnie z XAMLem miały już do czynienia, a jak nie to krótko ujmując jest to język oparty na XMLu służący właśnie do tworzenia warstwy interfejsu użytkownika. Ma on tą przewagę nad tworzeniem widoków w kodzie, że po krótkiej praktyce, jestem w stanie z wizualizować sobie interfejs w głowie - przydatna umiejętność.
Pisząc XAMLowy widok w Xamarin.Forms jest on podzielony na dwa elementy - plik *.xaml, w którym jest xml z widokiem i plik tzw "codebehing" - *.xaml.cs który jest podpięty pod plik *.xaml i wygląda on mniej więcej tak:
public partial class WidokTest : ContentPage
{
public WidokTest ()
{
InitializeComponent();
}
}
Jak widać dla naszego testowego widoku mamy klasę, w której określamy bazowy widok po którym dziedziczymy - ContentPage lub ContentView najczęściej, a w konstruktorze wywołujemy inicjalizację komponentu.Na pytanie skąd ten partial odpowiedź jest prosta - drugą częścią naszej klasy jest właśnie nasz plik xamlowy, który kompilator przerabia na jedną, wspólną klasę.
Dobrą praktyką jest aby w codebehind robić jak najmniej, a wszystkie operacje na widoku działy się poprzez bindingi. No ale w naszej klasie testowej brakuje co najmniej jednego elementu: ustalenia właściwości BindingContext, który powinien być instancją jednego z naszych modeli widoków.
Stworzyłem więc taką klasę bazową:
public abstract class ViewPage<T> : ContentPage where T : IViewModel
{
readonly T _viewModel;
public T ViewModel
{
get { return _viewModel; }
}
public ViewPage()
{
using (var scope = IoC.container.BeginLifetimeScope())
{
_viewModel = scope.Resolve<T>(new TypedParameter(this.GetType(), this));
}
BindingContext = _viewModel;
}
public ViewPage(object viewModelContext)
{
using (var scope = IoC.container.BeginLifetimeScope())
{
_viewModel = scope.Resolve<T>(new TypedParameter(this.GetType(), this), new NamedParameter("context", viewModelContext));
}
BindingContext = _viewModel;
}
}
Stosując taką generyczną klasę bazową dla widoków mogę łatwo i szybko wyciągnąć z kontenera odpowiedni model i podpiąć go pod widok. Korzystając z niej, nasza testowa klasa widoku wyglądałaby następująco:public partial class WidokTest : ViewPage<WidokTestModel>
{
public WidokTest ()
{
InitializeComponent();
}
}
public partial class WidokTest : ViewPage<WidokTestModel>
{
public WidokTest (object kontekstWidoku) : base(kontekstWidoku)
{
InitializeComponent();
}
}
Jak widać ilość kodu w codebehind wzrosła bardzo nieznacznie, a mamy załatwiony najważniejszy element. Niestety wszystko ma swoją cenę - trzeba przypomnieć sobie o tym nieszczęsnym partialu - nasz klasa dziedziczy teraz po nowym elemencie i musimy analogicznie zmienić to w XAMLu:
<test:ViewPage xmlns:test="clr-namespace:MyApp.Test" x:TypeArguments="test:WidokTestModel"
Wystarczy dodać namespace w którym jest nasza klasa bazowa, zmienić nazwę głównego elementu oraz dodać generyczny parametr. W ten sposób wszystko nam się ładnie kompiluje, a my w prosty sposób spinany ze sobą widoki i modele widoków.
0 komentarze:
Prześlij komentarz