Wzorzec Model-View-ViewModel

Uwaga

Ta książka elektroniczna została opublikowana wiosną 2017 r. i od tego czasu nie została zaktualizowana. Jest wiele w książce, która pozostaje cenna, ale niektóre z materiałów są przestarzałe.

Środowisko deweloperskie Xamarin.Forms zwykle polega na utworzeniu interfejsu użytkownika w języku XAML, a następnie dodaniu kodu, który działa w interfejsie użytkownika. W miarę modyfikowania aplikacji i zwiększania rozmiaru i zakresu mogą wystąpić złożone problemy z konserwacją. Te problemy obejmują ścisłe sprzężenie między kontrolkami interfejsu użytkownika a logiką biznesową, co zwiększa koszt wprowadzania modyfikacji interfejsu użytkownika oraz trudności z testowaniem jednostkowym takiego kodu.

Wzorzec Model-View-ViewModel (MVVM) pomaga całkowicie oddzielić logikę biznesową i prezentacji aplikacji od interfejsu użytkownika. Utrzymanie czystej separacji między logiką aplikacji a interfejsem użytkownika pomaga rozwiązać wiele problemów programistycznych i może ułatwić testowanie, konserwację i rozwijanie aplikacji. Może również znacznie poprawić możliwości ponownego użycia kodu i umożliwić deweloperom i projektantom interfejsu użytkownika łatwiejsze współpracę podczas opracowywania odpowiednich części aplikacji.

Wzorzec MVVM

Wzorzec MVVM zawiera trzy podstawowe składniki: model, widok i model widoku. Każdy z nich służy odrębnemu celowi. Rysunek 2–1 przedstawia relacje między trzema składnikami.

The MVVM pattern

Rysunek 2–1. Wzorzec MVVM

Oprócz zrozumienia obowiązków poszczególnych składników ważne jest również zrozumienie sposobu interakcji ze sobą. Na wysokim poziomie widok "wie o" modelu widoku, a model widoku "wie o" modelu, ale model nie jest świadomy modelu widoku, a model widoku nie jest świadomy widoku. W związku z tym model widoku izoluje widok od modelu i umożliwia ewolucję modelu niezależnie od widoku.

Zalety korzystania ze wzorca MVVM są następujące:

  • Jeśli istnieje implementacja modelu, która hermetyzuje istniejącą logikę biznesową, może być trudna lub ryzykowna zmiana. W tym scenariuszu model widoku działa jako karta dla klas modelu i pozwala uniknąć wprowadzania jakichkolwiek istotnych zmian w kodzie modelu.
  • Deweloperzy mogą tworzyć testy jednostkowe dla modelu widoku i modelu bez korzystania z widoku. Testy jednostkowe modelu widoku mogą wykonywać dokładnie takie same funkcje, jak używane przez widok.
  • Interfejs użytkownika aplikacji można przeprojektować bez dotykania kodu, pod warunkiem że widok jest implementowany całkowicie w języku XAML. W związku z tym nowa wersja widoku powinna współdziałać z istniejącym modelem widoku.
  • Projektanci i deweloperzy mogą pracować niezależnie i współbieżnie na swoich składnikach podczas procesu programowania. Projektanci mogą skupić się na widoku, podczas gdy deweloperzy mogą pracować nad modelem widoku i składnikami modelu.

Kluczem do efektywnego używania maszyny MVVM jest zrozumienie sposobu, w jaki kod aplikacji jest uwzględniany w poprawnych klasach, oraz zrozumienie sposobu interakcji z klasami. W poniższych sekcjach omówiono obowiązki każdej z klas we wzorcu MVVM.

Widok

Widok jest odpowiedzialny za definiowanie struktury, układu i wyglądu tego, co użytkownik widzi na ekranie. W idealnym przypadku każdy widok jest definiowany w języku XAML z ograniczonym kodem, który nie zawiera logiki biznesowej. Jednak w niektórych przypadkach kod w tle może zawierać logikę interfejsu użytkownika, która implementuje zachowanie wizualne, które jest trudne do wyrażenia w języku XAML, takie jak animacje.

Xamarin.Forms W aplikacji widok jest zazwyczaj klasą pochodną lub ContentViewpochodnąPage. Jednak widoki mogą być również reprezentowane przez szablon danych, który określa elementy interfejsu użytkownika, które mają być używane do wizualnego reprezentowania obiektu podczas jego wyświetlania. Szablon danych jako widok nie ma żadnego kodu i jest przeznaczony do powiązania z określonym typem modelu widoku.

Porada

Unikaj włączania i wyłączania elementów interfejsu użytkownika w kodzie. Upewnij się, że modele wyświetlania są odpowiedzialne za definiowanie zmian stanu logicznego, które mają wpływ na niektóre aspekty wyświetlania widoku, takie jak to, czy polecenie jest dostępne, czy wskazanie, że operacja oczekuje. W związku z tym włącz i wyłącz elementy interfejsu użytkownika przez powiązanie, aby wyświetlić właściwości modelu, zamiast włączać i wyłączać je w kodzie.

Istnieje kilka opcji wykonywania kodu w modelu widoku w odpowiedzi na interakcje w widoku, takie jak kliknięcie przycisku lub wybór elementu. Jeśli kontrolka obsługuje polecenia, właściwość kontrolki Command może być powiązana z właściwością ICommand w modelu widoku. Po wywołaniu polecenia kontrolki zostanie wykonany kod w modelu widoku. Oprócz poleceń, zachowania mogą być dołączone do obiektu w widoku i mogą nasłuchiwać polecenia do wywołania lub zdarzenia do wywołania. W odpowiedzi zachowanie może następnie wywołać ICommand metodę w modelu widoku lub metodę w modelu widoku.

Model widoku

Model widoku implementuje właściwości i polecenia, z którymi widok może powiązać dane, i powiadamia widok wszelkich zmian stanu za pośrednictwem zdarzeń powiadamiania o zmianie. Właściwości i polecenia udostępniane przez model widoku definiują funkcje, które mają być oferowane przez interfejs użytkownika, ale widok określa sposób wyświetlania tej funkcji.

Porada

Zachowaj czas reakcji interfejsu użytkownika przy użyciu operacji asynchronicznych. Aplikacje mobilne powinny pozostawić odblokowany wątek interfejsu użytkownika, aby poprawić postrzeganie wydajności przez użytkownika. W związku z tym w modelu widoku należy używać metod asynchronicznych dla operacji we/wy i zgłaszać zdarzenia w celu asynchronicznego powiadamiania widoków o zmianach właściwości.

Model widoku jest również odpowiedzialny za koordynowanie interakcji widoku z dowolnymi wymaganymi klasami modelu. Zazwyczaj istnieje relacja "jeden do wielu" między modelem widoku a klasami modelu. Model widoku może zdecydować się na uwidocznienie klas modelu bezpośrednio do widoku, aby kontrolki w widoku mogły powiązać dane bezpośrednio z nimi. W takim przypadku klasy modelu muszą być zaprojektowane tak, aby obsługiwały powiązania danych i zdarzenia powiadamiania o zmianie.

Każdy model widoku udostępnia dane z modelu w postaci, którą widok może łatwo wykorzystywać. W tym celu model widoku czasami wykonuje konwersję danych. Umieszczenie tej konwersji danych w modelu widoku jest dobrym pomysłem, ponieważ zapewnia właściwości, z którymi widok może być powiązany. Na przykład model widoku może łączyć wartości dwóch właściwości, aby ułatwić wyświetlanie go w widoku.

Porada

Scentralizowanie konwersji danych w warstwie konwersji. Istnieje również możliwość użycia konwerterów jako oddzielnej warstwy konwersji danych, która znajduje się między modelem widoku a widokiem. Może to być konieczne, na przykład wtedy, gdy dane wymagają specjalnego formatowania, które model widoku nie udostępnia.

Aby model widoku mógł uczestniczyć w dwukierunkowym powiązaniu danych z widokiem, jego właściwości muszą zgłosić PropertyChanged zdarzenie. Wyświetl modele spełniające to wymaganie przez zaimplementowanie interfejsu INotifyPropertyChanged i wywoływanie PropertyChanged zdarzenia po zmianie właściwości.

W przypadku kolekcji jest udostępniany widok przyjazny ObservableCollection<T> dla widoku. Ta kolekcja implementuje zmienione powiadomienie o kolekcji, co pozwala deweloperowi na zaimplementowanie interfejsu INotifyCollectionChanged w kolekcjach.

Model

Klasy modelu to klasy niewizualne, które hermetyzują dane aplikacji. W związku z tym model może być uważany za reprezentujący model domeny aplikacji, który zwykle zawiera model danych wraz z logiką biznesową i walidacją. Przykłady obiektów modelu obejmują obiekty transferu danych (DTO), zwykłe stare obiekty CLR (POCO) oraz generowane obiekty jednostki i serwera proxy.

Klasy modeli są zwykle używane w połączeniu z usługami lub repozytoriami, które hermetyzują dostęp do danych i buforowanie.

Łączenie modeli widoku z widokami

Modele widoków można łączyć z widokami przy użyciu funkcji powiązania danych w programie Xamarin.Forms. Istnieje wiele podejść, które mogą służyć do tworzenia widoków i wyświetlania modeli i kojarzenia ich w czasie wykonywania. Te podejścia dzielą się na dwie kategorie, znane jako widok pierwszej kompozycji, i wyświetl pierwszy skład modelu. Wybór między wyświetlaniem pierwszej kompozycji i wyświetlaniem pierwszego składu modelu jest kwestią preferencji i złożoności. Jednak wszystkie podejścia mają ten sam cel, który jest przeznaczony dla widoku, aby model widoku został przypisany do jej właściwości BindingContext.

W widoku pierwszej kompozycji aplikacja składa się koncepcyjnie z widokami, które łączą się z modelami widoku, od których zależą. Główną zaletą tego podejścia jest to, że ułatwia tworzenie luźno powiązanych, testowalnych aplikacji jednostkowych, ponieważ modele widoku nie są zależne od samych widoków. Łatwo jest również zrozumieć strukturę aplikacji, postępując zgodnie ze strukturą wizualną, zamiast śledzić wykonywanie kodu, aby zrozumieć, jak klasy są tworzone i skojarzone. Ponadto pierwsza konstrukcja widoku jest zgodna z Xamarin.Forms systemem nawigacji odpowiedzialnym za konstruowanie stron podczas nawigacji, co sprawia, że model widoku jest złożony i nieprawidłowo dopasowany do platformy.

W przypadku pierwszego składu modelu widoku aplikacja składa się koncepcyjnie z modelami wyświetlania, a usługa jest odpowiedzialna za lokalizowanie widoku dla modelu widoku. Wyświetlanie pierwszej kompozycji modelu wydaje się bardziej naturalne dla niektórych deweloperów, ponieważ tworzenie widoku może być abstrakcyjne, co pozwala im skupić się na logicznej strukturze aplikacji bez interfejsu użytkownika. Ponadto umożliwia tworzenie modeli widoku przez inne modele widoku. Jednak takie podejście jest często złożone i może stać się trudne do zrozumienia, w jaki sposób są tworzone i skojarzone różne części aplikacji.

Porada

Zachowaj niezależne wyświetlanie modeli i widoków. Powiązanie widoków z właściwością w źródle danych powinno być główną zależnością widoku od odpowiedniego modelu widoku. W szczególności nie należy odwoływać się do typów widoków, takich jak Button i ListView, z modeli widoku. Postępując zgodnie z zasadami opisanymi tutaj, modele wyświetlania można testować w izolacji, co zmniejsza prawdopodobieństwo wystąpienia usterek oprogramowania przez ograniczenie zakresu.

W poniższych sekcjach omówiono główne podejścia do łączenia modeli widoku z widokami.

Deklaratywne tworzenie modelu widoku

Najprostszym podejściem jest utworzenie deklaratywnego wystąpienia odpowiedniego modelu widoku w języku XAML. Po skonstruowaniu widoku zostanie również skonstruowany odpowiedni obiekt modelu widoku. To podejście przedstawiono w poniższym przykładzie kodu:

<ContentPage ... xmlns:local="clr-namespace:eShop">  
    <ContentPage.BindingContext>  
        <local:LoginViewModel />  
    </ContentPage.BindingContext>  
    ...  
</ContentPage>

Po utworzeniu ContentPage obiektu wystąpienie obiektu LoginViewModel jest automatycznie konstruowane i ustawiane jako widok BindingContext.

Ta deklaratywna konstrukcja i przypisanie modelu widoku według widoku ma przewagę, że jest prosta, ale ma wadę, że wymaga domyślnego konstruktora (bez parametrów) w modelu widoku.

Programowe tworzenie modelu widoku

Widok może mieć kod w pliku za kodem, który powoduje przypisanie modelu widoku do jego BindingContext właściwości. Jest to często realizowane w konstruktorze widoku, jak pokazano w poniższym przykładzie kodu:

public LoginView()  
{  
    InitializeComponent();  
    BindingContext = new LoginViewModel(navigationService);  
}

Programowa konstrukcja i przypisanie modelu widoku w kodzie widoku ma przewagę nad tym, że jest prosta. Jednak główną wadą tego podejścia jest to, że widok musi zapewnić model widoku z wszelkimi wymaganymi zależnościami. Użycie kontenera wstrzykiwania zależności może pomóc zachować luźne sprzężenie między modelem widoku i widoku. Aby uzyskać więcej informacji, zobacz Wstrzykiwanie zależności.

Tworzenie widoku zdefiniowanego jako szablon danych

Widok można zdefiniować jako szablon danych i skojarzyć z typem modelu widoku. Szablony danych można zdefiniować jako zasoby lub można je zdefiniować w tekście w kontrolce, która będzie wyświetlać model widoku. Zawartość kontrolki jest wystąpieniem modelu widoku, a szablon danych jest używany do wizualnego reprezentowania go. Ta technika jest przykładem sytuacji, w której model widoku jest najpierw tworzone, po czym następuje utworzenie widoku.

Automatyczne tworzenie modelu widoku za pomocą lokalizatora modelu widoku

Lokalizator modelu widoku to niestandardowa klasa, która zarządza wystąpieniem modeli widoku i ich skojarzenia z widokami. W aplikacji ViewModelLocator mobilnej eShopOnContainers klasa ma dołączoną właściwość , AutoWireViewModelktóra służy do kojarzenia modeli widoku z widokami. W kodzie XAML widoku ta dołączona właściwość jest ustawiona na wartość true, aby wskazać, że model widoku powinien być automatycznie połączony z widokiem, jak pokazano w poniższym przykładzie kodu:

viewModelBase:ViewModelLocator.AutoWireViewModel="true"

Właściwość AutoWireViewModel jest właściwością, którą można powiązać, zainicjowaną z wartością false, a jej wartość zmienia wywoływaną procedurę OnAutoWireViewModelChanged obsługi zdarzeń. Ta metoda rozwiązuje model widoku dla widoku. Poniższy przykład kodu pokazuje, jak to jest osiągane:

private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)  
{  
    var view = bindable as Element;  
    if (view == null)  
    {  
        return;  
    }  

    var viewType = view.GetType();  
    var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");  
    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;  
    var viewModelName = string.Format(  
        CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);  

    var viewModelType = Type.GetType(viewModelName);  
    if (viewModelType == null)  
    {  
        return;  
    }  
    var viewModel = _container.Resolve(viewModelType);  
    view.BindingContext = viewModel;  
}

Metoda OnAutoWireViewModelChanged próbuje rozwiązać model widoku przy użyciu podejścia opartego na konwencji. Ta konwencja zakłada, że:

  • Modele widoków znajdują się w tym samym zestawie co typy widoków.
  • Widoki znajdują się w obiekcie . Wyświetla podrzędną przestrzeń nazw.
  • Modele wyświetlania znajdują się w elemecie . Podrzędna przestrzeń nazw ViewModels.
  • Wyświetlanie nazw modeli odpowiada nazwom widoków i kończy się ciągiem "ViewModel".

OnAutoWireViewModelChanged Na koniec metoda ustawia BindingContext typ widoku na rozpoznany typ modelu widoku. Aby uzyskać więcej informacji na temat rozpoznawania typu modelu widoku, zobacz Rozwiązanie.

Takie podejście ma przewagę nad tym, że aplikacja ma jedną klasę, która jest odpowiedzialna za utworzenie wystąpienia modeli wyświetlania i ich połączenia z widokami.

Porada

Użyj lokalizatora modelu widoku, aby ułatwić podstawianie. Lokalizator modelu widoku może być również używany jako punkt podstawienia dla alternatywnych implementacji zależności, takich jak w przypadku testów jednostkowych lub danych czasu projektowania.

Aktualizowanie widoków w odpowiedzi na zmiany w bazowym modelu lub modelu widoku

Wszystkie klasy modelu widoku i modelu, które są dostępne dla widoku, powinny implementować INotifyPropertyChanged interfejs. Zaimplementowanie tego interfejsu w modelu widoku lub klasie modelu umożliwia klasie dostarczanie powiadomień o zmianie do dowolnych kontrolek powiązanych z danymi w widoku, gdy zmienia się wartość właściwości bazowej.

Aplikacje powinny być zaprojektowane pod kątem poprawnego użycia powiadomienia o zmianie właściwości, spełniając następujące wymagania:

  • Zawsze zgłaszaj PropertyChanged zdarzenie, jeśli wartość właściwości publicznej ulegnie zmianie. Nie zakładaj, że podniesienie PropertyChanged zdarzenia można zignorować ze względu na wiedzę na temat sposobu tworzenia powiązań XAML.
  • Zawsze zgłaszaj PropertyChanged zdarzenie dla wszystkich właściwości obliczeniowych, których wartości są używane przez inne właściwości w modelu lub modelu widoku.
  • Zawsze wywoływanie PropertyChanged zdarzenia na końcu metody, która powoduje zmianę właściwości lub gdy obiekt jest znany jako bezpieczny. Wywołanie zdarzenia przerywa operację przez synchroniczne wywoływanie programów obsługi zdarzenia. Jeśli dzieje się to w trakcie operacji, może uwidocznić obiekt do funkcji wywołania zwrotnego, gdy jest w niebezpiecznym, częściowo zaktualizowanym stanie. Ponadto istnieje możliwość wyzwalania kaskadowych zmian przez PropertyChanged zdarzenia. Kaskadowe zmiany zwykle wymagają ukończenia aktualizacji, zanim zmiana kaskadowa będzie bezpieczna do wykonania.
  • Nigdy nie zgłaszaj PropertyChanged zdarzenia, jeśli właściwość nie ulegnie zmianie. Oznacza to, że przed zgłoszeniem PropertyChanged zdarzenia należy porównać stare i nowe wartości.
  • Nigdy nie zgłaszaj PropertyChanged zdarzenia podczas konstruktora modelu widoku, jeśli inicjujesz właściwość. Kontrolki powiązane z danymi w widoku nie będą subskrybowane, aby otrzymywać powiadomienia o zmianach w tym momencie.
  • Nigdy nie zgłaszaj więcej niż jednego PropertyChanged zdarzenia z tym samym argumentem nazwy właściwości w ramach pojedynczego synchronicznego wywołania publicznej metody klasy. Na przykład, biorąc pod uwagę NumberOfItems właściwość, której magazyn zapasowy jest _numberOfItems polem, jeśli metoda zwiększa _numberOfItems się pięćdziesiąt razy podczas wykonywania pętli, powinna zgłaszać tylko powiadomienie o zmianie właściwości dla NumberOfItems właściwości raz, po zakończeniu wszystkich prac. W przypadku metod asynchronicznych zgłoś PropertyChanged zdarzenie dla danej nazwy właściwości w każdym synchronicznym segmencie asynchronicznego łańcucha kontynuacji.

Aplikacja mobilna eShopOnContainers używa ExtendedBindableObject klasy do dostarczania powiadomień o zmianach, które przedstawiono w poniższym przykładzie kodu:

public abstract class ExtendedBindableObject : BindableObject  
{  
    public void RaisePropertyChanged<T>(Expression<Func<T>> property)  
    {  
        var name = GetMemberInfo(property).Name;  
        OnPropertyChanged(name);  
    }  

    private MemberInfo GetMemberInfo(Expression expression)  
    {  
        ...  
    }  
}

Klasa platformy BindableObject Xamarin.Form implementuje INotifyPropertyChanged interfejs i udostępnia metodę OnPropertyChanged . Klasa ExtendedBindableObject udostępnia metodę RaisePropertyChanged wywoływania powiadomienia o zmianie właściwości, a w ten sposób używa funkcji zapewnianych przez klasę BindableObject .

Każda klasa modelu widoku w aplikacji mobilnej eShopOnContainers pochodzi z ViewModelBase klasy, która z kolei pochodzi z ExtendedBindableObject klasy . W związku z tym każda klasa modelu widoku używa RaisePropertyChanged metody w ExtendedBindableObject klasie w celu udostępnienia powiadomienia o zmianie właściwości. Poniższy przykład kodu pokazuje, jak aplikacja mobilna eShopOnContainers wywołuje powiadomienie o zmianie właściwości przy użyciu wyrażenia lambda:

public bool IsLogin  
{  
    get  
    {  
        return _isLogin;  
    }  
    set  
    {  
        _isLogin = value;  
        RaisePropertyChanged(() => IsLogin);  
    }  
}

Należy pamiętać, że użycie wyrażenia lambda w ten sposób wiąże się z niewielkim kosztem wydajności, ponieważ wyrażenie lambda musi być oceniane dla każdego wywołania. Chociaż koszt wydajności jest niewielki i zwykle nie będzie miał wpływu na aplikację, koszty mogą być naliczane, gdy istnieje wiele powiadomień o zmianach. Jednak zaletą tego podejścia jest zapewnienie bezpieczeństwa i refaktoryzacji typów kompilacji podczas zmieniania nazw właściwości.

Interakcja interfejsu użytkownika przy użyciu poleceń i zachowań

W aplikacjach mobilnych akcje są zwykle wywoływane w odpowiedzi na akcję użytkownika, taką jak kliknięcie przycisku, które można zaimplementować przez utworzenie programu obsługi zdarzeń w pliku za pomocą kodu. Jednak w wzorcu MVVM należy unikać odpowiedzialności za implementację akcji z modelem widoku i umieszczanie kodu w tle.

Polecenia zapewniają wygodny sposób reprezentowania akcji, które mogą być powiązane z kontrolkami w interfejsie użytkownika. Hermetyzują kod implementujący akcję i ułatwiają oddzielenie go od jej wizualnej reprezentacji w widoku. Xamarin.Forms Zawiera kontrolki, które mogą być deklaratywne połączone z poleceniem, a te kontrolki będą wywoływać polecenie, gdy użytkownik wchodzi w interakcję z kontrolką.

Zachowania umożliwiają również deklaratywne połączenie kontrolek z poleceniem. Jednak zachowania mogą służyć do wywoływania akcji skojarzonej z zakresem zdarzeń zgłaszanych przez kontrolkę. W związku z tym zachowania dotyczą wielu tych samych scenariuszy co kontrolki z obsługą poleceń, zapewniając jednocześnie większą elastyczność i kontrolę. Ponadto zachowania mogą być również używane do kojarzenia obiektów poleceń lub metod z kontrolkami, które nie zostały specjalnie zaprojektowane do interakcji z poleceniami.

Implementowanie poleceń

Wyświetlanie modeli zwykle uwidacznia właściwości polecenia w celu powiązania z widoku, które są wystąpieniami obiektów implementujących ICommand interfejs. Wiele Xamarin.Forms kontrolek udostępnia Command właściwość , która może być związana z obiektem dostarczonym ICommand przez model widoku. Interfejs ICommand definiuje metodę Execute , która hermetyzuje samą operację, metodę CanExecute , która wskazuje, czy można wywołać polecenie, oraz CanExecuteChanged zdarzenie, które występuje w przypadku wystąpienia zmian mających wpływ na to, czy polecenie powinno zostać wykonane. Klasy Command i Command<T> dostarczane przez Xamarin.Formselement implementują ICommand interfejs , gdzie T jest typem argumentów do Execute i CanExecute.

W modelu widoku powinien istnieć obiekt typu Command lub Command<T> dla każdej właściwości publicznej w modelu widoku typu ICommand. Konstruktor Command lub Command<T> wymaga obiektu wywołania zwrotnego Action wywoływanego podczas wywoływania ICommand.Execute metody. Metoda CanExecute jest opcjonalnym parametrem konstruktora i jest elementem Func , który zwraca boolwartość .

Poniższy kod pokazuje, jak Command wystąpienie, które reprezentuje polecenie register, jest konstruowane przez określenie delegata Register do metody modelu widoku:

public ICommand RegisterCommand => new Command(Register);

Polecenie jest uwidocznione w widoku za pomocą właściwości zwracającej odwołanie do obiektu ICommand. Execute Gdy metoda jest wywoływana w Command obiekcie, po prostu przekazuje wywołanie do metody w modelu widoku za pośrednictwem delegata określonego w konstruktorzeCommand.

Metodę asynchroniczną można wywołać za pomocą polecenia i przy użyciu async słów kluczowych i await podczas określania delegata Execute polecenia. Oznacza to, że wywołanie zwrotne jest elementem Task i powinno być oczekiwane. Na przykład poniższy kod pokazuje, w jaki sposób Command wystąpienie, które reprezentuje polecenie logowania, jest tworzone przez określenie delegata SignInAsync do metody modelu widoku:

public ICommand SignInCommand => new Command(async () => await SignInAsync());

Parametry można przekazać do Execute akcji i CanExecute przy użyciu klasy , Command<T> aby utworzyć wystąpienie polecenia. Na przykład poniższy kod pokazuje sposób Command<T> użycia wystąpienia w celu wskazania, że NavigateAsync metoda będzie wymagać argumentu typu string:

public ICommand NavigateCommand => new Command<string>(NavigateAsync);

W klasach Command i Command<T> delegat metody CanExecute w każdym konstruktorze jest opcjonalny. Jeśli delegat nie zostanie określony, Command funkcja zwróci wartość true .CanExecute Jednak model widoku może wskazywać na zmianę stanu polecenia CanExecute przez wywołanie ChangeCanExecute metody w Command obiekcie . Powoduje to wywołanie CanExecuteChanged zdarzenia. Wszystkie kontrolki w interfejsie użytkownika powiązane z poleceniem zaktualizują ich stan włączony, aby odzwierciedlić dostępność polecenia powiązanego z danymi.

Wywoływanie poleceń z widoku

W poniższym przykładzie LoginView kodu pokazano, jak element Grid w klasie tworzy powiązanie RegisterCommandLoginViewModel z klasą TapGestureRecognizer przy użyciu wystąpienia:

<Grid Grid.Column="1" HorizontalOptions="Center">  
    <Label Text="REGISTER" TextColor="Gray"/>  
    <Grid.GestureRecognizers>  
        <TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />  
    </Grid.GestureRecognizers>  
</Grid>

Parametr polecenia można również opcjonalnie zdefiniować przy użyciu CommandParameter właściwości . Typ oczekiwanego argumentu jest określony w Execute metodach i CanExecute target. Polecenie TapGestureRecognizer spowoduje automatyczne wywołanie polecenia docelowego, gdy użytkownik wchodzi w interakcję z dołączoną kontrolką. Jeśli podano parametr polecenia, zostanie przekazany jako argument do delegata Execute polecenia.

Implementowanie zachowań

Zachowania umożliwiają dodawanie funkcji do kontrolek interfejsu użytkownika bez konieczności ich podklasy. Zamiast tego funkcja jest implementowana w klasie zachowania i dołączona do kontrolki tak, jakby była częścią samej kontrolki. Zachowania umożliwiają zaimplementowanie kodu, który zwykle trzeba napisać jako kod za pomocą kodu, ponieważ bezpośrednio wchodzi w interakcje z interfejsem API kontrolki, w taki sposób, że można go zwięźle dołączyć do kontrolki i spakować do ponownego użycia w więcej niż jednym widoku lub aplikacji. W kontekście maszyny MVVM zachowania są przydatnym podejściem do łączenia kontrolek z poleceniami.

Zachowanie dołączone do kontrolki za pośrednictwem dołączonych właściwości jest nazywane zachowaniem dołączonym. Zachowanie może następnie użyć uwidocznionego interfejsu API elementu, do którego jest dołączony, aby dodać funkcjonalność do tej kontrolki lub innych kontrolek w drzewie wizualnym widoku. Aplikacja mobilna eShopOnContainers zawiera klasę LineColorBehavior , która jest dołączonym zachowaniem. Aby uzyskać więcej informacji na temat tego zachowania, zobacz Wyświetlanie błędów walidacji.

Zachowanie Xamarin.Forms jest klasą, która pochodzi z Behavior klasy lub Behavior<T> , gdzie T jest typem kontrolki, do której należy zastosować zachowanie. Te klasy zapewniają OnAttachedTo metody i OnDetachingFrom , które powinny zostać zastąpione w celu zapewnienia logiki, która zostanie wykonana, gdy zachowanie jest dołączone do kontrolek i odłączone od nich.

W aplikacji BindableBehavior<T> mobilnej eShopOnContainers klasa pochodzi z Behavior<T> klasy . BindableBehavior<T> Celem klasy jest zapewnienie klasy bazowej dla Xamarin.Forms zachowań, które wymagają BindingContext ustawienia zachowania do dołączonej kontrolki.

Klasa BindableBehavior<T> udostępnia metodę, którą można OnAttachedTo zastąpić, która ustawia BindingContext zachowanie, oraz metodę, OnDetachingFrom która umożliwia wyczyszczenie elementu BindingContext. Ponadto klasa przechowuje odwołanie do dołączonej kontrolki AssociatedObject we właściwości .

Aplikacja mobilna eShopOnContainers zawiera klasę EventToCommandBehavior , która wykonuje polecenie w odpowiedzi na zdarzenie. Ta klasa pochodzi z BindableBehavior<T> klasy , aby zachowanie można było powiązać i wykonać określony ICommand przez Command właściwość, gdy zachowanie jest używane. Poniższy przykład kodu przedstawia klasę EventToCommandBehavior :

public class EventToCommandBehavior : BindableBehavior<View>  
{  
    ...  
    protected override void OnAttachedTo(View visualElement)  
    {  
        base.OnAttachedTo(visualElement);  

        var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();  
        if (events.Any())  
        {  
            _eventInfo = events.FirstOrDefault(e => e.Name == EventName);  
            if (_eventInfo == null)  
                throw new ArgumentException(string.Format(  
                        "EventToCommand: Can't find any event named '{0}' on attached type",   
                        EventName));  

            AddEventHandler(_eventInfo, AssociatedObject, OnFired);  
        }  
    }  

    protected override void OnDetachingFrom(View view)  
    {  
        if (_handler != null)  
            _eventInfo.RemoveEventHandler(AssociatedObject, _handler);  

        base.OnDetachingFrom(view);  
    }  

    private void AddEventHandler(  
            EventInfo eventInfo, object item, Action<object, EventArgs> action)  
    {  
        ...  
    }  

    private void OnFired(object sender, EventArgs eventArgs)  
    {  
        ...  
    }  
}

Metody OnAttachedTo i OnDetachingFrom służą do rejestrowania i wyrejestrowania programu obsługi zdarzeń dla zdarzenia zdefiniowanego EventName we właściwości . Następnie po uruchomieniu zdarzenia wywoływana OnFired jest metoda , która wykonuje polecenie.

Zaletą używania EventToCommandBehavior polecenia do wykonywania polecenia po uruchomieniu zdarzenia jest to, że polecenia mogą być skojarzone z kontrolkami, które nie zostały zaprojektowane do interakcji z poleceniami. Ponadto powoduje to przeniesienie kodu obsługującego zdarzenia w celu wyświetlenia modeli, w których można je przetestować jednostkowo.

Wywoływanie zachowań z widoku

Jest EventToCommandBehavior to szczególnie przydatne w przypadku dołączania polecenia do kontrolki, która nie obsługuje poleceń. Na przykład ProfileView polecenie używa EventToCommandBehavior elementu , aby wykonać OrderDetailCommand polecenie , gdy ItemTapped zdarzenie jest uruchamiane na liście ListView zamówień użytkownika, jak pokazano w poniższym kodzie:

<ListView>  
    <ListView.Behaviors>  
        <behaviors:EventToCommandBehavior             
            EventName="ItemTapped"  
            Command="{Binding OrderDetailCommand}"  
            EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />  
    </ListView.Behaviors>  
    ...  
</ListView>

W czasie wykonywania obiekt EventToCommandBehavior będzie reagować na interakcję z obiektem ListView. Po wybraniu elementu w elemencie ListViewzdarzenie zostanie wyzwolone ItemTapped , co spowoduje wykonanie OrderDetailCommand elementu w elemencie ProfileViewModel. Domyślnie argumenty zdarzeń dla zdarzenia są przekazywane do polecenia . Te dane są konwertowane, ponieważ są przekazywane między obiektem źródłowym i docelowym przez konwerter określony we EventArgsConverter właściwości , która zwraca wartość Item elementu ListView z obiektu ItemTappedEventArgs. W związku z tym po wykonaniu OrderDetailCommand wybranego Order parametru jest przekazywany jako parametr do zarejestrowanej akcji.

Aby uzyskać więcej informacji na temat zachowań, zobacz Zachowania.

Podsumowanie

Wzorzec Model-View-ViewModel (MVVM) pomaga całkowicie oddzielić logikę biznesową i prezentacji aplikacji od interfejsu użytkownika. Utrzymanie czystej separacji między logiką aplikacji a interfejsem użytkownika pomaga rozwiązać wiele problemów programistycznych i może ułatwić testowanie, konserwację i rozwijanie aplikacji. Może również znacznie poprawić możliwości ponownego użycia kodu i umożliwić deweloperom i projektantom interfejsu użytkownika łatwiejsze współpracę podczas opracowywania odpowiednich części aplikacji.

Za pomocą wzorca MVVM interfejs użytkownika aplikacji oraz podstawowa prezentacja i logika biznesowa są oddzielone od trzech oddzielnych klas: widoku, który hermetyzuje logikę interfejsu użytkownika i interfejsu użytkownika; model widoku, który hermetyzuje logikę i stan prezentacji; i model, który hermetyzuje logikę biznesową i dane aplikacji.