. Net WinForms INotifyPropertyChanged met à jour toutes les liaisons lorsqu'une est modifiée. Meilleure façon de faire?
Dans une application Windows forms, une modification de propriété qui déclenche INotifyPropertyChanged entraînera la lecture de chaque propriété de mon objet lié, pas seulement la propriété modifiée. (Voir Exemple de code ci-dessous)
Cela semble absurdement inutile puisque l'interface nécessite le nom de la propriété changeante. Cela provoque beaucoup d'horloge dans mon application parce que certains des getters de propriété nécessitent des calculs à effectuer.
Je devrai probablement implémenter une sorte de la logique dans Mes getters pour jeter les lectures inutiles s'il n'y a pas de meilleure façon de le faire.
Est-ce que je manque quelque chose? Est-il un meilleur moyen? Ne dites pas d'utiliser une technologie de présentation différente, je le fais sur Windows Mobile ( bien que le comportement se produise également sur le framework complet).
Voici un code de jouet pour démontrer le problème. En cliquant sur le bouton, les deux zones de texte seront remplies même si une propriété a modifié.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Example
{
public class ExView : Form
{
private Presenter _presenter = new Presenter();
public ExView()
{
this.MinimizeBox = false;
TextBox txt1 = new TextBox();
txt1.Parent = this;
txt1.Location = new Point(1, 1);
txt1.Width = this.ClientSize.Width - 10;
txt1.DataBindings.Add("Text", _presenter, "SomeText1");
TextBox txt2 = new TextBox();
txt2.Parent = this;
txt2.Location = new Point(1, 40);
txt2.Width = this.ClientSize.Width - 10;
txt2.DataBindings.Add("Text", _presenter, "SomeText2");
Button but = new Button();
but.Parent = this;
but.Location = new Point(1, 80);
but.Click +=new EventHandler(but_Click);
}
void but_Click(object sender, EventArgs e)
{
_presenter.SomeText1 = "some text 1";
}
}
public class Presenter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _SomeText1 = string.Empty;
public string SomeText1
{
get
{
return _SomeText1;
}
set
{
_SomeText1 = value;
_SomeText2 = value; // <-- To demonstrate that both properties are read
OnPropertyChanged("SomeText1");
}
}
private string _SomeText2 = string.Empty;
public string SomeText2
{
get
{
return _SomeText2;
}
set
{
_SomeText2 = value;
OnPropertyChanged("SomeText2");
}
}
private void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
2 réponses
La raison pour laquelle toutes les propriétés sont lues lorsque l'événement est déclenché repose sur la méthode PushData appelée sur l'objet binding lorsque L'événement ProperyChanged est déclenché. Si vous regardez la stacktrace, vous remarquerez que la méthode PropValueChanged de l'objet interne BindToObject est appelée, qui appelle à son tour L'événement Oncurrentchanged sur le BindingManager. Le mécanisme de liaison garde la trace des changements d'élément actuels, mais il ne fait pas une distinction plus granulaire. Coupable" La méthode PushData appelle le getter sur vos propriétés (jetez un oeil au code en utilisant reflector). Il n'y a donc aucun moyen de contourner cela. Cela dit, en règle générale, dans les accesseurs get et set, il n'est pas recommandé de faire le traitement lourd, utiliser des méthodes get et set pour que (si possible)
Jetez également un oeil à cet article, et ce commentaire en particulier (http://www.codeproject.com/Messages/2514032/How-Binding-watches-control-properties-i-e-how-doe.aspx), cela explique exactement comment l'événement propertychanged est déclenché, bien qu'il ne résoudra pas votre problème de getter: http://www.codeproject.com/KB/database/databinding_tutorial.aspx?msg=2514032
Une idée à explorer est de retarder l'appel du getter. Vous pouvez y parvenir en jouant avec la propriété ControlUpdateMode de la liaison. Lorsque cette valeur est définie sur Never, le contrôle correspondant ne sera pas mis à jour en cas de modification. Cependant, lorsque vous revenez à la valeur OnPropertyChanged, la méthode PushData sera appelée, de sorte que les getters seront accessibles. Donc, compte tenu de votre exemple, ce code empêchera temporairement la zone de texte 2 de mettre à jour:
void but_Click(object sender, EventArgs e)
{
txt2.DataBindings[0].ControlUpdateMode = ControlUpdateMode.Never;
_presenter.SomeText1 = "some text 1";
}
Je teste la liaison de sous-classement comme ceci et gère OnPropertyChanged, peut-être vous aide.
public class apBinding : Binding
{
public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
: base(propertyName, dataSource, dataMember)
{
this.ControlUpdateMode = ControlUpdateMode.Never;
dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.BindingMemberInfo.BindingField)
{
this.ReadValue();
}
}
}
Maintenant, le problème que je trouve est que le contrôle écrase la valeur de l'objet lié, j'ai donc modifié à
public class apBinding : Binding
{
public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
: base(propertyName, dataSource, dataMember)
{
dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.ControlUpdateMode = ControlUpdateMode.Never;
if (e.PropertyName == this.BindingMemberInfo.BindingField)
{
this.ReadValue();
}
}
}
Ensuite, la première fois que propertychanges est appelé, je désactive controlupdate. et le contrôle est correctement mis à jour à la première exécution.