Model-View-ViewModel (MVVM)

Napiwek

Ta zawartość jest fragmentem książki eBook, wzorców aplikacji dla przedsiębiorstw przy użyciu platformy .NET, dostępnej na platformie .NET MAUIDocs lub jako bezpłatnego pliku PDF do pobrania, który można odczytać w trybie offline.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

Środowisko deweloperskie platformy .NET MAUI zwykle obejmuje utworzenie interfejsu użytkownika w języku XAML, a następnie dodanie kodu, który działa w interfejsie użytkownika. Złożone problemy z konserwacją mogą wystąpić w miarę modyfikowania aplikacji i zwiększania ich rozmiaru i zakresu. 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 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 ułatwia 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 współpracę w przypadku 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. Na poniższym diagramie przedstawiono relacje między trzema składnikami.

The MVVM pattern

Oprócz zrozumienia obowiązków poszczególnych składników ważne jest również zrozumienie sposobu ich interakcji. 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 istniejąca implementacja modelu hermetyzuje istniejącą logikę biznesową, zmiana może być trudna lub ryzykowna. W tym scenariuszu model widoku działa jako karta dla klas modelu i uniemożliwia wprowadzanie 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 modelu widoku i kodu modelu, pod warunkiem, że widok jest implementowany całkowicie w języku XAML lub C#. W związku z tym nowa wersja widoku powinna działać z istniejącym modelem widoku.
  • Projektant i deweloperzy mogą pracować niezależnie i współbieżnie na swoich składnikach podczas opracowywania. Projektant mogą skupić się na widoku, podczas gdy deweloperzy mogą pracować nad modelem widoku i składnikami modelu.

Kluczem do efektywnego korzystania z maszyny MVVM jest zrozumienie, jak uwzględniać kod aplikacji w odpowiednich klasach i jak wchodzić w interakcje 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 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.

W aplikacji .NET MAUI widok jest zazwyczaj klasą pochodną lub ContentViewpochodnąContentPage. 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 widoku 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ść Command kontrolki 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 zdarzenie, które ma zostać podniesione. W odpowiedzi zachowanie może następnie wywołać polecenie ICommand 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 wieloplatformowe 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.

Połączenie wyświetlanie modeli do widoków

Modele widoków można łączyć z widokami przy użyciu funkcji powiązania danych platformy .NET MAUI. 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 systemem nawigacji firmy Microsoft Maui, który jest odpowiedzialny za konstruowanie stron podczas nawigacji, co sprawia, że model widoku jest złożony i nieprawidłowo dopasowany do platformy.

Dzięki pierwszej kompozycji modelu widoku aplikacja składa się koncepcyjnie z modelami widoków, z usługą odpowiedzialną 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 należy odwoływać się do typów widoków, takich jak Button i ListView, z modeli wyświetlania. 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>
    <!-- Omitted for brevity... -->
</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, co 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.

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

Wszystkie klasy modelu i modelu widoku, które są dostępne dla widoku, powinny implementować interfejs [INotifyPropertyChanged . 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.

Prostym sposobem zapewnienia tej funkcjonalności jest utworzenie rozszerzenia BindableObject klasy. W tym przykładzie ExtendedBindableObject klasa udostępnia powiadomienia o zmianie, 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)
    {
        // Omitted for brevity ...
    }
}

Klasa platformy .NET MAUIimplementuje INotifyPropertyChanged interfejs i udostępnia metodęOnPropertyChanged.BindableObject 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 .

Klasy modelu widoku mogą następnie 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 wieloplatformowa eShopOnContainers wywołuje powiadomienie o zmianie właściwości przy użyciu wyrażenia lambda:

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

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. Mimo że koszt wydajności jest niewielki i zwykle nie wpływa 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.

Struktury MVVM

Wzorzec MVVM jest dobrze ugruntowany na platformie .NET, a społeczność stworzyła wiele struktur, które ułatwiają ten rozwój. Każda struktura udostępnia inny zestaw funkcji, ale standardem jest zapewnienie wspólnego modelu widoku z implementacją interfejsu INotifyPropertyChanged . Dodatkowe funkcje struktur MVVM obejmują polecenia niestandardowe, pomocniki nawigacji, składniki iniekcji zależności/lokalizatora usług i integrację platformy interfejsu użytkownika. Chociaż nie jest konieczne korzystanie z tych struktur, mogą przyspieszyć i znormalizować programowanie. Aplikacja wieloplatformowa eShopOnContainers używa zestawu narzędzi .NET Community MVVM Toolkit. Podczas wybierania struktury należy wziąć pod uwagę potrzeby aplikacji i mocne strony zespołu. Poniższa lista zawiera niektóre z bardziej typowych platform MVVM dla platformy .NET MAUI.

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

W aplikacjach wieloplatformowych akcje są zwykle wywoływane w odpowiedzi na akcję użytkownika, taką jak kliknięcie przycisku, które można zaimplementować, tworząc procedurę 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. Dzięki temu modele wyświetlania stają się bardziej przenośne na nowe platformy, ponieważ nie mają bezpośredniej zależności od zdarzeń udostępnianych przez platformę interfejsu użytkownika platformy. Platforma .NET MAUI 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 publiczne w celu powiązania z widoku, które implementuje ICommand interfejs. Wiele kontrolek i gestów platformy .NET MAUI udostępnia Command właściwość, która może być danymi powiązanymi z obiektem dostarczonym ICommand przez model widoku. Kontrolka przycisku jest jedną z najczęściej używanych kontrolek, zapewniając właściwość polecenia wykonywaną po kliknięciu przycisku.

Uwaga

Chociaż istnieje możliwość uwidocznienia rzeczywistej implementacji interfejsu używanego przez ICommand model wyświetlania (na przykład Command<T> lub RelayCommand), zaleca się publiczne uwidocznienie poleceń jako ICommand. W ten sposób, jeśli kiedykolwiek trzeba zmienić implementację w późniejszym terminie, można ją łatwo zamienić.

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. W większości przypadków będziemy dostarczać tylko metodę Execute dla naszych poleceń. Aby uzyskać bardziej szczegółowe omówienie programu ICommand, zapoznaj się z dokumentacją poleceń dla platformy .NET MAUI.

Dostarczane z platformą .NET MAUI to klasy iCommand<T>, które implementują ICommand interfejs, gdzie T jest typem argumentów do Execute i CanExecute.Command Command i Command<T> to podstawowe implementacje, które zapewniają minimalny zestaw funkcji potrzebnych dla interfejsu ICommand .

Uwaga

Wiele platform MVVM oferuje bardziej rozbudowane implementacje funkcji interfejsu ICommand .

Konstruktor Command lub Command<T> wymaga obiektu wywołania zwrotnego akcji wywoływanego podczas wywoływania ICommand.Execute metody. Metoda CanExecute jest opcjonalnym parametrem konstruktora i jest func, który zwraca wartość logiczną.

Aplikacja wieloplatformowa eShopOnContainers używa poleceń RelayCommand i AsyncRelayCommand. Główną korzyścią dla nowoczesnych aplikacji jest AsyncRelayCommand zapewnienie lepszej funkcjonalności operacji asynchronicznych.

Poniższy kod pokazuje, w jaki sposób Command wystąpienie, które reprezentuje polecenie rejestru, jest konstruowane przez określenie delegata do metody modelu Widoku rejestru:

public ICommand RegisterCommand { get; }

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 asynchronicznego przy użyciu słów kluczowych asynchronicznych 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 ICommand wystąpienie, które reprezentuje polecenie logowania, jest konstruowane przez określenie delegata do SignInAsync metody modelu widoku:

public ICommand SignInCommand { get; }
...
SignInCommand = new AsyncRelayCommand(async () => await SignInAsync());

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

public ICommand NavigateCommand { get; }

...
NavigateCommand = new AsyncRelayCommand<string>(NavigateAsync);

W klasach RelayCommand i RelayCommand<T> delegat metody CanExecute w każdym konstruktorze jest opcjonalny. Jeśli delegat nie zostanie określony, Command funkcja zwróci wartość true dla elementu 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 interfejsu 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 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 docelowych. Element TapGestureRecognizer automatycznie wywołuje polecenie docelowe, gdy użytkownik wchodzi w interakcję z dołączoną kontrolką. Wartość CommandParameter, jeśli zostanie podana, zostanie przekazana 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 napisać jako kod w tle, 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.

Zachowanie platformy .NET MAUI to klasa pochodząca z Behavior klasy or 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> wieloplatformowej eShopOnContainers klasa pochodzi z Behavior<T> klasy . BindableBehavior<T> Celem klasy jest zapewnienie klasy bazowej dla zachowań platformy .NETMAUI, 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 .

Aplikacja wieloplatformowa eShopOnContainers zawiera klasę EventToCommandBehavior dostarczaną przez MAUI zestaw narzędzi Community. EventToCommandBehavior Wykonuje polecenie w odpowiedzi na zdarzenie występujące. Ta klasa pochodzi z BaseBehavior<View> 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 :

/// <summary>
/// The <see cref="EventToCommandBehavior"/> is a behavior that allows the user to invoke a <see cref="ICommand"/> through an event. It is designed to associate Commands to events exposed by controls that were not designed to support Commands. It allows you to map any arbitrary event on a control to a Command.
/// </summary>
public class EventToCommandBehavior : BaseBehavior<VisualElement>
{
    // Omitted for brevity...

    /// <inheritdoc/>
    protected override void OnAttachedTo(VisualElement bindable)
    {
        base.OnAttachedTo(bindable);
        RegisterEvent();
    }

    /// <inheritdoc/>
    protected override void OnDetachingFrom(VisualElement bindable)
    {
        UnregisterEvent();
        base.OnDetachingFrom(bindable);
    }

    static void OnEventNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        => ((EventToCommandBehavior)bindable).RegisterEvent();

    void RegisterEvent()
    {
        UnregisterEvent();

        var eventName = EventName;
        if (View is null || string.IsNullOrWhiteSpace(eventName))
        {
            return;
        }

        eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't resolve the event.", nameof(EventName));

        ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
        ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);

        eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, this) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't create event handler.", nameof(EventName));

        eventInfo.AddEventHandler(View, eventHandler);
    }

    void UnregisterEvent()
    {
        if (eventInfo is not null && eventHandler is not null)
        {
            eventInfo.RemoveEventHandler(View, eventHandler);
        }

        eventInfo = null;
        eventHandler = null;
    }

    /// <summary>
    /// Virtual method that executes when a Command is invoked
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    [Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
    protected virtual void OnTriggerHandled(object? sender = null, object? eventArgs = null)
    {
        var parameter = CommandParameter
            ?? EventArgsConverter?.Convert(eventArgs, typeof(object), null, null);

        var command = Command;
        if (command?.CanExecute(parameter) ?? false)
        {
            command.Execute(parameter);
        }
    }
}

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, OnTriggerHandled 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

Zdarzenie EventToCommandBehavior jest szczególnie przydatne w przypadku dołączania polecenia do kontrolki, która nie obsługuje poleceń. Na przykład element LoginView używa elementu EventToCommandBehavior , aby wykonać ValidateCommand element , gdy użytkownik zmieni wartość hasła, jak pokazano w poniższym kodzie:

<Entry
    IsPassword="True"
    Text="{Binding Password.Value, Mode=TwoWay}">
    <!-- Omitted for brevity... -->
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateCommand}" />
    </Entry.Behaviors>
    <!-- Omitted for brevity... -->
</Entry>

W czasie wykonywania obiekt EventToCommandBehavior będzie reagować na interakcję z elementem Entry. Gdy użytkownik wpisze dane w Entry polu, TextChanged zdarzenie zostanie wyzwolony, co spowoduje wykonanie elementu ValidateCommand w obiekcie LoginViewModel. Domyślnie argumenty zdarzeń dla zdarzenia są przekazywane do polecenia . W razie potrzeby EventArgsConverter właściwość może służyć do konwertowania dostarczonego EventArgs przez zdarzenie na wartość oczekiwaną przez polecenie jako dane wejściowe.

Aby uzyskać więcej informacji na temat zachowań, zobacz Zachowania w Centrum deweloperów platformy .NET MAUI .

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 ułatwia 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 współpracę w przypadku tworzenia odpowiednich części aplikacji.

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