Udostępnij za pośrednictwem


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 Xamarin.Forms deweloperskie zwykle obejmuje utworzenie interfejsu użytkownika w języku XAML, a następnie dodanie 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 w przejrzysty sposób oddzielić logikę biznesową i prezentacji aplikacji od interfejsu użytkownika. Utrzymywanie 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 wykorzystania kodu i umożliwić deweloperom i projektantom interfejsu użytkownika łatwiejsze współpracę podczas tworzenia 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.

Wzorzec MVVM

Rysunek 2–1. Wzorzec MVVM

Oprócz zrozumienia obowiązków poszczególnych składników ważne jest również, aby zrozumieć, jak współdziałają 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 to być trudne lub ryzykowne, aby go zmienić. W tym scenariuszu model widoku działa jako karta dla klas modeli 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 w całości w języku XAML. W związku z tym nowa wersja widoku powinna 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 klas. 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 zawartości wyświetlanej przez użytkownika 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-behind może zawierać logikę interfejsu użytkownika, która implementuje zachowanie wizualne, które jest trudne do wyrażenia w języku XAML, takich 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.

Napiwek

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 dostępność polecenia, 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, które ma być wywoływane lub zgłaszane zdarzenie. W odpowiedzi zachowanie może następnie wywołać ICommand metodę w modelu widoku lub metodę w modelu widoku.

ViewModel

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 pomocą 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.

Napiwek

Zachowaj czas reakcji interfejsu użytkownika przy użyciu operacji asynchronicznych. Aplikacje mobilne powinny odblokować wątek interfejsu użytkownika, aby poprawić postrzeganie wydajności przez użytkownika. W związku z tym w modelu widoku użyj metod asynchronicznych dla operacji we/wy i zgłoś 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 modeli. Zazwyczaj istnieje relacja jeden do wielu między modelem widoku a klasami modeli. Model widoku może zdecydować się na bezpośrednie uwidocznienie klas modelu w widoku, aby kontrolki w widoku mogły powiązać dane bezpośrednio z nimi. W takim przypadku klasy modeli 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ż udostępnia 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 przez widok.

Napiwek

Scentralizowanie konwersji danych w warstwie konwersji. Można również użyć 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 jeśli dane wymagają specjalnego formatowania, które nie udostępnia model widoku.

Aby model widoku mógł uczestniczyć w dwukierunkowym powiązaniu danych z widokiem, jego właściwości muszą zgłosić PropertyChanged zdarzenie. Wyświetlanie modeli spełnia to wymaganie przez zaimplementowanie interfejsu INotifyPropertyChanged i podniesienie 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 zwalnia dewelopera z konieczności zaimplementowania interfejsu INotifyCollectionChanged w kolekcjach.

Model

Klasy modeli to klasy inne niż wizualne, 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 wygenerowane 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 wyświetlania można łączyć z widokami przy użyciu funkcji powiązania danych w Xamarin.Formsprogramie . 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 należą do dwóch kategorii, znanych jako pierwszy skład widoku, i wyświetl pierwszy skład modelu. Wybór między pierwszą kompozycją widoku i wyświetlaniem pierwszego składu modelu jest problemem 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 właściwości BindingContext.

Po wyświetleniu pierwszej kompozycji aplikacja składa się koncepcyjnie z widokami, które łączą się z modelami widoków, od których zależą. Główną zaletą tego podejścia jest to, że ułatwia tworzenie luźno połączonych, testowalnych aplikacji jednostkowych, ponieważ modele widoków nie są zależne od samych widoków. Łatwo jest również zrozumieć strukturę aplikacji, postępując zgodnie ze strukturą wizualizacji, 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 widoków, 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 nieuprzyjającej interfejsu użytkownika aplikacji. 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.

Napiwek

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 odwołują się do typów widoków, takich jak Button i ListView, z modeli widoków. Postępując zgodnie z zasadami opisanymi tutaj, modele wyświetlania mogą być testowane w izolacji, co zmniejsza prawdopodobieństwo wystąpienia wad 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. Takie 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 LoginViewModel obiektu 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 pomocą kodu, 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);  
}

Programowe tworzenie i przypisywanie modelu widoku w obrębie kodu w widoku ma zaletę, ż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 iniekcji 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 ramach kontrolki, 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 najpierw utworzono wystąpienie modelu widoku, a następnie utworzenie widoku.

Automatyczne tworzenie modelu widoku za pomocą lokalizatora modelu widoku

Lokalizator modelu widoku to klasa niestandardowa, która zarządza tworzeniem wystąpień modeli widoku i ich skojarzeniem z widokami. W aplikacji ViewModelLocator mobilnej eShopOnContainers klasa ma dołączoną właściwość AutoWireViewModel, która służy do kojarzenia modeli widoku z widokami. W kodzie XAML widoku ta dołączona właściwość ma 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 powiązana właściwość, która jest inicjowana na wartość false, a gdy jej wartość zmienia OnAutoWireViewModelChanged procedurę obsługi zdarzeń jest wywoływana. Ta metoda rozwiązuje model widoku widoku dla widoku. W poniższym przykładzie kodu pokazano, jak można to osiągnąć:

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ć problem z modelem widoku przy użyciu podejścia opartego na konwencji. Ta konwencja zakłada, że:

  • Modele wyświetlania 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 . Przestrzeń nazw podrzędnych modelu ViewModels.
  • Wyświetl nazwy modeli odpowiadają nazwam widoków i kończą 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 pojedynczą klasę, która jest odpowiedzialna za utworzenie wystąpienia modeli wyświetlania i ich połączenia z widokami.

Napiwek

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 modelu lub modelu widoku bazowego

Wszystkie klasy modelu i modelu widoku, 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 wartość właściwości bazowej ulegnie zmianie.

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że być ignorowane z powodu znajomości sposobu tworzenia powiązania 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. Podniesienie zdarzenia przerywa operację, wywołując programy obsługi zdarzenia synchronicznie. Jeśli dzieje się to w trakcie operacji, może to spowodować uwidocznienie obiektu do funkcji wywołania zwrotnego, gdy jest w niebezpiecznym, częściowo zaktualizowanym stanie. Ponadto istnieje możliwość wyzwolenia 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 podniesieniem 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ą w tym momencie subskrybowane w celu otrzymywania powiadomień o zmianach.
  • 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łosić tylko powiadomienie o zmianie właściwości dla NumberOfItems właściwości raz, po zakończeniu całej pracy. 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 są wyświetlane 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 i w ten sposób używa funkcji udostępnianych 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 typu 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ć, tworząc program obsługi zdarzeń w pliku za pomocą kodu. Jednak we wzorcu MVVM należy unikać odpowiedzialności za implementację akcji z modelem widoku i umieszczanie kodu w kodzie.

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ą również służyć 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ć danymi powiązanymi 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, które mają 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 i Execute 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 or 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, w jaki sposób Command wystąpienie, które reprezentuje polecenie rejestru, 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 metody w modelu widoku za pośrednictwem delegata określonego w konstruktorzeCommand.

Metodę asynchroniczną można wywołać za pomocą polecenia i przy async użyciu 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, jak Command wystąpienie, które reprezentuje polecenie logowania, jest konstruowane 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 Command<T> klasy , aby utworzyć wystąpienie polecenia. Na przykład poniższy kod pokazuje, w jaki sposób Command<T> wystąpienie jest używane do 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 pełnomocnik nie zostanie określony, Command funkcja zwróci wartość true .CanExecute Jednak model widoku może wskazywać zmianę stanu polecenia CanExecute przez wywołanie ChangeCanExecute metody w Command obiekcie. Spowoduje 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 kodu pokazano, jak element Grid w LoginView klasie jest powiązany RegisterCommand LoginViewModel 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 docelowych. Element TapGestureRecognizer automatycznie wywołuje polecenie docelowe, 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 funkcjonalność 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 by napisać jako kod za pomocą kodu, ponieważ bezpośrednio wchodzi w interakcję z interfejsem API kontrolki, w taki sposób, że może być zwięzłie dołączony do kontrolki i spakowany do ponownego użycia w więcej niż jednym widoku lub aplikacji. W kontekście maszyny WIRTUALNEJ MVVM zachowania są przydatne do łączenia kontrolek z poleceniami.

Zachowanie dołączone do kontrolki za pomocą dołączonych właściwości jest nazywane dołączonym zachowaniem. 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 to klasa, 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 będzie wykonywana, 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ę przesłoniętą OnAttachedTo , która ustawia BindingContext zachowanie, oraz metodę, OnDetachingFrom która umożliwia wyczyszczenie BindingContextmetody . 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że wiązać się z i wykonywać określone 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, gdy zdarzenie zostanie wyzwolony, OnFired metoda zostanie wywołana, co spowoduje wykonanie polecenia.

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 spowoduje 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 zdarzenie, 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 elementem ListView. Po wybraniu elementu w elemencie ItemTapped ListViewzdarzenie zostanie wyzwolone, co spowoduje wykonanie elementu OrderDetailCommand w elemencie ProfileViewModel. Domyślnie argumenty zdarzeń dla zdarzenia są przekazywane do polecenia . Te dane są konwertowane, ponieważ są przekazywane między elementem źródłowym i docelowym przez konwerter określony we EventArgsConverter właściwości , która zwraca Item wartość ListView z ItemTappedEventArgs. W związku z tym po wykonaniu OrderDetailCommand zaznaczony Order element jest przekazywany jako parametr do zarejestrowanej akcji.

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

Podsumowanie

Wzorzec Model-View-ViewModel (MVVM) pomaga w przejrzysty sposób oddzielić logikę biznesową i prezentacji aplikacji od interfejsu użytkownika. Utrzymywanie 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 wykorzystania kodu i umożliwić deweloperom i projektantom interfejsu użytkownika łatwiejsze współpracę podczas tworzenia odpowiednich części aplikacji.

Korzystając ze wzorca MVVM, interfejs użytkownika aplikacji i podstawowej prezentacji i logiki biznesowej jest oddzielony 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; oraz model, który hermetyzuje logikę biznesową i dane aplikacji.