Modèle Model-View-ViewModel

Notes

Ce livre électronique a été publié au printemps 2017 et n’a pas été mis à jour depuis lors. Il y a beaucoup dans le livre qui reste précieux, mais une partie du matériel est obsolète.

L’expérience Xamarin.Forms du développeur implique généralement la création d’une interface utilisateur en XAML, puis l’ajout de code-behind qui fonctionne sur l’interface utilisateur. À mesure que les applications sont modifiées et augmentent en taille et en étendue, des problèmes de maintenance complexes peuvent survenir. 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 Model-View-ViewModel (MVVM) permet de séparer proprement la logique métier et de présentation d’une application de son interface utilisateur. Le maintien d’une séparation propre entre la logique d’application et l’interface utilisateur permet de résoudre de nombreux problèmes de développement et peut faciliter le test, la maintenance et l’évolution d’une application. Il peut également améliorer considérablement les opportunités de réutilisation du code et permet aux développeurs et aux concepteurs d’interface utilisateur de collaborer plus facilement lors du développement de 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. La figure 2-1 montre les relations entre les trois composants.

Le modèle MVVM

Figure 2-1 : Modèle MVVM

En plus de comprendre les responsabilités de chaque composant, il est également important de comprendre comment ils interagissent les uns avec les autres. À 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 :

  • S’il existe une implémentation de modèle existante qui encapsule une logique métier existante, il peut être difficile ou risqué de la modifier. Dans ce scénario, le modèle d’affichage joue le rôle d’adaptateur pour les classes de modèle et vous permet d’éviter d’apporter des modifications majeures 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’interface utilisateur de l’application peut être repensée sans toucher au code, à condition que la vue soit implémentée entièrement en XAML. 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 pendant le processus de 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.

La clé de l’utilisation efficace de MVVM consiste à comprendre comment factoriser le code d’application dans les classes correctes et à comprendre comment les 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 Xamarin.Forms application, une vue est généralement une Pageclasse dérivée ou ContentViewdérivée. 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. Assurez-vous que les modèles d’affichage sont responsables de la définition des modifications d’état logique qui affectent certains aspects de l’affichage, par exemple si une commande est disponible ou une indication qu’une opération est 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é du Command contrôle peut être liée aux données d’une ICommand propriété sur le modèle d’affichage. 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 et peuvent écouter une commande à appeler ou un événement déclenché. En réponse, le comportement peut ensuite appeler un ICommand sur le modèle d’affichage ou une méthode sur le modèle d’affichage.

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 mobiles doivent garder le thread d’interface utilisateur 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 d’affichage peut combiner les valeurs de deux propriétés pour faciliter son affichage.

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 d’affichage à des vues

Les modèles d’affichage peuvent être connectés à des vues à l’aide des fonctionnalités de liaison de données de Xamarin.Forms. 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 première construction de la vue s’aligne sur le système de navigation responsable de la Xamarin.Forms construction des pages lorsque la navigation se produit, ce qui rend un modèle de première composition complexe et mal aligné avec la plateforme.

Avec la première composition du modèle d’affichage, l’application est composée conceptuellement de modèles d’affichage, un service étant responsable de la localisation de la vue pour un modèle d’affichage. 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 devenir 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 les types d’affichage, tels que Button et ListView, à partir de modèles d’affichage. 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 d’affichage 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>  
    ...  
</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 d’affichage par programmation

Une vue peut avoir du code dans le fichier code-behind qui entraîne l’affectation du modèle d’affichage à sa BindingContext propriété. 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.

Création d’une vue définie en tant que modèle de données

Une vue peut être définie en tant que modèle de données et associée à un type de modèle de vue. Les modèles de données peuvent être définis en tant que ressources, ou ils peuvent être définis inline dans le contrôle qui affiche le modèle d’affichage. Le contenu du contrôle est le modèle d’affichage instance, et le modèle de données est utilisé pour le représenter visuellement. Cette technique est un exemple de situation dans laquelle le modèle d’affichage est instancié en premier, suivi de la création de la vue.

Création automatique d’un modèle d’affichage avec un localisateur de modèle d’affichage

Un localisateur de modèle d’affichage est une classe personnalisée qui gère l’instanciation des modèles d’affichage et leur association aux vues. Dans l’application mobile eShopOnContainers, la ViewModelLocator classe a une propriété jointe, AutoWireViewModel, qui est utilisée pour associer des modèles d’affichage à des vues. Dans le code XAML de la vue, cette propriété jointe a la valeur true pour indiquer que le modèle d’affichage doit être automatiquement connecté à la vue, comme illustré dans l’exemple de code suivant :

viewModelBase:ViewModelLocator.AutoWireViewModel="true"

La AutoWireViewModel propriété est une propriété pouvant être liée qui est initialisée sur false, et lorsque sa valeur change, le OnAutoWireViewModelChanged gestionnaire d’événements est appelé. Cette méthode résout le modèle d’affichage de l’affichage. L’exemple de code suivant montre comment procéder :

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

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

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

La OnAutoWireViewModelChanged méthode tente de résoudre le modèle d’affichage à l’aide d’une approche basée sur des conventions. Cette convention part du principe que :

  • Les modèles d’affichage se trouvent dans le même assembly que les types d’affichage.
  • Les vues se trouvent dans un . Affiche l’espace de noms enfant.
  • Les modèles d’affichage se trouvent dans un . Espace de noms enfant ViewModels.
  • Les noms des modèles d’affichage correspondent aux noms d’affichage et se terminent par « ViewModel ».

Enfin, la OnAutoWireViewModelChanged méthode définit le BindingContext du type d’affichage sur le type de modèle d’affichage résolu. Pour plus d’informations sur la résolution du type de modèle d’affichage, consultez Résolution.

Cette approche présente l’avantage qu’une application a une seule classe responsable de l’instanciation des modèles d’affichage et de leur connexion aux vues.

Conseil

Utilisez un localisateur de modèle d’affichage pour faciliter la substitution. Un localisateur de modèle de vue peut également être utilisé comme point de substitution pour d’autres implémentations de dépendances, telles que pour les données de test unitaire ou de conception.

Mise à jour des vues en réponse aux modifications apportées au modèle ou au modèle d’affichage sous-jacent

Toutes les classes de modèle et de modèle d’affichage accessibles à une vue doivent implémenter 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.

Les applications doivent être conçues pour l’utilisation correcte de la notification de modification de propriété, en répondant aux exigences suivantes :

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

L’application mobile eShopOnContainers utilise la ExtendedBindableObject classe pour fournir des notifications de modification, comme illustré dans 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)  
    {  
        ...  
    }  
}

La classe de BindableObject Xamarin.Form implémente l’interface INotifyPropertyChanged et fournit une OnPropertyChanged méthode. 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.

Chaque classe de modèle de vue dans l’application mobile eShopOnContainers dérive de la ViewModelBase classe , qui à son tour dérive de la ExtendedBindableObject classe . 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 mobile eShopOnContainers appelle une notification de modification de propriété à l’aide d’une expression lambda :

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

Notez que l’utilisation d’une expression lambda de cette façon implique un faible coût de performances, car l’expression lambda doit être évaluée pour chaque appel. Bien que le coût des performances soit faible et n’ait normalement pas d’impact sur une application, les coûts peuvent s’accumuler lorsqu’il existe de nombreuses notifications de modification. 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.

Interaction de l’interface utilisateur à l’aide de commandes et de comportements

Dans les applications mobiles, les actions sont généralement appelées en réponse à une action de l’utilisateur, telle qu’un clic sur un bouton, qui peut être implémentée en créant 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. Ils encapsulent le code qui implémente l’action et aident à la dissocier de sa représentation visuelle dans la vue. Xamarin.Forms inclut des contrôles qui peuvent être connectés de manière déclarative à une commande, et ces contrôles appellent la commande lorsque 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 de commandes

Les modèles d’affichage exposent généralement des propriétés de commande, pour la liaison à partir de la vue, qui sont des instances d’objet qui implémentent l’interface ICommand . Un certain nombre de Xamarin.Forms contrôles fournissent une Command propriété, qui peut être liée à des données à un ICommand objet fourni par le modèle de vue. 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. Les Command classes et Command<T> , fournies par Xamarin.Forms, implémentent l’interface ICommand , où T est le type des arguments de Execute et CanExecute.

Dans un modèle de vue, il doit y avoir un objet de type Command ou Command<T> pour chaque propriété publique dans le modèle de vue de type ICommand. Le Command constructeur ou Command<T> nécessite un Action objet de rappel appelé lorsque la ICommand.Execute méthode est appelée. La CanExecute méthode est un paramètre de constructeur facultatif et est un Func qui retourne un bool.

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

public ICommand RegisterCommand => new Command(Register);

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 async mots clés et await lors de la spécification du délégué de Execute la commande. Cela indique que le rappel est un Task et qu’il doit être attendu. Par exemple, le code suivant montre comment un Command instance, qui représente une commande de connexion, est construit en spécifiant un délégué à la méthode de modèle d’affichage SignInAsync :

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

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

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

Dans les classes Command et Command<T>, le délégué de la méthode CanExecute dans chaque constructeur est facultatif. Si aucun délégué n’est spécifié, le 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 de l’interface utilisateur qui sont liés à la commande mettent ensuite à jour leur status activé 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 paramètre de commande, s’il est fourni, est passé en tant qu’argument au délégué de Execute 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. Les comportements vous permettent d’implémenter du code que vous devez normalement écrire en tant que code-behind, car il interagit directement avec l’API du contrôle, de telle sorte qu’il puisse être attaché de manière concise au contrôle et empaqueté 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. L’application mobile eShopOnContainers contient la LineColorBehavior classe, qui est un comportement attaché. Pour plus d’informations sur ce comportement, consultez Affichage des erreurs de validation.

Un Xamarin.Forms comportement est une classe qui dérive de la Behavior classe 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 mobile eShopOnContainers, la BindableBehavior<T> classe dérive de la Behavior<T> classe . L’objectif de la BindableBehavior<T> classe est de fournir une classe de base pour Xamarin.Forms les comportements qui nécessitent que le BindingContext du comportement soit défini sur le 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. De plus, la classe stocke une référence au contrôle attaché dans la propriété AssociatedObject.

L’application mobile eShopOnContainers comprend une EventToCommandBehavior classe, qui exécute une commande en réponse à un événement qui se produit. Cette classe dérive de la classe BindableBehavior<T> 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 :

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

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

            AddEventHandler(_eventInfo, AssociatedObject, OnFired);  
        }  
    }  

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

        base.OnDetachingFrom(view);  
    }  

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

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

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

Est EventToCommandBehavior particulièrement utile pour attacher une commande à un contrôle qui ne prend pas en charge les commandes. Par exemple, le ProfileView utilise EventToCommandBehavior pour exécuter le OrderDetailCommand lorsque l’événement ItemTapped se déclenche sur le ListView qui répertorie les commandes de l’utilisateur, comme indiqué dans le code suivant :

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

Au moment de l’exécution, le EventToCommandBehavior répond à l’interaction avec le ListView. Lorsqu’un élément est sélectionné dans , ListViewl’événement ItemTapped se déclenche, ce qui exécute le OrderDetailCommand dans le ProfileViewModel. Par défaut, les arguments d’événement de l’événement sont passés à la commande. Ces données sont converties au fur et à mesure qu’elles sont passées entre la source et la cible par le convertisseur spécifié dans la EventArgsConverter propriété, qui retourne le Item de à ListView partir de .ItemTappedEventArgs Par conséquent, lorsque le OrderDetailCommand est exécuté, le sélectionné Order est passé en tant que paramètre à l’action inscrite.

Pour plus d’informations sur les comportements, consultez Comportements.

Résumé

Le modèle Model-View-ViewModel (MVVM) permet de séparer proprement la logique métier et de présentation d’une application de son interface utilisateur. Le maintien d’une propre séparation entre la logique d’application et l’interface utilisateur permet de résoudre de nombreux problèmes de développement et peut faciliter le test, la maintenance et l’évolution d’une application. Il peut également améliorer considérablement les opportunités de réutilisation du code et permet aux développeurs et aux concepteurs d’interface utilisateur de collaborer plus facilement lors du développement de leurs parties respectives d’une application.

À l’aide du modèle MVVM, l’interface utilisateur de l’application et la présentation sous-jacente et la logique métier sont séparées en trois classes distinctes : la vue, qui encapsule l’interface utilisateur et la logique de l’interface utilisateur ; 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.