Поделиться через


Model-View-ViewModel (MVVM)

Совет

Это содержимое является фрагментом из электронной книги, шаблонов корпоративных приложений с помощью .NET MAUI, доступных в .NET Docs или в виде бесплатного скачиваемого PDF-файла, который можно прочитать в автономном режиме.

Корпоративные шаблоны приложений с помощью эскиза обложки электронной книги .NET MAUI .

Интерфейс разработчика .NET MAUI обычно включает создание пользовательского интерфейса в XAML, а затем добавление кода позади, которое работает в пользовательском интерфейсе. Сложные проблемы с обслуживанием могут возникнуть, так как приложения изменяются и увеличиваются в размерах и области. Эти проблемы включают тесное взаимодействие между элементами управления пользовательским интерфейсом и бизнес-логикой, что повышает затраты на изменение пользовательского интерфейса и трудности модульного тестирования такого кода.

Шаблон MVVM помогает четко отделять бизнес-логику приложения и логику презентации от пользовательского интерфейса. Поддержание четкого разделения между логикой приложения и пользовательским интерфейсом помогает решить многочисленные проблемы разработки и упрощает тестирование, обслуживание и развитие приложения. Он также может значительно улучшить возможности повторного использования кода и позволяет разработчикам и конструкторам пользовательского интерфейса работать более легко при разработке соответствующих частей приложения.

Шаблон MVVM

В шаблоне MVVM есть три основных компонента: модель, представление и модель представления. Каждый служит отдельной целью. На схеме ниже показаны связи между тремя компонентами.

Шаблон MVVM

Помимо понимания обязанностей каждого компонента, важно также понять, как они взаимодействуют. На высоком уровне представление "знает о" модели представления, и модель представления "знает о" модели, но модель не знает о модели представления, и модель представления не знает о представлении. Поэтому модель представления изолирует представление от модели и позволяет модели развиваться независимо от представления.

Преимущества использования шаблона MVVM приведены ниже.

  • Если существующая реализация модели инкапсулирует существующую бизнес-логику, это может быть трудно или рискованно изменить ее. В этом сценарии модель представления выступает в качестве адаптера для классов моделей и не позволяет вносить основные изменения в код модели.
  • Разработчики могут создавать модульные тесты для модели представления и модели без использования представления. Модульные тесты для модели представления могут выполнять точно те же функции, что и в представлении.
  • Пользовательский интерфейс приложения можно изменить без касания модели представления и кода модели, если представление реализовано полностью в XAML или C#. Поэтому новая версия представления должна работать с существующей моделью представления.
  • Конструкторы и разработчики могут работать независимо и параллельно с их компонентами во время разработки. Конструкторы могут сосредоточиться на представлении, в то время как разработчики могут работать над моделью представления и компонентами модели.

Ключ к эффективному использованию MVVM заключается в понимании того, как фактор кода приложения в правильные классы и как классы взаимодействуют. В следующих разделах рассматриваются обязанности каждого класса в шаблоне MVVM.

Представления

Представление отвечает за определение структуры, макета и внешнего вида того, что пользователь видит на экране. В идеале каждое представление определяется в XAML с ограниченным кодом, который не содержит бизнес-логику. Однако в некоторых случаях код программной части может содержать логику пользовательского интерфейса, которая реализует визуальное поведение, которое трудно выразить в XAML, например анимации.

В приложении .NET MAUI представление обычно является производным или ContentViewпроизводным от него классомContentPage. Однако представления также могут быть представлены шаблоном данных, который указывает элементы пользовательского интерфейса, которые будут использоваться для визуального представления объекта при отображении. Шаблон данных в виде представления не имеет программной части и предназначен для привязки к определенному типу модели представления.

Совет

Избегайте включения и отключения элементов пользовательского интерфейса в коде позади.

Убедитесь, что модели представления отвечают за определение изменений логического состояния, влияющих на некоторые аспекты отображения представления, например наличие команды или указание на ожидание операции. Поэтому включите и отключите элементы пользовательского интерфейса путем привязки к просмотру свойств модели, а не включение и отключение их в коде.

Существует несколько вариантов выполнения кода в модели представления в ответ на взаимодействие с представлением, например нажатие кнопки или выбор элемента. Если элемент управления поддерживает команды, свойство command элемента управления может быть привязано к свойству ICommand в модели представления. При вызове команды элемента управления код в модели представления будет выполнен. Помимо команд, поведение может быть присоединено к объекту в представлении и может прослушивать вызов команды или вызываемое событие. В ответ поведение может вызывать ICommand в модели представления или методе в модели представления.

ViewModel

Модель представления реализует свойства и команды, к которым представление может привязать данные, и уведомляет представление о любых изменениях состояния с помощью событий уведомлений об изменении. Свойства и команды, предоставляемые моделью представления, определяют функциональные возможности, предоставляемые пользовательским интерфейсом, но представление определяет способ отображения этой функции.

Совет

Следите за реагированием пользовательского интерфейса с помощью асинхронных операций.

Приложения с несколькими платформами должны разблокировать поток пользовательского интерфейса, чтобы улучшить восприятие производительности пользователя. Поэтому в модели представления используйте асинхронные методы для операций ввода-вывода и вызывайте события для асинхронного уведомления представлений об изменениях свойств.

Модель представления также отвечает за координацию взаимодействия представления с любыми необходимыми классами моделей. Обычно между моделью представления и классами моделей обычно существует связь "один ко многим". Модель представления может предоставить классы моделей непосредственно представлению, чтобы элементы управления в представлении могли напрямую привязать данные к ним. В этом случае классы модели должны быть разработаны для поддержки привязки данных и событий уведомлений об изменении.

Каждая модель представления предоставляет данные из модели в форме, которую представление может легко использовать. Для этого модель представления иногда выполняет преобразование данных. Размещение этого преобразования данных в модели представления является хорошей идеей, так как она предоставляет свойства, к которым может привязаться представление. Например, модель представления может объединить значения двух свойств, чтобы упростить отображение в представлении.

Совет

Централизация преобразований данных в уровне преобразования.

Кроме того, можно использовать преобразователи в качестве отдельного слоя преобразования данных, который находится между моделью представления и представлением. Это может потребоваться, например, если для данных требуется специальное форматирование, которое не предоставляет модель представления.

Чтобы модель представления участвовала в двусторонней привязке данных с представлением, его свойства должны вызвать PropertyChanged событие. Модели просмотра удовлетворяют этому требованию, реализуя INotifyPropertyChanged интерфейс, и вызывая PropertyChanged событие при изменении свойства.

Для коллекций предоставляется понятное ObservableCollection<T> представление. Эта коллекция реализует уведомление об изменении коллекции, что позволяет разработчику реализовать INotifyCollectionChanged интерфейс в коллекциях.

Модель

Классы модели — это не визуальные классы, которые инкапсулируют данные приложения. Поэтому модель можно рассматривать как представляющую модель домена приложения, которая обычно включает модель данных вместе с бизнес-логикой и логикой проверки. Примерами объектов модели являются объекты передачи данных (DTOs), обычные старые объекты CLR (POC), а также созданные сущности и прокси-объекты.

Классы моделей обычно используются в сочетании со службами или репозиториями, которые инкапсулируют доступ к данным и кэширование.

Подключение моделей представления к представлениям

Модели просмотра можно подключить к представлениям с помощью возможностей привязки данных .NET MAUI. Существует множество подходов, которые можно использовать для создания представлений и представлений моделей и связывания их во время выполнения. Эти подходы делятся на две категории, известные как первая композиция представления и первая композиция модели представления. Выбор между первой композицией представления и моделью представления — это проблема предпочтения и сложности. Однако все подходы используют одну и ту же цель, что и для представления, назначаемой модели представления свойству BindingContext.

При первом создании представления приложение концептуально состоит из представлений, которые подключаются к моделям представления, от которых они зависят. Основное преимущество этого подхода заключается в том, что это упрощает создание слабо связанных, модульных тестируемых приложений, так как модели представления не зависят от самих представлений. Также легко понять структуру приложения, следуя его визуальной структуре, а не отслеживать выполнение кода, чтобы понять, как создаются и связаны классы. Кроме того, представление первого построения соответствует системе навигации Майкрософт Maui, ответственной за создание страниц при возникновении навигации, что делает модель представления первой сложной композицией и несогласованной с платформой.

При первой композиции модели представления приложение концептуально состоит из моделей представления, а служба, отвечающая за поиск представления для модели представления. Первая композиция модели просмотра чувствует себя более естественной для некоторых разработчиков, так как создание представления может быть абстрагировано, что позволяет им сосредоточиться на логической структуре, отличной от пользовательского интерфейса приложения. Кроме того, она позволяет создавать модели просмотра другими моделями представлений. Однако этот подход часто сложный, и становится трудно понять, как создаются и связаны различные части приложения.

Совет

Сохраняйте независимые модели представления и представления.

Привязка представлений к свойству в источнике данных должна быть основной зависимостью представления от соответствующей модели представления. В частности, не ссылайтесь на типы представлений, такие как Button и ListView, из моделей представлений. Следуя принципам, описанным здесь, модели представления можно протестировать в изоляции, поэтому снижение вероятности дефектов программного обеспечения путем ограничения области.

В следующих разделах рассматриваются основные подходы к подключению моделей представления к представлениям.

Создание модели представления декларативно

Самый простой подход заключается в том, чтобы представление декларативно создать экземпляр соответствующей модели представления в XAML. При построении представления также будет создан соответствующий объект модели представления. Этот подход демонстрируется в следующем примере кода:

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

ContentPage При создании экземпляр LoginViewModel создается автоматически и устанавливается в качестве представленияBindingContext.

Это декларативное построение и назначение модели представления по представлению имеет преимущество, что это просто, но имеет недостаток, что он требует конструктора по умолчанию (без параметров) в модели представления.

Создание модели представления программным способом

Представление может иметь код в файле программной части, что приводит к назначению модели представления свойству BindingContext . Это часто выполняется в конструкторе представления, как показано в следующем примере кода:

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

Программное построение и назначение модели представления в коде представления имеет преимущество, что это просто. Однако основным недостатком этого подхода является то, что представление должно предоставить модель представления с любыми необходимыми зависимостями. Использование контейнера внедрения зависимостей может помочь поддерживать свободное взаимодействие между моделью представления и представления. Дополнительные сведения см. в разделе Введение зависимостей.

Обновление представлений в ответ на изменения в базовой модели представления или модели

Все классы модели представления и модели, доступные для представления, должны реализовать INotifyPropertyChanged интерфейс. Реализация этого интерфейса в модели представления или классе модели позволяет классу предоставлять уведомления об изменениях любым элементам управления, привязанным к данным, в представлении при изменении значения базового свойства.

Приложение должно быть разработано для правильного использования уведомления об изменении свойств, выполнив следующие требования:

  • Всегда вызывайте PropertyChanged событие, если значение общедоступного свойства изменяется. Не предполагайте, что повышение PropertyChanged события можно игнорировать из-за знаний о том, как происходит привязка XAML.
  • Всегда вызывайте PropertyChanged событие для любых вычисляемых свойств, значения которых используются другими свойствами в модели представления или модели.
  • Всегда вызывайте PropertyChanged событие в конце метода, который изменяет свойство, или когда объект, как известно, находится в безопасном состоянии. Вызов события прерывает операцию, вызывая обработчики события синхронно. Если это происходит в середине операции, объект может предоставлять функции обратного вызова, если он находится в небезопасном, частично обновленном состоянии. Кроме того, можно запускать каскадные изменения событиями PropertyChanged . Каскадные изменения обычно требуют завершения обновлений перед выполнением каскадного изменения.
  • Никогда не вызывает PropertyChanged событие, если свойство не изменяется. Это означает, что перед созданием PropertyChanged события необходимо сравнить старые и новые значения.
  • Никогда не вызывайте PropertyChanged событие во время конструктора модели представления, если вы инициализируете свойство. Элементы управления с привязкой к данным в представлении не будут подписаны на получение уведомлений об изменениях на данный момент.
  • Никогда не вызывайте несколько PropertyChanged событий с одним аргументом имени свойства в одном синхронном вызове общедоступного метода класса. Например, учитывая NumberOfItems свойство, резервное хранилище которого является _numberOfItems полем, если метод увеличивается _numberOfItems пятьдесят раз во время выполнения цикла, он должен вызывать уведомление NumberOfItems об изменении свойства только один раз, после завершения работы. Для асинхронных методов вызовите PropertyChanged событие для заданного имени свойства в каждом синхронном сегменте асинхронной цепочки продолжения.

Простой способ предоставить эту функциональность — создать расширение BindableObject класса. В этом примере ExtendedBindableObject класс предоставляет уведомления об изменениях, которые показаны в следующем примере кода:

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 ...
    }
}

Класс .NET MAUIBindableObject реализует INotifyPropertyChanged интерфейс и предоставляет OnPropertyChanged метод. Класс ExtendedBindableObject предоставляет RaisePropertyChanged метод для вызова уведомления об изменении свойства и использует функциональные возможности, предоставляемые классом BindableObject .

Затем классы модели просмотра могут быть производными от ExtendedBindableObject класса. Таким образом, каждый класс модели представления использует RaisePropertyChanged метод в ExtendedBindableObject классе для предоставления уведомления об изменении свойств. В следующем примере кода показано, как мультиплатформенное приложение eShop вызывает уведомление об изменении свойств с помощью лямбда-выражения:

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

Использование лямбда-выражения таким образом включает небольшую стоимость производительности, так как лямбда-выражение должно оцениваться для каждого вызова. Хотя производительность невелика и обычно не влияет на приложение, затраты могут начисляться при наличии большого количества уведомлений об изменениях. Однако преимущество этого подхода заключается в том, что он обеспечивает безопасность типов компиляции и рефакторинг при переименовании свойств.

Платформы MVVM

Шаблон MVVM хорошо установлен в .NET, и сообщество создало множество платформ, которые помогают упростить эту разработку. Каждая платформа предоставляет различные наборы функций, но они стандартны для предоставления общей модели представления с реализацией INotifyPropertyChanged интерфейса. К дополнительным функциям платформ MVVM относятся пользовательские команды, вспомогательные функции навигации, компоненты внедрения зависимостей и компоненты указателя служб и интеграция платформы пользовательского интерфейса. Хотя использовать эти платформы не нужно, они могут ускорить и стандартизировать разработку. Приложение eShop с несколькими платформами использует набор средств MVVM сообщества .NET. При выборе платформы следует учитывать потребности вашего приложения и сильные стороны вашей команды. Приведенный ниже список содержит некоторые из наиболее распространенных платформ MVVM для .NET MAUI.

Взаимодействие пользовательского интерфейса с помощью команд и поведения

В мультиплатформенных приложениях действия обычно вызываются в ответ на действие пользователя, например нажатие кнопки, которое можно реализовать путем создания обработчика событий в файле кода программной части. Однако в шаблоне MVVM ответственность за реализацию действия лежит на модели представления, и следует избежать размещения кода в коде.

Команды предоставляют удобный способ представления действий, которые можно привязать к элементам управления в пользовательском интерфейсе. Они инкапсулируют код, реализующий действие и помогающий сохранить его отложенным от визуального представления в представлении. Таким образом, модели представления становятся более переносимыми на новые платформы, так как они не имеют прямой зависимости от событий, предоставляемых платформой пользовательского интерфейса платформы. .NET MAUI включает элементы управления, которые могут быть декларативно подключены к команде, и эти элементы управления вызывают команду, когда пользователь взаимодействует с элементом управления.

Поведение также позволяет элементам управления декларативно подключаться к команде. Однако поведение можно использовать для вызова действия, связанного с рядом событий, вызываемых элементом управления. Таким образом, поведение определяет многие из тех же сценариев, что и элементы управления с поддержкой команд, обеспечивая большую степень гибкости и управления. Кроме того, поведение также можно использовать для связывания объектов команд или методов с элементами управления, которые не были специально разработаны для взаимодействия с командами.

Реализация команд

Обычно модели просмотра предоставляют общедоступные свойства для привязки из представления, реализующей ICommand интерфейс. Многие элементы управления и жесты .NET MAUI предоставляют Command свойство, которое может быть привязано к объекту, предоставленному ICommand моделью представления. Элемент управления кнопкой является одним из наиболее часто используемых элементов управления, предоставляя свойство команды, которое выполняется при нажатии кнопки.

Примечание.

Хотя можно предоставить фактическую реализацию ICommand интерфейса, который использует модель представления (например, Command<T> или RelayCommand), рекомендуется предоставить команды как ICommandобщедоступные. Таким образом, если вы когда-либо хотите изменить реализацию на более позднюю дату, ее можно легко переключить.

Интерфейс ICommand определяет Execute метод, который инкапсулирует саму операцию, CanExecute метод, который указывает, может ли вызываться команда, и CanExecuteChanged событие, возникающее при возникновении изменений, влияющих на выполнение команды. В большинстве случаев мы предоставим Execute только метод для команд. Дополнительные сведения ICommandсм. в документации по команде для .NETMAUI.

В .NET представлены Command классы, реализующие ICommand интерфейс, где T тип аргументов и CanExecuteExecute .Command<T> MAUI Command и Command<T> являются основными реализациями, обеспечивающими минимальный набор функциональных возможностей, необходимых для ICommand интерфейса.

Примечание.

Многие платформы MVVM предлагают более широкие возможности реализации ICommand интерфейса.

Command<T> Для Command конструктора требуется объект обратного вызова Action, который вызывается при вызове ICommand.Execute метода. Метод CanExecute является необязательным параметром конструктора и представляет собой Func, который возвращает логическое значение.

Приложение eShop с несколькими платформами использует RelayCommand и AsyncRelayCommand. Основное преимущество современных приложений заключается в том, что AsyncRelayCommand более эффективная функциональность для асинхронных операций.

В следующем коде показано, как Command экземпляр, представляющий команду регистрации, создается путем указания делегата для метода модели регистрации представления:

public ICommand RegisterCommand { get; }

Команда предоставляется представлению с помощью свойства, возвращающего ссылку на объект ICommand. Execute При вызове метода в Command объекте он просто перенаправит вызов метода в модели представления с помощью делегата, указанного в конструктореCommand. Асинхронный метод может вызываться командой с помощью асинхронных и ожидающих ключевых слов при указании делегата Execute команды. Это означает, что обратный вызов является и Task должен ожидаться. Например, в следующем коде показано, как ICommand экземпляр, представляющий команду входа, создается путем указания делегата в SignInAsync метод модели представления:

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

Параметры можно передать в Execute и CanExecute действия с помощью AsyncRelayCommand<T> класса для создания экземпляра команды. Например, в следующем коде показано, как AsyncRelayCommand<T> используется экземпляр для указания того, что NavigateAsync методу потребуется аргумент строки типа:

public ICommand NavigateCommand { get; }

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

В обоих RelayCommand RelayCommand<T> классах делегат метода в каждом конструкторе является необязательным CanExecute . Если делегат не указан, Command возвращает значение true для CanExecute. Однако модель представления может указывать на изменение состояния команды CanExecute путем вызова ChangeCanExecute метода в объекте Command . Это приводит CanExecuteChanged к возникновению события. Затем все элементы управления пользовательского интерфейса, привязанные к команде, обновят их состояние, чтобы отразить доступность команды, привязанной к данным.

Вызов команд из представления

В следующем примере кода показано, как Grid привязывается LoginView к RegisterCommand LoginViewModel классу с помощью экземпляра TapGestureRecognizer :

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

При необходимости можно определить параметр команды с помощью CommandParameter свойства. Тип ожидаемого аргумента указывается в Execute методах и CanExecute целевых методах. При TapGestureRecognizer взаимодействии пользователя с присоединенным элементом управления автоматически вызывается целевая команда. Если CommandParameterэто указано, будет передан в качестве аргумента делегату execute команды.

Реализация поведения

Поведение позволяет добавлять функциональные возможности в элементы управления пользовательским интерфейсом без их подкласса. Функциональность реализуется в классе реакции на событие и присоединяется к элементу управления так, как если бы она была его частью. Поведение позволяет реализовать код, который обычно требуется писать как код программной части, так как он напрямую взаимодействует с API элемента управления, таким образом, что он может быть кратко присоединен к элементу управления и упаковано для повторного использования в нескольких представлениях или приложениях. В контексте MVVM поведение является полезным подходом для подключения элементов управления к командам.

Поведение, присоединенное к элементу управления с помощью присоединенных свойств, называется присоединенным поведением. Затем поведение может использовать предоставленный API элемента, к которому он присоединяется, чтобы добавить функциональные возможности в этот элемент управления или другие элементы управления в визуальном дереве представления.

Поведение .NET MAUI — это класс, производный от Behavior класса или Behavior<T> класса, где T является типом элемента управления, к которому должно применяться поведение. Эти классы предоставляют OnAttachedTo и OnDetachingFrom методы, которые следует переопределить, чтобы обеспечить логику, которая будет выполняться при присоединении поведения к элементам управления и отсоединения от элементов управления.

В приложении BindableBehavior<T> с несколькими платформами eShop класс является производным от Behavior<T> класса. Цель BindableBehavior<T> этого класса — предоставить базовый класс для поведения .NET MAUI , требующего BindingContext задания поведения присоединенного элемента управления.

Класс BindableBehavior<T> предоставляет переопределимый метод, который задает BindingContext поведение, и переопределимый OnAttachedTo OnDetachingFrom метод, который очищает BindingContext.

Приложение eShop с несколькими платформами включает класс EventToCommandBehavior , предоставляемый набором MAUI средств Сообщества. EventToCommandBehavior выполняет команду в ответ на событие. Этот класс является производным от BaseBehavior<View> класса, чтобы поведение может привязаться к свойству и выполнить ICommand его Command при использовании поведения. Следующий пример кода демонстрирует класс 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);
        }
    }
}

OnDetachingFrom Методы OnAttachedTo используются для регистрации и отмены регистрации обработчика событий для события, определенного в свойствеEventName. Затем при срабатывании OnTriggerHandled события вызывается метод, который выполняет команду.

Преимущество выполнения EventToCommandBehavior команды при срабатывании события заключается в том, что команды могут быть связаны с элементами управления, которые не были разработаны для взаимодействия с командами. Кроме того, этот код перемещает код обработки событий для просмотра моделей, где его можно модульного тестирования.

Вызов поведения из представления

Это EventToCommandBehavior особенно полезно для присоединения команды к элементу управления, который не поддерживает команды. Например, LoginView используется EventToCommandBehavior для выполнения ValidateCommand , когда пользователь изменяет значение пароля, как показано в следующем коде:

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

Во время выполнения EventToCommandBehavior ответит на взаимодействие с Entryним. Когда пользователь вводит Entry в поле, TextChanged событие будет запускаться, которое будет выполняться ValidateCommand в поле LoginViewModel. По умолчанию аргументы событий для события передаются команде. При необходимости EventArgsConverter свойство можно использовать для преобразования EventArgs предоставленного события в значение, которое команда ожидает в качестве входных данных.

Дополнительные сведения о поведении см. в разделе "Поведение " в Центре разработчиков .NET MAUI .

Итоги

Шаблон Model-View-ViewModel (MVVM) помогает четко отделять бизнес-логику приложения и логику презентации от пользовательского интерфейса. Поддержание четкого разделения между логикой приложения и пользовательским интерфейсом помогает решить многочисленные проблемы разработки и упрощает тестирование, обслуживание и развитие приложения. Он также может значительно улучшить возможности повторного использования кода и позволяет разработчикам и конструкторам пользовательского интерфейса работать более легко при разработке соответствующих частей приложения.

С помощью шаблона MVVM пользовательский интерфейс приложения и базовой презентации и бизнес-логики разделены на три отдельных класса: представление, инкапсулирующее логику пользовательского интерфейса и пользовательского интерфейса; модель представления, которая инкапсулирует логику презентации и состояние; и модель, которая инкапсулирует бизнес-логику и данные приложения.