Partager via


Modèle-vue-vue modèle (MVVM)

Conseil

Ce contenu est un extrait du livre électronique Modèles d’application d’entreprise avec .NET MAUI, disponible dans la .documentation .NET ou en tant que PDF téléchargeable gratuitement qui peut être lu hors connexion.

Miniature de la couverture du livre électronique Enterprise Application Patterns Using .NET MAUI.

L’expérience de développement .NET MAUI implique généralement la création d’une interface utilisateur en XAML, puis l’ajout de code-behind fonctionnant sur l’interface utilisateur. Des problèmes de maintenance complexes peuvent survenir quand les applications sont modifiées et qu’elles augmentent en taille et en étendue. Ces problèmes incluent le couplage étroit entre les contrôles d’IU et la logique métier, ce qui augmente le coût des modifications de l’IU ainsi que la difficulté d’effectuer des tests unitaires sur ce code.

Le modèle MVVM permet de séparer de manière nette la logique métier et la logique de présentation d’une application de son interface utilisateur (IU). Le maintien d’une séparation nette entre la logique d’application et l’IU permet de résoudre de nombreux problèmes de développement et de faciliter le test, la maintenance ainsi que l’évolution d’une application. Il permet également d’améliorer considérablement les opportunités de réutilisation du code. Ainsi, les développeurs et les concepteurs d’IU peuvent collaborer plus facilement quand ils développent leurs parties respectives d’une application.

Le modèle MVVM

Il existe trois composants principaux dans le modèle MVVM : le modèle, la vue et le modèle de vue. Chacun sert un objectif distinct. Le diagramme ci-dessous montre les relations entre les trois composants.

Le modèle MVVM

Il est important de comprendre les responsabilités de chaque composant, et il est tout aussi important de comprendre la façon dont elles interagissent. À un niveau supérieur, la vue « connaît » le modèle de vue, et le modèle de vue « connaît » le modèle, mais le modèle ignore l’existence du modèle de vue et le modèle de vue ignore l’existence de la vue. Ainsi, le modèle de vue isole la vue du modèle et permet au modèle d’évoluer indépendamment de la vue.

Les avantages liés à l’utilisation du modèle MVVM sont les suivants :

  • Si une implémentation de modèle existante encapsule une logique métier existante, il peut être difficile ou risqué de la changer. Dans ce scénario, le modèle de vue agit comme un adaptateur pour les classes de modèle et vous empêche d’apporter des changements majeurs au code du modèle.
  • Les développeurs peuvent créer des tests unitaires pour le modèle de vue et le modèle, sans utiliser la vue. Les tests unitaires du modèle de vue peuvent offrir exactement les mêmes fonctionnalités que celles utilisées par la vue.
  • L’IU de l’application peut être repensée sans toucher au modèle de vue et au code du modèle, à condition que la vue soit entièrement implémentée en XAML ou en C#. Une nouvelle version de la vue doit donc fonctionner avec le modèle de vue existant.
  • Les concepteurs et les développeurs peuvent travailler indépendamment et simultanément sur leurs composants durant le développement. Les concepteurs peuvent se concentrer sur la vue, tandis que les développeurs peuvent travailler sur le modèle de vue et les composants du modèle.

Pour utiliser efficacement MVVM, vous devez bien comprendre comment factoriser le code d’application dans les classes appropriées, et comment ces classes interagissent. Les sections suivantes décrivent les responsabilités de chacune des classes du modèle MVVM.

Affichage

La vue est chargée de définir la structure, la disposition et l’apparence de ce que l’utilisateur voit à l’écran. Dans l’idéal, chaque vue est définie en XAML, avec un code-behind limité qui ne contient pas de logique métier. Toutefois, dans certains cas, le code-behind peut contenir une logique d’IU qui implémente un comportement visuel difficile à exprimer en XAML, par exemple les animations.

Dans une application .NET MAUI, une vue est généralement une classe dérivée de ContentPage ou de ContentView. Toutefois, les vues peuvent également être représentées par un modèle de données, qui spécifie les éléments d’IU à utiliser pour représenter visuellement un objet quand il est affiché. Un modèle de données en tant que vue n’a pas de code-behind et est conçu pour être lié à un type de modèle de vue spécifique.

Conseil

Évitez d’activer et de désactiver les éléments d’IU dans le code-behind.

Vérifiez que les modèles de vue sont responsables de la définition des changements d’état logiques qui affectent certains aspects de l’affichage de la vue, par exemple la disponibilité d’une commande ou l’indication d’une opération en attente. Vous devez donc activer et désactiver les éléments d’IU en établissant une liaison aux propriétés du modèle de vue, au lieu de les activer et de les désactiver dans le code-behind.

Il existe plusieurs options pour exécuter du code sur le modèle de vue en réponse aux interactions avec la vue, par exemple un clic sur un bouton ou la sélection d’un élément. Si un contrôle prend en charge les commandes, la propriété Command du contrôle peut être liée aux données à une propriété ICommand du modèle de vue. Quand la commande du contrôle est appelée, le code du modèle de vue est exécuté. En plus des commandes, les comportements peuvent être attachés à un objet dans la vue. Ils peuvent écouter une commande à appeler ou l’événement à déclencher. En réponse, le comportement peut ensuite appeler un ICommand sur le modèle de vue ou une méthode sur le modèle de vue.

Vue modèle

Le modèle de vue implémente des propriétés et des commandes avec lesquelles la vue peut effectuer une liaison aux données. Il notifie la vue de tout changement d’état via des événements de notification de changement. Les propriétés et les commandes fournies par le modèle de vue définissent la fonctionnalité à offrir par l’IU, mais la vue détermine la façon dont cette fonctionnalité doit être affichée.

Conseil

Gardez l’IU réactive avec les opérations asynchrones.

Les applications multiplateformes doivent garder le thread d’IU débloqué pour améliorer la perception des performances de l’utilisateur. Ainsi, dans le modèle de vue, utilisez des méthodes asynchrones pour les opérations d’E/S, et déclenchez des événements pour envoyer aux vues de manière asynchrone des notifications sur les changements de propriétés.

Le modèle de vue est également responsable de la coordination des interactions de la vue avec les classes de modèle nécessaires. Il existe généralement une relation un-à-plusieurs entre le modèle de vue et les classes de modèle. Le modèle de vue peut exposer les classes de modèle directement à la vue pour que les contrôles de la vue puissent effectuer une liaison aux données directement avec celles-ci. Dans ce cas, les classes de modèle doivent être conçues pour prendre en charge les événements de liaison de données et de notification de changement.

Chaque modèle de vue fournit les données d’un modèle sous une forme que la vue peut facilement consommer. Pour ce faire, le modèle de vue effectue parfois une conversion de données. Il est judicieux de placer cette conversion de données dans le modèle de vue, car elle fournit des propriétés auxquelles la vue peut se lier. Par exemple, le modèle de vue peut combiner les valeurs de deux propriétés pour faciliter son affichage par la vue.

Conseil

Centralisez les conversions de données dans une couche de conversion.

Il est également possible d’utiliser des convertisseurs en tant que couche de conversion de données distincte située entre le modèle de vue et la vue. Cela peut être nécessaire, par exemple, quand les données nécessitent une mise en forme spéciale que le modèle de vue ne fournit pas.

Pour que le modèle de vue participe à la liaison de données bidirectionnelle avec la vue, ses propriétés doivent déclencher l’événement PropertyChanged. Les modèles de vue répondent à cet impératif en implémentant l’interface INotifyPropertyChanged et en déclenchant l’événement PropertyChanged en cas de changement d’une propriété.

Pour les collections, le ObservableCollection<T> adapté aux vues est fourni. Cette collection implémente la notification des changements apportés aux collections, ce qui évite au développeur d’implémenter l’interface INotifyCollectionChanged sur les collections.

Modèle

Les classes de modèle sont des classes non visuelles qui encapsulent les données de l’application. Ainsi, le modèle peut être considéré comme une représentation du modèle de domaine de l’application, qui comprend généralement un modèle de données avec une logique métier et une logique de validation. Parmi les exemples d’objets de modèle, citons les objets de transfert de données (DTO), les objets CLR traditionnels (POCO) ainsi que les objets d’entité et de proxy générés.

Les classes de modèle sont généralement utilisées conjointement avec des services ou des dépôts qui encapsulent l’accès aux données et la mise en cache.

Connexion de modèles de vue aux vues

Les modèles de vue peuvent être connectés aux vues à l’aide des fonctionnalités de liaison aux données de .NET MAUI. Il existe de nombreuses approches qui permettent de construire des vues et des modèles de vue, et de les associer au moment de l’exécution. Ces approches se divisent en deux catégories, appelées composition de vue en premier et composition de modèle de vue en premier. Le choix entre la composition de vue en premier et la composition de modèle de vue en premier est une question de préférence et de complexité. Toutefois, toutes les approches partagent le même objectif : pour la vue, un modèle de vue doit être affecté à sa propriété BindingContext.

Avec la composition de vue en premier, l’application est composée conceptuellement de vues qui se connectent aux modèles de vue dont elles dépendent. Le principal avantage de cette approche est qu’elle facilite la construction d’applications faiblement couplées et pouvant faire l’objet de tests unitaires, car les modèles de vue ne dépendent pas des vues elles-mêmes. Il est également facile de comprendre la structure de l’application en suivant sa structure visuelle, au lieu d’avoir à suivre l’exécution du code pour comprendre la façon dont les classes sont créées et associées. En outre, la construction en vue première s'aligne sur le système de navigation de Microsoft Maui qui est responsable de la construction des pages lors de la navigation, ce qui rend la composition en vue première complexe et mal alignée avec la plateforme.

Avec la composition de modèle de vue en premier, l’application est composée conceptuellement de modèles de vue, avec un service responsable de la localisation de la vue d’un modèle de vue. La composition de modèle de vue en premier semble plus naturelle à certains développeurs, car la création de la vue peut être mise de côté, ce qui leur permet de se concentrer sur la structure logique de l’application hors IU. De plus, elle permet la création de modèles de vue par d’autres modèles de vue. Toutefois, cette approche est souvent complexe, et il peut s’avérer difficile de comprendre comment les différentes parties de l’application sont créées et associées.

Conseil

Gardez une indépendance entre les modèles de vue et les vues.

La liaison des vues à une propriété dans une source de données doit correspondre à la dépendance principale de la vue par rapport au modèle de vue correspondant. Plus précisément, ne référencez pas de types de vue, par exemple Button et ListView, à partir de modèles de vue. En suivant les principes décrits ici, les modèles de vue peuvent être testés isolément, ce qui réduit la probabilité de défauts logiciels en limitant l’étendue.

Les sections suivantes décrivent les principales approches de connexion des modèles de vue aux vues.

Création d’un modèle de vue de manière déclarative

L’approche la plus simple consiste pour la vue à instancier de manière déclarative son modèle de vue correspondant en XAML. Quand la vue est construite, l’objet de modèle de vue correspondant est également construit. Cette approche est illustrée dans l’exemple de code suivant :

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

Quand ContentPage est créé, une instance de LoginViewModel est automatiquement construite et définie en tant que BindingContext de la vue.

La construction et l’affectation déclaratives du modèle de vue par la vue présentent l’avantage d’être simples, mais présentent l’inconvénient de nécessiter un constructeur par défaut (sans paramètre) dans le modèle de vue.

Création d’un modèle de vue par programmation

Une vue peut avoir du code dans le fichier code-behind, ce qui entraîne l’affectation du modèle de vue à sa propriété BindingContext. Cela s’effectue souvent dans le constructeur de la vue, comme le montre l’exemple de code suivant :

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

La construction et l’affectation programmatiques du modèle de vue dans le code-behind de la vue présentent l’avantage d’être simples. Toutefois, le principal inconvénient de cette approche est que la vue doit fournir au modèle de vue toutes les dépendances nécessaires. L’utilisation d’un conteneur d’injection de dépendances peut contribuer à maintenir un couplage faible entre la vue et le modèle de vue. Pour plus d’informations, consultez Injection de dépendances.

Mise à jour des vues en réponse aux changements apportés au modèle ou au modèle de vue sous-jacent

Toutes les classes de modèle de vue et de modèle accessibles à une vue doivent mettre en œuvre l'interface INotifyPropertyChanged. L’implémentation de cette interface dans une classe de modèle de vue ou de modèle permet à la classe de fournir des notifications de changement à tous les contrôles liés aux données dans la vue en cas de changement de la valeur de propriété sous-jacente.

L’architecture des applications doit être conçue pour permettre une utilisation appropriée des notifications de changement de propriété, en répondant aux impératifs suivants :

  • Déclenchez toujours un événement PropertyChanged en cas de changement de la valeur d’une propriété publique. Ne partez pas du principe que le déclenchement de l’événement PropertyChanged peut être ignoré dans la mesure où vous savez comment la liaison XAML se produit.
  • Déclenchez toujours un événement PropertyChanged pour les propriétés calculées dont les valeurs sont utilisées par d’autres propriétés dans le modèle de vue ou le modèle.
  • Déclenchez toujours l’événement PropertyChanged à la fin de la méthode qui effectue un changement de propriété, ou quand l’objet est dans un état sécurisé. Le déclenchement de l’événement interrompt l’opération en appelant les gestionnaires d’événements de manière synchrone. Si cela se produit au milieu d’une opération, l’objet peut être exposé à des fonctions de rappel alors qu’il se trouve dans un état non sécurisé et partiellement mis à jour. De plus, il est possible que des changements en cascade soient déclenchés par les événements PropertyChanged. Pour pouvoir être effectués de manière sécurisée, les changements en cascade nécessitent généralement au préalable l’exécution des mises à jour.
  • Ne déclenchez jamais d’événement PropertyChanged si la propriété ne change pas. Cela signifie que vous devez comparer les anciennes et les nouvelles valeurs avant de déclencher l’événement PropertyChanged.
  • Ne déclenchez jamais l’événement PropertyChanged durant l’exécution du constructeur d’un modèle de vue si vous initialisez une propriété. Les contrôles liés aux données dans la vue ne se sont pas abonnés à la réception des notifications de changement à ce stade.
  • Ne déclenchez jamais plusieurs événements PropertyChanged avec le même argument de nom de propriété dans un seul appel synchrone d’une méthode publique d’une classe. Par exemple, pour une propriété NumberOfItems dont le magasin de stockage est le champ _numberOfItems, si une méthode incrémente _numberOfItems 50 fois durant l’exécution d’une boucle, elle ne doit déclencher qu’une seule fois la notification de changement de propriété sur la propriété NumberOfItems, une fois le travail effectué. Avec les méthodes asynchrones, déclenchez l’événement PropertyChanged pour un nom de propriété donné dans chaque segment synchrone d’une chaîne de continuation asynchrone.

Il existe un moyen simple de fournir cette fonctionnalité : la création d’une extension de la classe BindableObject. Dans cet exemple, la classe ExtendedBindableObject fournit des notifications de changement, comme le montre l’exemple de code suivant :

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

La classe BindableObject de .NET MAUI implémente l’interface INotifyPropertyChanged et fournit une méthode OnPropertyChanged. La classe ExtendedBindableObject fournit la méthode RaisePropertyChanged pour appeler la notification de changement de propriété. Ainsi, elle utilise les fonctionnalités fournies par la classe BindableObject.

Les classes de modèle de vue peuvent ensuite dériver de la classe ExtendedBindableObject. Ainsi, chaque classe de modèle de vue utilise la méthode RaisePropertyChanged de la classe ExtendedBindableObject pour fournir une notification de changement de propriété. L’exemple de code suivant montre comment l’application multiplateforme eShop appelle la notification de changement de propriété à l’aide d’une expression lambda :

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

L’utilisation d’une expression lambda de cette façon implique un faible coût au niveau des performances, car l’expression lambda doit être évaluée pour chaque appel. Bien que le coût au niveau des performances soit faible et qu’il n’impacte généralement pas une application, vous pouvez être confronté à une accumulation des coûts en cas de nombreuses notifications de changement. Toutefois, cette approche présente l’avantage d’offrir une cohérence des types au moment de la compilation et une prise en charge de la refactorisation au moment du renommage des propriétés.

Frameworks MVVM

Le modèle MVVM est bien établi dans .NET, et la communauté a créé de nombreux frameworks qui facilitent ce développement. Chaque framework fournit un ensemble distinct de fonctionnalités, mais il est usuel qu’il fournisse un modèle de vue commun avec une implémentation de l’interface INotifyPropertyChanged. Les fonctionnalités supplémentaires des frameworks MVVM incluent des commandes personnalisées, des composants d’assistance pour la navigation, des composants d’injection de dépendances/de localisateur de services ainsi que l’intégration de la plateforme d’IU. Bien qu’il ne soit pas nécessaire d’utiliser ces frameworks, ils peuvent accélérer et standardiser votre développement. L’application multiplateforme eShop utilise le kit de ressources MVVM de la communauté .NET. Quand vous choisissez un framework, vous devez prendre en compte les besoins de votre application et les points forts de votre équipe. La liste ci-dessous comprend certains des frameworks MVVM les plus courants pour .NET MAUI.

Interaction avec l’IU à l’aide de commandes et de comportements

Dans les applications multiplateformes, les actions sont généralement appelées en réponse à une action de l’utilisateur, par exemple un clic sur un bouton, qui peut être implémentée à travers la création d’un gestionnaire d’événements dans le fichier code-behind. Toutefois, dans le modèle MVVM, la responsabilité de l’implémentation de l’action incombe au modèle de vue. De plus, le placement de code dans le code-behind doit être évité.

Les commandes offrent un moyen pratique de représenter des actions qui peuvent être liées à des contrôles dans l’IU. Elles encapsulent le code qui implémente l’action et contribuent à le maintenir découplé de sa représentation visuelle dans la vue. Ainsi, vos modèles de vue deviennent plus faciles à porter sur les nouvelles plateformes, car ils n’ont pas de dépendance directe avec les événements fournis par le framework d’IU de la plateforme. .NET MAUI comprend des contrôles qui peuvent être connectés de manière déclarative à une commande. Ces contrôles appellent la commande quand l’utilisateur interagit avec le contrôle.

Les comportements permettent également aux contrôles d’être connectés de manière déclarative à une commande. Toutefois, les comportements peuvent être utilisés pour appeler une action associée à une plage d’événements déclenchée par un contrôle. Ainsi, les comportements répondent à un grand nombre des scénarios propres aux contrôles basés sur des commandes, tout en offrant un plus grand degré de flexibilité et de contrôle. De plus, les comportements peuvent également être utilisés pour associer des objets de commande ou des méthodes à des contrôles qui n’ont pas été spécifiquement conçus pour interagir avec des commandes.

Implémentation des commandes

Les modèles de vue exposent généralement des propriétés publiques, pour la liaison à partir de la vue, qui implémentent l’interface ICommand. De nombreux contrôles et mouvements .NET MAUI fournissent une propriété Command, qui peut être liée aux données d’un objet ICommand fourni par le modèle de vue. Le contrôle bouton est l’un des contrôles les plus couramment utilisés. Il fournit une propriété de commande qui s’exécute quand vous cliquez sur le bouton.

Remarque

Bien qu’il soit possible d’exposer l’implémentation réelle de l’interface ICommand utilisée par votre modèle de vue (par exemple Command<T> ou RelayCommand), il est recommandé d’exposer publiquement vos commandes en tant que ICommand. Ainsi, si vous devez changer l’implémentation plus tard, vous pourrez le faire facilement.

L’interface ICommand définit une méthode Execute, qui encapsule l’opération proprement dite, une méthode CanExecute, qui indique si la commande peut être appelée ainsi qu’un événement CanExecuteChanged, qui a lieu quand des changement se produisent et qu’ils impactent la décision d’exécuter ou non la commande. Dans la plupart des cas, nous fournissons uniquement la méthode Execute pour nos commandes. Pour une vue d’ensemble plus détaillée de ICommand, consultez la documentation relative aux commandes pour .NET MAUI.

Vous disposez avec .NET MAUI des classes Command et Command<T> qui implémentent l’interface ICommand, où T correspond au type des arguments de Execute et CanExecute. Command et Command<T> sont des implémentations de base qui fournissent l’ensemble minimal de fonctionnalités nécessaires à l’interface ICommand.

Notes

De nombreux frameworks MVVM offrent des implémentations plus riches en fonctionnalités de l’interface ICommand.

Le constructeur Command ou Command<T> nécessite un objet de rappel Action, qui est appelé quand la méthode ICommand.Execute est appelée. La méthode CanExecute est un paramètre de constructeur facultatif et un Func qui retourne une valeur booléenne.

L’application multiplateforme eShop utilise RelayCommand et AsyncRelayCommand. Le principal avantage de AsyncRelayCommand pour les applications modernes est d’offrir de meilleures fonctionnalités dans le cadre des opérations asynchrones.

Le code suivant montre comment une instance de Command, qui représente une commande d’inscription, est construite en spécifiant un délégué de la méthode du modèle de vue Register :

public ICommand RegisterCommand { get; }

La commande est exposée à la vue via une propriété qui retourne une référence à ICommand. Quand la méthode Execute est appelée sur l’objet Command, elle transfère simplement l’appel à la méthode dans le modèle de vue via le délégué spécifié dans le constructeur Command. Une méthode asynchrone peut être appelée par une commande à l’aide des mots clés async et await au moment de la spécification du délégué Execute de la commande. Cela indique que le rappel est un Task et qu’il doit être attendu. Par exemple, le code suivant montre comment une instance de ICommand, qui représente une commande de connexion, est construite en spécifiant un délégué de la méthode du modèle de vue SignInAsync :

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

Vous pouvez passer des paramètres aux actions Execute et CanExecute à l’aide de la classe AsyncRelayCommand<T> pour instancier la commande. Par exemple, le code suivant montre comment une instance de AsyncRelayCommand<T> est utilisée pour indiquer que la méthode NavigateAsync nécessite un argument de type chaîne :

public ICommand NavigateCommand { get; }

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

Dans les classes RelayCommand et RelayCommand<T>, le délégué de la méthode CanExecute dans chaque constructeur est facultatif. Si aucun délégué n’est spécifié, Command retourne true pour CanExecute. Toutefois, le modèle de vue peut indiquer un changement dans l’état CanExecute de la commande en appelant la méthode ChangeCanExecute sur l’objet Command. Cela entraîne le déclenchement de l’événement CanExecuteChanged. Tous les contrôles d’IU liés à la commande mettent ensuite à jour leur état d’activation pour refléter la disponibilité de la commande liée aux données.

Appel de commandes à partir d’une vue

L’exemple de code suivant montre comment un Grid dans LoginView se lie à RegisterCommand dans la classe LoginViewModel à l’aide d’une instance de TapGestureRecognizer :

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

Vous pouvez également définir éventuellement un paramètre de commande à l’aide de la propriété CommandParameter. Le type de l’argument attendu est spécifié dans les méthodes cibles Execute et CanExecute. Le TapGestureRecognizer appelle automatiquement la commande cible quand l’utilisateur interagit avec le contrôle attaché. Le CommandParameter, s’il est fourni, est passé en tant qu’argument au délégué Execute de la commande.

Implémentation des comportements

Les comportements permettent d’ajouter des fonctionnalités aux contrôles d’IU sans avoir à les sous-classer. En effet, vous implémentez les fonctionnalités dans une classe de comportement et les attachez au contrôle comme si elles en faisaient partie. Avec les comportements, vous pouvez implémenter du code que vous devez généralement écrire sous forme de code-behind, car il interagit directement avec l’API du contrôle de manière à être attaché directement au contrôle et packagé pour être réutilisé dans plusieurs vues ou applications. Dans le contexte de MVVM, les comportements sont une approche utile pour connecter des contrôles aux commandes.

Un comportement attaché à un contrôle via des propriétés jointes est appelé comportement attaché. Le comportement peut ensuite utiliser l’API exposée de l’élément auquel il est attaché pour ajouter des fonctionnalités à ce contrôle, ou à d’autres contrôles, dans l’arborescence d’éléments visuels de la vue.

Un comportement .NET MAUI est une classe qui dérive de la classe Behavior ou Behavior<T>, où T est le type du contrôle auquel le comportement doit s’appliquer. Ces classes fournissent les méthodes OnAttachedTo et OnDetachingFrom, qui doivent être remplacées pour offrir une logique à exécuter quand le comportement est attaché et détaché des contrôles.

Dans l’application multiplateforme eShop, la classe BindableBehavior<T> dérive de la classe Behavior<T>. L’objectif de la classe BindableBehavior<T> est de fournir une classe de base aux comportements .NET MAUI qui nécessitent la définition du BindingContext du comportement en fonction du contrôle attaché.

La classe BindableBehavior<T> fournit une méthode OnAttachedTo substituable qui définit le BindingContext du comportement ainsi qu’une méthode OnDetachingFrom substituable, qui nettoie le BindingContext.

L’application multiplateforme eShop comprend une classe EventToCommandBehavior, qui est fournie par le kit de ressources de la communauté MAUI. EventToCommandBehavior exécute une commande en réponse à un événement qui se produit. Cette classe dérive de la classe BaseBehavior<View> pour que le comportement puisse se lier et exécuter un ICommand spécifié par une propriété Command quand le comportement est consommé. L’exemple de code suivant illustre la classe 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);
        }
    }
}

Les méthodes OnAttachedTo et OnDetachingFrom permettent d’inscrire et d’annuler l’inscription d’un gestionnaire d’événements pour l’événement défini dans la propriété EventName. Puis, quand l’événement se déclenche, la méthode OnTriggerHandled est appelée, ce qui entraîne l’exécution de la commande.

L’utilisation de EventToCommandBehavior pour exécuter une commande quand un événement se déclenche présente l’avantage suivant : les commandes peuvent être associées à des contrôles qui n’ont pas été conçus pour interagir avec les commandes. De plus, cela permet de déplacer le code de gestion des événements vers les modèles de vue, où il peut faire l’objet d’un test unitaire.

Appel de comportements à partir d’une vue

Le EventToCommandBehavior est particulièrement utile pour attacher une commande à un contrôle qui ne prend pas en charge les commandes. Par exemple, LoginView utilise EventToCommandBehavior pour exécuter ValidateCommand quand l’utilisateur change la valeur de son mot de passe, comme le montre le code suivant :

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

Au moment de l’exécution, le EventToCommandBehavior répond à l’interaction avec le Entry. Quand un utilisateur tape dans le champ Entry, l’événement TextChanged se déclenche, ce qui entraîne l’exécution de ValidateCommand dans LoginViewModel. Par défaut, les arguments d’événement de l’événement sont passés à la commande. Si nécessaire, la propriété EventArgsConverter peut être utilisée pour convertir le EventArgs fourni par l’événement en une valeur attendue par la commande en tant qu’entrée.

Pour plus d’informations sur les comportements, consultez Comportements dans le Centre des développeurs .NET MAUI.

Résumé

Le modèle MVVM (modèle-vue-vue modèle) permet de séparer de manière nette la logique métier et la logique de présentation d’une application de son interface utilisateur (IU). Le maintien d’une séparation nette entre la logique d’application et l’IU permet de résoudre de nombreux problèmes de développement et de faciliter le test, la maintenance ainsi que l’évolution d’une application. Il permet également d’améliorer considérablement les opportunités de réutilisation du code. Ainsi, les développeurs et les concepteurs d’IU peuvent collaborer plus facilement quand ils développent leurs parties respectives d’une application.

À l’aide du modèle MVVM, l’IU de l’application ainsi que la logique métier et la présentation sous-jacentes sont séparées en trois classes distinctes : la vue, qui encapsule l’IU et la logique d’IU, le modèle de vue, qui encapsule la logique et l’état de présentation, et le modèle, qui encapsule la logique métier et les données de l’application.