2010-03-16 5 views
3

Demandes/problèmes:WinForms Databinding meilleures pratiques

  1. Je voudrais lier plusieurs propriétés d'une entité à des contrôles dans un formulaire. Certains ne sont lus que de temps en temps (selon la logique métier). - Edit: La logique est basée sur l'instance liée, et pas seulement sur son type.
  2. Lorsque vous utilisez une entité qui implémente INotifyPropertyChanged comme DataSource, chaque notification de changement Actualise tous les contrôles liés à cette source de données (facile à vérifier - il suffit de lier deux propriétés à deux contrôles et invoquer une notification de changement sur l'un d'eux, vous verrez que les deux propriétés sont frappées et réévaluées).
  3. Il doit y avoir notifications d'erreur conviviales (l'entité implémente IDataErrorInfo). (Probablement en utilisant ErrorProvider)

Utilisation de l'entité comme DataSource des contrôles conduit à des problèmes de performance et rend la vie plus difficile quand il est temps pour un contrôle à lecture seule. J'ai pensé à créer une sorte de wrapper qui contient l'entité et une propriété spécifique pour que chaque contrôle soit lié à un DataSource différent. En outre, ce wrapper peut contenir l'indicateur ReadOnly pour cette propriété afin que le contrôle soit lié directement à cette valeur.

L'enveloppe pourrait ressembler à ceci:

interface IPropertyWrapper : INotifyPropertyChanged, IDataErrorInfo 
{ 
    object Value { get; set; } 

    bool IsReadOnly { get; } 
} 

Mais cela signifie aussi un ErrorProvider différent pour chaque propriété (wrapper propriété)

Je sens que je suis en train de réinventer la roue ... Quelle est la manière «appropriée» de gérer des demandes de reliure complexes comme celles-ci?

Merci d'avance.

Répondre

0

Je n'envelopperais pas chaque propriété individuellement ... J'envelopperais l'objet de domaine racine. J'y implémenterais la logique en lecture seule ... et ne définirais la valeur que sur l'objet du domaine réel si l'indicateur readonly est défini sur false.

+0

Hey , notez que chaque propriété a une logique de lecture seule différente. Comment emballeriez-vous l'objet de domaine pour l'activer? Et qu'en est-il de l'impact sur les performances lié au fait que la même source de données est liée à de nombreux contrôles? –

3

Vous pouvez écrire un wrapper pour votre entité qui implémente ICustomTypeDescriptor. De cette façon, vous pouvez décider quelles propriétés sont en lecture seule ou non ... mais c'est beaucoup de travail pour un scénario pas si complexe.

Une solution plus simple consisterait à remplacer DataSourceUpdateMode de la liaison par Never lorsque vous souhaitez que la propriété soit en lecture seule.


MISE À JOUR: voici un emballage de base mise en œuvre ICustomTypeDescriptor:

class EntityWrapper<T> : CustomTypeDescriptor 
{ 
    public EntityWrapper(T entity) 
    { 
     this.Entity = entity; 
     var properties = TypeDescriptor.GetProperties(typeof(T)) 
        .Cast<PropertyDescriptor>() 
        .ToArray(); 
     ReadOnly = properties.ToDictionary(p => p.Name, p => p.IsReadOnly); 
     _properties = new PropertyDescriptorCollection(properties 
          .Select(p => new WrapperPropertyDescriptor(p, this)) 
          .ToArray()); 
    } 

    public T Entity { get; private set; } 
    public Dictionary<string, bool> ReadOnly { get; private set; } 

    public override PropertyDescriptorCollection GetProperties() 
    { 
     return _properties; 
    } 

    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) 
    { 
     return _properties; 
    } 

    private PropertyDescriptorCollection _properties; 
    private class WrapperPropertyDescriptor : PropertyDescriptor 
    { 
     private EntityWrapper<T> _entityWrapper; 
     private PropertyDescriptor _property; 

     public WrapperPropertyDescriptor(PropertyDescriptor property, EntityWrapper<T> entityWrapper) 
      : base(property) 
     { 
      _property = property; 
      _entityWrapper = entityWrapper; 
     } 

     public override bool CanResetValue(object component) 
     { 
      return _property.CanResetValue(component); 
     } 

     public override Type ComponentType 
     { 
      get { return _property.ComponentType; } 
     } 

     public override object GetValue(object component) 
     { 
      return _property.GetValue(component); 
     } 

     public override bool IsReadOnly 
     { 
      get 
      { 
       return _entityWrapper.ReadOnly[this.Name]; 
      } 
     } 

     public override Type PropertyType 
     { 
      get { return _property.PropertyType; } 
     } 

     public override void ResetValue(object component) 
     { 
      _property.ResetValue(component); 
     } 

     public override void SetValue(object component, object value) 
     { 
      _property.SetValue(component, value); 
     } 

     public override bool ShouldSerializeValue(object component) 
     { 
      return _property.ShouldSerializeValue(component); 
     } 
    } 
} 

Comme vous pouvez le voir, il est parfaitement possible de faire une propriété en lecture seule pour un seul exemple:

 MyEntity a = new MyEntity { Foo = "hello", Bar = 42 }; 
     MyEntity b = new MyEntity { Foo = "world", Bar = 5 }; 
     EntityWrapper<MyEntity> wa = new EntityWrapper<MyEntity>(a); 
     EntityWrapper<MyEntity> wb = new EntityWrapper<MyEntity>(b); 

     var fooA = wa.GetProperties()["Foo"]; 
     var fooB = wb.GetProperties()["Foo"]; 

     wa.ReadOnly["Foo"] = false; 
     wb.ReadOnly["Foo"] = true; 

     Console.WriteLine("Property Foo of object a is read-only : {0}", fooA.IsReadOnly); 
     Console.WriteLine("Property Foo of object b is read-only : {0}", fooB.IsReadOnly); 
+0

L'interface 'ICustomTypeDescriptor' vous permet de décrire un type, pas une seule entité, de sorte que la logique de lecture seule serait par type, pas par instance. Le réglage 'DataSourceUpdateMode.Never' laisse toujours l'utilisateur taper/changer la valeur des contrôles et le contrôle ne s'affichera que comme en lecture seule ... –

+0

" donc la logique de lecture seule serait par type, pas par instance ": pas nécessairement, voir ma mise à jour réponse –

+0

Malheureusement, j'ai essayé d'aller dans ce sens avant, et d'après mon expérience, cette solution n'est pas optimale. Et en pensant à la * responsabilité d'un descripteur de propriété *, alors il faut implémenter 'INotityPropertyChanged' (comme nous voulons que l'affichage change) et avoir le membre' EntityWrapper ', mais en passant le composant au '_property' enveloppé ... Eh bien, il semble juste faux (et avec l'exemple/mise en œuvre de test ne fonctionnera pas). De plus - Cela nous force à lier notre contrôle à * deux sources de données différentes * (cette entité - pour la valeur, et le descripteur - pour l'indication en lecture seule) –