Les convertisseurs de valeur sont-ils plus problématiques qu'ils n'en valent?

20

Je travaille sur une application WPF avec des vues qui nécessitent de nombreuses conversions de valeur. Au départ, ma philosophie (inspirée en partie par ce débat animé sur les Disciples XAML ) était que je devais faire le modèle de vue strictement pour soutenir les exigences de données de la vue. Cela signifiait que toutes les conversions de valeur nécessaires pour transformer les données en éléments tels que la visibilité, les pinceaux, les tailles, etc. seraient traitées avec des convertisseurs de valeur et des convertisseurs à valeurs multiples. Conceptuellement, cela semblait assez élégant. Le modèle de vue et la vue auraient tous deux un objectif distinct et seraient bien découplés. Une ligne claire serait tracée entre "données" et "apparence".

Eh bien, après avoir donné à cette stratégie "l'ancien essai du collège", j'ai des doutes si je veux continuer à développer de cette façon. En fait, j'envisage sérieusement de vider les convertisseurs de valeur et de placer la responsabilité de (presque) toute la conversion de valeur carrément entre les mains du modèle de vue.

La réalité de l'utilisation de convertisseurs de valeur ne semble tout simplement pas être à la hauteur de la valeur apparente de problèmes clairement séparés. Mon plus gros problème avec les convertisseurs de valeur est qu'ils sont fastidieux à utiliser. Vous devez créer une nouvelle classe, implémenter IValueConverterou IMultiValueConverter, convertir la valeur ou les valeurs du objecttype correct, tester DependencyProperty.Unset(au moins pour les convertisseurs à valeurs multiples), écrire la logique de conversion, enregistrer le convertisseur dans un dictionnaire de ressources [voir la mise à jour ci-dessous ], et enfin, branchez le convertisseur en utilisant du XAML assez verbeux (qui nécessite l'utilisation de chaînes magiques pour les liaisons et le nom du convertisseur)[voir la mise à jour ci-dessous]). Le processus de débogage n'est pas non plus un pique-nique, car les messages d'erreur sont souvent cryptiques, en particulier dans le mode de conception de Visual Studio / Expression Blend.

Cela ne veut pas dire que l'alternative - rendre le modèle de vue responsable de toute conversion de valeur - est une amélioration. Cela pourrait très bien être dû au fait que l'herbe est plus verte de l'autre côté. En plus de perdre l'élégante séparation des préoccupations, vous devez écrire un tas de propriétés dérivées et vous assurer d'appeler consciencieusement RaisePropertyChanged(() => DerivedProperty)lors de la définition des propriétés de base, ce qui pourrait s'avérer être un problème de maintenance désagréable.

Voici une liste initiale que j'ai dressée des avantages et des inconvénients de permettre aux modèles de vue de gérer la logique de conversion et de supprimer les convertisseurs de valeur:

  • Avantages:
    • Moins de liaisons totales depuis l'élimination des multi-convertisseurs
    • Moins de chaînes magiques (chemins de liaison + noms des ressources du convertisseur )
    • Plus besoin d'enregistrer chaque convertisseur (plus de maintenir cette liste)
    • Moins de travail pour écrire chaque convertisseur (pas d'interface d'implémentation ni de casting requis)
    • Peut facilement injecter des dépendances pour faciliter les conversions (par exemple, des tables de couleurs)
    • Le balisage XAML est moins détaillé et plus facile à lire
    • La réutilisation du convertisseur est toujours possible (bien qu'une certaine planification soit nécessaire)
    • Aucun problème mystérieux avec DependencyProperty.Unset (un problème que j'ai remarqué avec les convertisseurs à valeurs multiples)

* Les barrés indiquent les avantages qui disparaissent si vous utilisez des extensions de balisage (voir la mise à jour ci-dessous)

  • Les inconvénients:
    • Couplage plus fort entre le modèle de vue et la vue (par exemple, les propriétés doivent traiter de concepts comme la visibilité et les pinceaux)
    • Plus de propriétés totales pour permettre un mappage direct pour chaque liaison en vue
    • RaisePropertyChangeddoit être appelé pour chaque propriété dérivée (voir la mise à jour 2 ci-dessous)
    • Doit toujours s'appuyer sur des convertisseurs si la conversion est basée sur une propriété d'un élément d'interface utilisateur

Donc, comme vous pouvez probablement le constater, j'ai des brûlures d'estomac à ce sujet. J'hésite beaucoup à emprunter la voie de la refactorisation pour me rendre compte que le processus de codage est tout aussi inefficace et fastidieux, que j'utilise des convertisseurs de valeur ou que j'expose de nombreuses propriétés de conversion de valeur dans mon modèle de vue.

Suis-je en train de manquer des avantages / inconvénients? Pour ceux qui ont essayé les deux moyens de conversion de valeur, lequel avez-vous trouvé le mieux pour vous et pourquoi? Y a-t-il d'autres alternatives? (Les disciples ont mentionné quelque chose à propos des fournisseurs de descripteurs de type, mais je n'ai pas pu comprendre de quoi ils parlaient. Tout renseignement à ce sujet serait apprécié.)


Mise à jour

J'ai découvert aujourd'hui qu'il était possible d'utiliser quelque chose appelé une "extension de balisage" pour éliminer la nécessité d'enregistrer des convertisseurs de valeur. En fait, il élimine non seulement la nécessité de les enregistrer, mais il fournit en fait intellisense pour sélectionner un convertisseur lorsque vous tapez Converter=. Voici l'article qui m'a lancé: http://www.wpftutorial.net/ValueConverters.html .

La possibilité d'utiliser une extension de balisage modifie quelque peu l'équilibre dans ma liste d'avantages et d'inconvénients et la discussion ci-dessus (voir les barrés).

À la suite de cette révélation, j'expérimente avec un système hybride où j'utilise des convertisseurs BoolToVisibilityet ce que j'appelle MatchToVisibilityet le modèle de vue pour toutes les autres conversions. MatchToVisibility est essentiellement un convertisseur qui me permet de vérifier si la valeur liée (généralement une énumération) correspond à une ou plusieurs valeurs spécifiées dans XAML.

Exemple:

Visibility="{Binding Status, Converter={vc:MatchToVisibility
            IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"

Fondamentalement, cela vérifie si l'état est Terminé ou Annulé. Si c'est le cas, la visibilité est définie sur "Visible". Sinon, il prend la valeur "Hidden". Cela s'est avéré être un scénario très courant, et avoir ce convertisseur m'a sauvé environ 15 propriétés sur mon modèle de vue (plus les instructions RaisePropertyChanged associées). Notez que lorsque vous tapez Converter={vc:, "MatchToVisibility" apparaît dans un menu intellisense. Cela réduit considérablement le risque d'erreurs et rend l'utilisation des convertisseurs de valeur moins fastidieuse (vous n'avez pas besoin de vous rappeler ou de rechercher le nom du convertisseur de valeur que vous souhaitez).

Si vous êtes curieux, je vais coller le code ci-dessous. Une caractéristique importante de cette mise en œuvre MatchToVisibilityest qu'il vérifie si la valeur limite est enum, et si elle est, il vérifie que Value1, Value2etc. sont également énumérations du même type. Cela permet de vérifier au moment de la conception et de l'exécution si des valeurs d'énumération ont été mal typées. Pour améliorer cela à une vérification au moment de la compilation, vous pouvez utiliser ce qui suit à la place (j'ai tapé ceci à la main alors veuillez me pardonner si j'ai fait des erreurs):

Visibility="{Binding Status, Converter={vc:MatchToVisibility
            IfTrue={x:Type {win:Visibility.Visible}},
            IfFalse={x:Type {win:Visibility.Hidden}},
            Value1={x:Type {enum:Status.Finished}},
            Value2={x:Type {enum:Status.Canceled}}"

Bien que ce soit plus sûr, il est tout simplement trop verbeux pour en valoir la peine pour moi. Je pourrais aussi bien utiliser une propriété sur le modèle de vue si je veux le faire. Quoi qu'il en soit, je trouve que la vérification au moment de la conception est parfaitement adaptée aux scénarios que j'ai essayés jusqu'à présent.

Voici le code pour MatchToVisibility

[ValueConversion(typeof(object), typeof(Visibility))]
public class MatchToVisibility : BaseValueConverter
{
    [ConstructorArgument("ifTrue")]
    public object IfTrue { get; set; }

    [ConstructorArgument("ifFalse")]
    public object IfFalse { get; set; }

    [ConstructorArgument("value1")]
    public object Value1 { get; set; }

    [ConstructorArgument("value2")]
    public object Value2 { get; set; }

    [ConstructorArgument("value3")]
    public object Value3 { get; set; }

    [ConstructorArgument("value4")]
    public object Value4 { get; set; }

    [ConstructorArgument("value5")]
    public object Value5 { get; set; }

    public MatchToVisibility() { }

    public MatchToVisibility(
        object ifTrue, object ifFalse,
        object value1, object value2 = null, object value3 = null,
        object value4 = null, object value5 = null)
    {
        IfTrue = ifTrue;
        IfFalse = ifFalse;
        Value1 = value1;
        Value2 = value2;
        Value3 = value3;
        Value4 = value4;
        Value5 = value5;
    }

    public override object Convert(
        object value, Type targetType, object parameter, CultureInfo culture)
    {
        var ifTrue = IfTrue.ToString().ToEnum<Visibility>();
        var ifFalse = IfFalse.ToString().ToEnum<Visibility>();
        var values = new[] { Value1, Value2, Value3, Value4, Value5 };
        var valueStrings = values.Cast<string>();
        bool isMatch;
        if (Enum.IsDefined(value.GetType(), value))
        {
            var valueEnums = valueStrings.Select(vs => vs == null ? null : Enum.Parse(value.GetType(), vs));
            isMatch = valueEnums.ToList().Contains(value);
        }
        else
            isMatch = valueStrings.Contains(value.ToString());
        return isMatch ? ifTrue : ifFalse;
    }
}

Voici le code pour BaseValueConverter

// this is how the markup extension capability gets wired up
public abstract class BaseValueConverter : MarkupExtension, IValueConverter
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public abstract object Convert(
        object value, Type targetType, object parameter, CultureInfo culture);

    public virtual object ConvertBack(
        object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Voici la méthode d'extension ToEnum

public static TEnum ToEnum<TEnum>(this string text)
{
    return (TEnum)Enum.Parse(typeof(TEnum), text);
}

Update 2

Depuis que j'ai posté cette question, je suis tombé sur un projet open source qui utilise le "tissage IL" pour injecter du code NotifyPropertyChanged pour les propriétés et les propriétés dépendantes. Cela rend la mise en œuvre de la vision de Josh Smith du modèle de vue comme un «convertisseur de valeur sur les stéroïdes» un jeu d'enfant absolu. Vous pouvez simplement utiliser les "Propriétés implémentées automatiquement" et le tisserand fera le reste.

Exemple:

Si j'entre ce code:

public string GivenName { get; set; }
public string FamilyName { get; set; }

public string FullName
{
    get
    {
        return string.Format("{0} {1}", GivenName, FamilyName);
    }
}

... c'est ce qui se compile:

string givenNames;
public string GivenNames
{
    get { return givenName; }
    set
    {
        if (value != givenName)
        {
            givenNames = value;
            OnPropertyChanged("GivenName");
            OnPropertyChanged("FullName");
        }
    }
}

string familyName;
public string FamilyName
{
    get { return familyName; }
    set 
    {
        if (value != familyName)
        {
            familyName = value;
            OnPropertyChanged("FamilyName");
            OnPropertyChanged("FullName");
        }
    }
}

public string FullName
{
    get
    {
        return string.Format("{0} {1}", GivenName, FamilyName);
    }
}

C'est une énorme économie dans la quantité de code que vous devez taper, lire, faire défiler, etc. Plus important encore, cependant, cela vous évite d'avoir à comprendre quelles sont vos dépendances. Vous pouvez ajouter de nouvelles «propriétés» FullNamesans avoir à remonter minutieusement la chaîne de dépendances pour ajouter des RaisePropertyChanged()appels.

Comment s'appelle ce projet open-source? La version originale s'appelle "NotifyPropertyWeaver", mais le propriétaire (Simon Potter) a depuis créé une plate-forme appelée "Fody" pour héberger toute une série de tisserands IL. L'équivalent de NotifyPropertyWeaver sous cette nouvelle plate-forme s'appelle PropertyChanged.Fody.

Si vous préférez utiliser NotifyPropertyWeaver (qui est un peu plus simple à installer, mais ne sera pas nécessairement mis à jour à l'avenir au-delà des corrections de bugs), voici le site du projet: http://code.google.com/p/ informerpropertyweaver /

Quoi qu'il en soit, ces solutions IL Weaver changent complètement le calcul dans le débat entre le modèle de vue sur les stéroïdes et les convertisseurs de valeur.

devuxer
la source
Juste une note: BooleanToVisibilityprend une valeur liée à la visibilité (vrai / faux) et la traduit en une autre. Cela semble être une utilisation idéale de a ValueConverter. D'un autre côté, MatchToVisibilityest l'encodage de la logique métier dans View(quels types d'éléments doivent être visibles). À mon avis, cette logique devrait être poussée vers le bas ViewModel, voire plus loin dans ce que j'appelle le EditModel. Ce que l'utilisateur peut voir devrait être quelque chose à tester.
Scott Whitlock,
@Scott, c'est un bon point. L'application sur laquelle je travaille en ce moment n'est pas vraiment une application «professionnelle», où il existe différents niveaux d'autorisation pour les utilisateurs, donc je ne pensais pas dans ce sens. MatchToVisibilitysemblait être un moyen pratique d'activer certains commutateurs de mode simples (j'ai une vue en particulier avec une tonne de pièces qui peuvent être activées et désactivées. Dans la plupart des cas, des sections de la vue sont même étiquetées (avec x:Name) pour correspondre au mode ils correspondent.) Il ne m'est pas vraiment venu à l'esprit qu'il s'agit de «logique commerciale», mais je vais réfléchir à votre commentaire.
devuxer
Exemple: supposons que vous disposiez d'un système stéréo pouvant être en mode radio, CD ou MP3. Supposons qu'il existe des visuels correspondant à chaque mode dans différentes parties de l'interface utilisateur. Vous pouvez soit (1) laisser la vue décider quels graphiques correspondent à quel mode et les activer / désactiver en conséquence, (2) exposer les propriétés sur le modèle de vue pour chaque valeur de mode (par exemple, IsModeRadio, IsModeCD), ou (3) exposer propriétés sur le modèle de vue pour chaque élément / groupe graphique (par exemple, IsRadioLightOn, IsCDButtonGroupOn). (1) me semblait tout à fait normal, car il avait déjà une conscience de mode. Que pensez-vous dans ce cas?
devuxer
C'est la question la plus longue que j'aie jamais vue dans toute la SE! :]
trejder

Réponses:

10

J'ai utilisé ValueConvertersdans certains cas et mis la logique dans ViewModeld'autres. Mon sentiment est que a ValueConverterdevient une partie du Viewcalque, donc si la logique fait vraiment partie du Viewalors mettez-le là, sinon mettez-le dans le ViewModel.

Personnellement, je ne vois pas de problème avec un ViewModeltraitement avec des Viewconcepts spécifiques comme Brushes car dans mes applications, il ViewModeln'existe qu'une surface testable et liable pour le View. Cependant, certaines personnes mettent beaucoup de logique métier dans ViewModel(je ne le fais pas) et dans ce cas, cela ViewModelressemble plus à une partie de leur couche métier, donc dans ce cas, je ne voudrais pas de choses spécifiques à WPF.

Je préfère une séparation différente:

  • View- Des trucs WPF, parfois non testables (comme XAML et code-behind) mais aussi ValueConverters
  • ViewModel - classe testable et liable qui est également spécifique à WPF
  • EditModel - partie de la couche métier qui représente mon modèle lors de la manipulation
  • EntityModel - une partie de la couche métier qui représente mon modèle comme persistant
  • Repository- responsable de la persistance de la EntityModelà la base de données

Donc, la façon dont je le fais, j'ai peu d'utilité pour ValueConverters

La façon dont je me suis éloigné de certains de vos "Con" est de rendre les miens ViewModeltrès génériques. Par exemple, ViewModelj'ai un, appelé ChangeValueViewModelimplémente une propriété Label et une propriété Value. Sur le Viewil y a un Labelqui se lie à la propriété Label et un TextBoxqui se lie à la propriété Value.

J'ai ensuite un ChangeValueViewqui est un DataTemplateclavier du ChangeValueViewModeltype. Chaque fois que WPF voit ViewModelqu'il l'applique View. Le constructeur de my ChangeValueViewModelprend la logique d'interaction dont il a besoin pour rafraîchir son état à partir de EditModel(généralement en passant simplement a Func<string>) et l'action qu'il doit entreprendre lorsque l'utilisateur modifie la valeur (juste une Actionqui exécute une logique dans la EditModel).

Le parent ViewModel(pour l'écran) prend un EditModeldans son constructeur et instancie simplement les éléments élémentaires appropriés ViewModeltels que ChangeValueViewModel. Étant donné que le parent ViewModelinjecte l'action à effectuer lorsque l'utilisateur effectue une modification, il peut intercepter toutes ces actions et effectuer d'autres actions. Par conséquent, l'action de modification injectée ChangeValueViewModelpourrait ressembler à:

(string newValue) =>
{
    editModel.SomeField = newValue;
    foreach(var childViewModel in this.childViewModels)
    {
        childViewModel.RefreshStateFromEditModel();
    }
}

Évidemment, la foreachboucle peut être refactorisée ailleurs, mais ce que cela fait, c'est prendre l'action, l'appliquer au modèle, puis (en supposant que le modèle a mis à jour son état d'une manière inconnue), dit à tous les enfants ViewModeld'aller et d'obtenir leur état à partir de le modèle à nouveau. Si l'État a changé, ils sont responsables de l'exécution de leurs PropertyChangedévénements, si nécessaire.

Cela gère très bien l'interaction entre, disons, une zone de liste et un panneau de détails. Lorsque l'utilisateur sélectionne un nouveau choix, il met à jour le EditModelavec le choix et EditModelmodifie les valeurs des propriétés exposées pour le panneau de détail. Les ViewModelenfants chargés d'afficher les informations du panneau de détail sont automatiquement informés qu'ils doivent vérifier les nouvelles valeurs et, s'ils ont changé, ils déclenchent leurs PropertyChangedévénements.

Scott Whitlock
la source
/hochement. C'est assez semblable à la mienne.
Ian
+1. Merci pour votre réponse, Scott, j'ai à peu près les mêmes couches que vous, et je ne mets pas non plus de logique métier dans le modèle de vue. (Pour mémoire, j'utilise d'abord le code EntityFramework et j'ai une couche de service qui se traduit entre les modèles de vue et les modèles d'entité, et vice versa.) Donc, étant donné cela, je suppose que vous dites qu'il n'y a pas beaucoup de coûts pour placer la totalité / la majeure partie de la logique de conversion dans la couche du modèle de vue.
devuxer
@DanM - Oui, je suis d'accord. Je préférerais la conversion dans le ViewModelcalque. Tout le monde n'est pas d'accord avec moi, mais cela dépend du fonctionnement de votre architecture.
Scott Whitlock
2
J'allais dire +1 après avoir lu le premier paragraphe, mais ensuite j'ai lu le deuxième et je suis fortement en désaccord avec le fait de mettre du code spécifique à la vue dans les ViewModels. La seule exception est si le ViewModel est créé spécifiquement pour aller derrière une vue générique (comme un CalendarViewModelpour un CalendarViewUserControl ou unDialogViewModel pour un DialogView). C'est juste mon opinion cependant :)
Rachel
@Rachel - eh bien, si vous aviez continué à lire après mon deuxième paragraphe, vous verriez que c'est exactement ce que je faisais. :) Il n'y a pas de logique commerciale dans mon ViewModelart.
Scott Whitlock
8

Si la conversion est liée à la vue, comme décider de la visibilité d'un objet, déterminer l'image à afficher ou déterminer la couleur de pinceau à utiliser, je place toujours mes convertisseurs dans la vue.

Si cela est lié à l'entreprise, comme déterminer si un champ doit être masqué ou si un utilisateur est autorisé à effectuer une action, la conversion se produit dans mon ViewModel.

A partir de vos exemples, je pense que vous manque un gros morceau de WPF: DataTriggers. Vous semblez utiliser des convertisseurs pour déterminer des valeurs conditionnelles, mais les convertisseurs devraient vraiment être destinés à convertir un type de données en un autre.

Dans votre exemple ci-dessus

Exemple: supposons que vous disposiez d'un système stéréo pouvant être en mode radio, CD ou MP3. Supposons qu'il existe des visuels correspondant à chaque mode dans différentes parties de l'interface utilisateur. Vous pouvez soit (1) laisser la vue décider quels graphiques correspondent à quel mode et les activer / désactiver en conséquence, (2) exposer les propriétés sur le modèle de vue pour chaque valeur de mode (par exemple, IsModeRadio, IsModeCD), ou (3) exposer propriétés sur le modèle de vue pour chaque élément / groupe graphique (par exemple, IsRadioLightOn, IsCDButtonGroupOn). (1) me semblait tout à fait normal, car il avait déjà une conscience du mode. Que pensez-vous dans ce cas?

J'utiliserais un DataTriggerpour déterminer quelle image afficher, pas un Converter. Un convertisseur sert à convertir un type de données en un autre, tandis qu'un déclencheur est utilisé pour déterminer certaines propriétés en fonction d'une valeur.

<Style x:Key="RadioImageStyle">
    <Setter Property="Source" Value="{StaticResource RadioImage}" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding Mode}" Value="CD">
            <Setter Property="Source" Value="{StaticResource CDImage}" />
        </DataTrigger>
        <DataTrigger Binding="{Binding Mode}" Value="MP3">
            <Setter Property="Source" Value="{StaticResource MP3Image}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

La seule fois où j'envisagerais d'utiliser un convertisseur pour cela, c'est si la valeur liée contenait réellement les données d'image, et j'avais besoin de la convertir en un type de données que l'interface utilisateur pouvait comprendre. Par exemple, si la source de données contenait une propriété appelée ImageFilePath, j'envisagerais d'utiliser un convertisseur pour convertir la chaîne contenant l'emplacement du fichier image en une BitmapImagequi pourrait être utilisée comme source pour mon image.

<Style x:Key="RadioImageStyle">
    <Setter Property="Source" Value="{Binding ImageFilePath, 
            Converter={StaticResource StringPathToBitmapConverter}}" />
</Style>

Le résultat final est que j'ai un espace de noms de bibliothèque plein de convertisseurs génériques qui convertissent un type de données en un autre, et j'ai rarement à coder un nouveau convertisseur. Il y a des occasions où je voudrai des convertisseurs pour des conversions spécifiques, mais ils sont assez rares pour que cela ne me dérange pas de les écrire.

Rachel
la source
+1. Vous soulevez de bons points. J'ai déjà utilisé des déclencheurs, mais dans mon cas, je ne désactive pas les sources d'images (qui sont une propriété), je désactive des Gridéléments entiers . J'essaie également de faire des choses comme définir des pinceaux pour le premier plan / l'arrière-plan / le contour en fonction des données de mon modèle de vue et d'une palette de couleurs spécifique définie dans le fichier de configuration. Je ne suis pas sûr que ce soit un bon choix pour un déclencheur ou un convertisseur. Le seul problème que j'ai jusqu'à présent avec la plupart des logiques de vue dans le modèle de vue est de câbler tous les RaisePropertyChanged()appels.
devuxer
@DanM Je ferais en fait toutes ces choses dans un DataTrigger, même en supprimant les éléments de Grid. Habituellement, je place un endroit ContentControloù mon contenu dynamique devrait être et j'échange le ContentTemplatedans un déclencheur. J'ai un exemple sur le lien suivant si vous êtes intéressé (faites défiler jusqu'à la section avec l'en-tête de Using a DataTrigger) rachel53461.wordpress.com/2011/05/28/…
Rachel
J'ai déjà utilisé des modèles de données et des contrôles de contenu, mais je n'ai jamais eu besoin de déclencheurs car j'ai toujours eu un modèle de vue unique pour chaque vue. Quoi qu'il en soit, votre technique est parfaitement logique et assez élégante, mais elle est également très verbeuse. Avec MatchToVisibility, cela pourrait être raccourci à ceci: <TextBlock Text="I'm a Person" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Person}}"et<TextBlock Text="I'm a Business" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Business}}"
devuxer
1

Ça dépend de ce que vous testez , le cas échéant.

Pas de tests: mélanger le code View w / ViewModel à volonté (vous pouvez toujours refactoriser plus tard).
Tests sur ViewModel et / ou inférieur: utilisez des convertisseurs.
Tests sur les couches Model et / ou inférieures: mélanger Voir le code avec ViewModel à volonté

ViewModel résume le modèle de la vue . Personnellement, j'utiliserais ViewModel pour les pinceaux, etc. et je sauterais les convertisseurs. Testez la ou les couches où les données sont dans leur forme "la plus pure " (c'est-à-dire les couches modèles ).

Jake Berger
la source
2
Des points intéressants sur les tests, mais je suppose que je ne vois pas comment la logique du convertisseur dans le modèle de vue nuit à la testabilité du modèle de vue? Je ne suggère certainement pas de placer des contrôles d' interface utilisateur réels dans le modèle de vue. Il suffit de voir spécifiques des propriétés telles que Visibility, SolidColorBrushet Thickness.
devuxer
@DanM: Si vous utilisez une approche View-first , alors pas de problème . Cependant, certains utilisent une approche ViewModel-first où le ViewModel référence une vue, cela peut être problématique .
Jake Berger
Salut Jay, sans aucun doute une approche axée sur la vue. La vue ne sait rien du modèle de vue, à l'exception des noms des propriétés auxquelles elle doit se lier. Merci d'avoir suivi. +1.
devuxer
0

Cela ne résoudra probablement pas tous les problèmes que vous avez mentionnés, mais il y a deux points à considérer:

Tout d'abord, vous devez placer le code du convertisseur quelque part dans votre première stratégie. Considérez-vous cette partie de la vue ou du modèle de vue? Si cela fait partie de la vue, pourquoi ne pas placer les propriétés spécifiques à la vue dans la vue au lieu du modèle de vue?

Deuxièmement, il semble que votre conception sans convertisseur tente de modifier les propriétés réelles des objets qui existent déjà. Il semble qu'ils implémentent déjà INotifyPropertyChanged, alors pourquoi ne pas créer un objet wrapper spécifique à la vue auquel se lier? Voici un exemple simple:

public class RealData
{
    private bool mIsInteresting;
    public bool IsInteresting
    {
        get { return mIsInteresting; }
        set 
        {
            if (mIsInteresting != null) 
            {
                mIsInteresting = value;
                RaisePropertyChanged("IsInteresting");
            }
        }
    }
}

public class RealDataView
{
    private RealData mRealData;

    public RealDataView(RealData data)
    {
        mRealData = data;
        mRealData.PropertyChanged += OnRealDataPropertyChanged;
    }

    public Visibility IsVisiblyInteresting
    {
       get { return mRealData.IsInteresting ? Visibility.Visible : Visibility.Hidden; }
       set { mRealData.IsInteresting = (value == Visibility.Visible); }
    }

    private void OnRealDataPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "IsInteresting") 
        {
            RaisePropertyChanged(this, "IsVisiblyInteresting");
        }
    }
}
John Fisher
la source
Je ne voulais pas impliquer que je modifie les propriétés de mon modèle d'entité directement dans la vue ou le modèle de vue. Le modèle de vue est définitivement un calque différent de mon calque de modèle d'entité. En fait, le travail que j'ai fait jusqu'à présent était sur des vues en lecture seule. Cela ne veut pas dire que mon application n'impliquera aucune modification, mais je ne vois aucune conversion en cours sur les contrôles utilisés pour la modification (supposez donc que toutes les liaisons sont à sens unique, à l'exception des sélections dans les listes). Bon point sur les "vues de données". C'était un concept soulevé dans le post des disciples XAML auquel j'ai fait référence en haut de ma question.
devuxer
0

Parfois, il est bon d'utiliser un convertisseur de valeur pour profiter de la virtualisation.

Un exemple de cela dans un projet où nous devions afficher des données masquées par bit pour des centaines de milliers de cellules dans une grille. Lorsque nous avons décodé les masques de bits dans le modèle de vue pour chaque cellule, le programme a pris beaucoup trop de temps à charger.

Mais lorsque nous avons créé un convertisseur de valeur qui a décodé une seule cellule, le programme a été chargé en une fraction du temps et était tout aussi réactif car le convertisseur n'est appelé que lorsque l'utilisateur regarde une cellule particulière (et il ne devrait être appelé un maximum de trente fois à chaque fois que l'utilisateur change son point de vue sur la grille).

Je ne sais pas comment MVVM se plaignait de cette solution, mais elle a réduit le temps de chargement de 95%.

kleineg
la source