Question

Quand je veux lier un contrôle à une propriété de mon objet, je dois fournir le nom de la propriété en tant que chaîne. Ce n'est pas très bon parce que:

  1. Si la propriété est retirée ou renommé, je ne suis pas un compilateur Attention.
  2. Si renommer la propriété avec un outil de refactoring, il est probable que les données ne seront pas obligatoires mis à jour.
  3. Je ne suis pas une erreur jusqu'à ce que exécution si le type de la propriété est erroné, par exemple la liaison d'un nombre entier de une date chooser.

Y at-il un modèle de conception qui contourne, mais a encore la facilité d'utilisation de? Liaison de données

(Ceci est un problème dans WinForm, Asp.net et WPF et beaucoup plus probables d'autres systèmes)

Je l'ai maintenant trouvé « pour l'opérateur NomDe () en C # : typesafe databinding » qui a aussi un bon point de départ pour une solution

.

Si vous êtes prêt à utiliser un post-processeur après la compilation de votre code, notifypropertyweaver vaut bien la recherche à.


Tout le monde connaît une bonne solution pour WPF lorsque les liaisons sont faites en XML plutôt que C #?

Était-ce utile?

La solution

Merci à Oliver pour moi de commencer, j'ai maintenant une solution qui prend en charge à la fois refactoring et est de type sûr. Il m'a aussi mettre en œuvre INotifyPropertyChanged il se débrouille avec des propriétés étant rebaptisés.

Il est semble utilisation comme:

checkBoxCanEdit.Bind(c => c.Checked, person, p => p.UserCanEdit);
textBoxName.BindEnabled(person, p => p.UserCanEdit);
checkBoxEmployed.BindEnabled(person, p => p.UserCanEdit);
trackBarAge.BindEnabled(person, p => p.UserCanEdit);

textBoxName.Bind(c => c.Text, person, d => d.Name);
checkBoxEmployed.Bind(c => c.Checked, person, d => d.Employed);
trackBarAge.Bind(c => c.Value, person, d => d.Age);

labelName.BindLabelText(person, p => p.Name);
labelEmployed.BindLabelText(person, p => p.Employed);
labelAge.BindLabelText(person, p => p.Age);

La classe personne montre comment INotifyPropertyChanged mis en œuvre dans un type moyen sûr (ou voir cette réponse pour une autre plutôt belle façon de mettre en œuvre INotifyPropertyChanged, ActiveSharp - automatique INotifyPropertyChanged semble également bon):

public class Person : INotifyPropertyChanged
{
   private bool _employed;
   public bool Employed
   {
      get { return _employed; }
      set
      {
         _employed = value;
         OnPropertyChanged(() => c.Employed);
      }
   }

   // etc

   private void OnPropertyChanged(Expression<Func<object>> property)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, 
             new PropertyChangedEventArgs(BindingHelper.Name(property)));
      }
   }

   public event PropertyChangedEventHandler PropertyChanged;
}

Les WinForms de liaison classe d'aide a la viande dans ce qui fait tout le travail:

namespace TypeSafeBinding
{
    public static class BindingHelper
    {
        private static string GetMemberName(Expression expression)
        {
            // The nameof operator was implemented in C# 6.0 with .NET 4.6
            // and VS2015 in July 2015. 
            // The following is still valid for C# < 6.0

            switch (expression.NodeType)
            {
                case ExpressionType.MemberAccess:
                    var memberExpression = (MemberExpression) expression;
                    var supername = GetMemberName(memberExpression.Expression);
                    if (String.IsNullOrEmpty(supername)) return memberExpression.Member.Name;
                    return String.Concat(supername, '.', memberExpression.Member.Name);
                case ExpressionType.Call:
                    var callExpression = (MethodCallExpression) expression;
                    return callExpression.Method.Name;
                case ExpressionType.Convert:
                    var unaryExpression = (UnaryExpression) expression;
                    return GetMemberName(unaryExpression.Operand);
                case ExpressionType.Parameter:
                case ExpressionType.Constant: //Change
                    return String.Empty;
                default:
                    throw new ArgumentException("The expression is not a member access or method call expression");
            }
        }

        public static string Name<T, T2>(Expression<Func<T, T2>> expression)
        {
            return GetMemberName(expression.Body);
        }

        //NEW
        public static string Name<T>(Expression<Func<T>> expression)
        {
           return GetMemberName(expression.Body);
        }

        public static void Bind<TC, TD, TP>(this TC control, Expression<Func<TC, TP>> controlProperty, TD dataSource, Expression<Func<TD, TP>> dataMember) where TC : Control
        {
            control.DataBindings.Add(Name(controlProperty), dataSource, Name(dataMember));
        }

        public static void BindLabelText<T>(this Label control, T dataObject, Expression<Func<T, object>> dataMember)
        {
            // as this is way one any type of property is ok
            control.DataBindings.Add("Text", dataObject, Name(dataMember));
        }

        public static void BindEnabled<T>(this Control control, T dataObject, Expression<Func<T, bool>> dataMember)
        {       
           control.Bind(c => c.Enabled, dataObject, dataMember);
        }
    }
}

utilise beaucoup de nouvelles choses à 3,5 C # et montre tout ce qui est possible. Maintenant, si seulement nous avions hygiénique macros programmeur Lisp peut cesser de nous appeler des citoyens de seconde classe)

Autres conseils

L'opérateur NomDe a été mis en œuvre en C # 6.0 avec 4.6 .NET et VS2015 en Juillet 2015. Ce qui suit est toujours valide pour C # <6.0

Pour éviter les chaînes qui contiennent des noms de propriété, j'ai écrit une classe simple en utilisant des arbres d'expression pour renvoyer le nom du membre:

using System;
using System.Linq.Expressions;
using System.Reflection;

public static class Member
{
    private static string GetMemberName(Expression expression)
    {
        switch (expression.NodeType)
        {
            case ExpressionType.MemberAccess:
                var memberExpression = (MemberExpression) expression;
                var supername = GetMemberName(memberExpression.Expression);

                if (String.IsNullOrEmpty(supername))
                    return memberExpression.Member.Name;

                return String.Concat(supername, '.', memberExpression.Member.Name);

            case ExpressionType.Call:
                var callExpression = (MethodCallExpression) expression;
                return callExpression.Method.Name;

            case ExpressionType.Convert:
                var unaryExpression = (UnaryExpression) expression;
                return GetMemberName(unaryExpression.Operand);

            case ExpressionType.Parameter:
                return String.Empty;

            default:
                throw new ArgumentException("The expression is not a member access or method call expression");
        }
    }

    public static string Name<T>(Expression<Func<T, object>> expression)
    {
        return GetMemberName(expression.Body);
    }

    public static string Name<T>(Expression<Action<T>> expression)
    {
        return GetMemberName(expression.Body);
    }
}

Vous pouvez utiliser cette classe comme suit. Même si vous pouvez l'utiliser uniquement dans le code (donc pas en XAML), il est très utile (au moins pour moi), mais votre code est toujours pas typesafe. Vous pouvez étendre le nom de la méthode avec un second argument de type qui définit la valeur de retour de la fonction, ce qui limiterait le type de la propriété.

var name = Member.Name<MyClass>(x => x.MyProperty); // name == "MyProperty"

Jusqu'à présent, je n'ai rien trouvé qui résout le problème de la liaison de données Sûreté du typage.

Cordialement

Le cadre 4.5 nous donne la CallerMemberNameAttribute , qui fait passer le nom de la propriété comme une chaîne inutile:

private string m_myProperty;
public string MyProperty
{
    get { return m_myProperty; }
    set
    {
        m_myProperty = value;
        OnPropertyChanged();
    }
}

private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
    // ... do stuff here ...
}

Si vous travaillez Framework 4.0 avec KB2468871 installé, vous pouvez installer la Microsoft BCL pack de compatibilité par NuGet , qui a également fournit cet attribut.

Ce blog soulève quelques bonnes questions sur les performances de cette approche . Vous pouvez améliorer ces lacunes en convertissant l'expression d'une chaîne dans le cadre d'une sorte d'initialisation statique.

Les mécanismes réels pourraient être un peu disgracieux, mais il serait encore le type de sécurité et de performance à peu près égale à la première INotifyPropertyChanged.

Quelque chose un peu comme ceci:

public class DummyViewModel : ViewModelBase
{
    private class DummyViewModelPropertyInfo
    {
        internal readonly string Dummy;

        internal DummyViewModelPropertyInfo(DummyViewModel model)
        {
            Dummy = BindingHelper.Name(() => model.Dummy);
        }
    }

    private static DummyViewModelPropertyInfo _propertyInfo;
    private DummyViewModelPropertyInfo PropertyInfo
    {
        get { return _propertyInfo ?? (_propertyInfo = new DummyViewModelPropertyInfo(this)); }
    }

    private string _dummyProperty;
    public string Dummy
    {
        get
        {
            return this._dummyProperty;
        }
        set
        {
            this._dummyProperty = value;
            OnPropertyChanged(PropertyInfo.Dummy);
        }
    }
}

Une façon d'obtenir des commentaires si vos fixations sont cassés, est de créer un DataTemplate et déclarer son DataType être le type de ViewModel qu'il se lie à, par exemple si vous avez un PersonView et un PersonViewModel vous procédez comme suit:

  1. déclarer un DataTemplate avec DataType = PersonViewModel et une clé (par exemple PersonTemplate)

  2. Couper tous XAML PersonView et le coller dans le modèle de données (qui idéalement peut être juste en haut de la PersonView.

3a. Créer un ContentControl et définissez le ContentTemplate = PersonTemplate et lier son contenu à la PersonViewModel.

3b. Une autre option est de ne pas donner une clé de la DataTemplate et non régler la ContentTemplate du ContentControl. Dans ce cas, WPF savoir ce DataTemplate à utiliser, car il sait quel type d'objet que vous liez. Il recherche l'arbre et trouver votre DataTemplate et étant donné qu'il correspond au type de la liaison, il appliquera automatiquement comme ContentTemplate.

Vous vous retrouvez avec essentiellement la même vue que précédemment, mais puisque vous cartographié le DataTemplate à un type de données sous-jacente, des outils comme ReSharper pouvez vous donner des commentaires (via des identifiants de couleur - ReSharper-Options-Réglages couleurs Identifiers) à castrés votre les liaisons sont rompues ou non.

Vous ne toujours obtenir des avertissements du compilateur, mais pouvez vérifier visuellement pour les fixations cassées, ce qui est mieux que d'avoir à vérifier et-vient entre votre point de vue et viewmodel.

Un autre avantage de ces informations supplémentaires que vous donnez, est, qu'il peut également être utilisé en renommant refactoring. Pour autant que je me souviens ReSharper est capable de renommer automatiquement les liaisons sur DataTemplates tapées lorsque le nom de la propriété de ViewModel sous-jacente est modifiée et vice-versa.

  

1. Si la propriété est supprimé ou renommé, je ne reçois pas un avertissement du compilateur.

     

2.Dans un changement de nom de la propriété avec un outil de refactoring, il est probable que la liaison de données ne seront pas mises à jour.

     

3.J'ai ne reçoivent pas d'erreur avant l'exécution si le type de la propriété est faux, par exemple la liaison d'un nombre entier à un sélecteur de dates.

Oui, Ian, qui sont exactement les problèmes avec les données entraîné nom chaîne obligatoire. Vous avez demandé un modèle de conception. J'ai conçu le modèle typées Voir modèle (TVM) qui est une concrétion de la partie Voir modèle du modèle Model-View-ViewModel (MVVM). Il est basé sur une liaison de type sécurisé, similaire à votre propre réponse. Je viens juste de poster une solution pour WPF:

http: // www.codeproject.com/Articles/450688/Enhanced-MVVM-Design-w-Type-Safe-View-Models-TVM

x: bind (aussi appelé "les liaisons de données compilées") pour XAML (application universelle) dans les fenêtres 10 et 10 fenêtres téléphone peut résoudre ce problème, voir https://channel9.msdn.com/Events/Build/2015/3-635

Je ne peux pas trouver la ligne sur docs pour, mais ne l'ai pas mis beaucoup d'efforts dans, car il est quelque chose que je ne vais pas utiliser pendant un certain temps. Cependant, cette réponse devrait être un pointeur utile à d'autres personnes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top