lapadula - Mo 04.02.19 14:51
Titel: UI mit INotifiyPropertyChanged Interface aktualisieren
Hallo, ich experimentiere gerade etwas mit dem PropertyChanged Interface und habe dazu eine Frage.
Ich möchte mein Klassen-Objekt aktuell halten. Wenn sich eine Property des Objektes ändert, dann möchte ich das
das UI aktualisieren.
Nur als Beispiel: Ich erstelle eine Rechnung. Das Klassen-Objekt Rechnung hat die Property Betrag und Umsatzsteuer.
Wenn der Benutzer einen Bruttobetrag eingibt, dann soll er die berechnete Umsatzsteuer unmittelbar sehen.
Ich habe da was zusammengebastelt, das Event wird aber nach unnötigerweise (in diesem Beispiel) doppelt abgefeuert.
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82:
| public class ClassRechnung : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private readonly Dictionary<string, object> propertyValues;
public ClassRechnung() { propertyValues = new Dictionary<string, object>(); } public int Betrag { get { return Get(() => Betrag); } set { if (value != Betrag) { Set(() => Betrag, value); } } }
public decimal MwST { get { return Get(() => MwST); } set { if (value != MwST) { Set(() => MwST, value); } } }
protected void Set<T>(Expression<Func<T>> expression, T value) { string propertyName = GetPropertyNameFromExpression(expression); Set(propertyName, value); } public static string GetPropertyNameFromExpression<T>(Expression<Func<T>> expression) { MemberExpression memberExpression = (MemberExpression)expression.Body; return memberExpression.Member.Name; }
protected void Set<T>(string name, T value) { if (propertyValues.ContainsKey(name)) { propertyValues[name] = value; OnPropertyChanged(name); } else { propertyValues.Add(name, value); OnPropertyChanged(name); } } protected T Get<T>(string name) { if (propertyValues.ContainsKey(name)) { return (T)propertyValues[name]; } return default(T); } protected T Get<T>(Expression<Func<T>> expression) { string propertyName = GetPropertyNameFromExpression(expression); return Get<T>(propertyName); }
protected void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } |
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
| public partial class Form1 : Form { ClassRechnung _Rechnung = null; public Form1() { InitializeComponent(); _Rechnung = new ClassRechnung(); _Rechnung.PropertyChanged += ClassRechnung_PropertyChanged; } private void ClassRechnung_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { UpdateUI(); }
private void textBoxBetrag_TextChanged(object sender, System.EventArgs e) { int Betrag = 0; if (int.TryParse(textBoxBetrag.Text, out Betrag)) { _Rechnung.Betrag = Betrag; _Rechnung.MwST = (Betrag * 0.19M); } }
private void UpdateUI() { labelBetrag.Text = _Rechnung.Betrag.ToString(); labelMwst.Text = _Rechnung.MwST.ToString(); }
} |
Ist vllt ein schlechtes Beispiel, weil die MwSt normalerweise nicht vom Benutzer verändert wird und ich somit das Event bei
dieser Property nicht abfeuern muss. Wenn es aber verschiedene Rechnungspositionen gibt, wo der Benutzer die Beträge ändern kann, dann möchte ich mein Rechnungsobjekt aktuell halten,
ohne das das Event mehrfach abgefeuert wird.
Ich könnte pfuschen und den handler, nach dem der Betrag gesetzt wurde rausnehmen und dann wieder zuweisen, so:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| _Rechnung .Betrag = Betrag;
_Rechnung .PropertyChanged -= ClassRechnung _PropertyChanged;
_Rechnung .MwST = (Betrag * 0.19M); _Rechnung .xxx = xxx; _Rechnung .xxx = xxx; _Rechnung .xxx = xxx; _Rechnung .xxx = xxx;
_Rechnung .PropertyChanged += ClassRechnung _PropertyChanged; |
Wie kann ich es besser lösen?
jfheins - Mi 06.02.19 21:59
Ich kann dir leider bei deinem Problem nicht direkt helfen. Nur: Wenn du zwei Properties änderst, dann sind zwei Events voll OK. Das soll so und ist in der Regel auch nicht zu langsam ;-)
(Oder meintest du, dass pro Property zweimal gefeuert wird?)
Was mir aber aufgefallen ist: Du machst das viel zu kompliziert mit dem INotifyPropertyChanged.
Schreib dir eine Basisklasse:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| public abstract class BindableBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null) { if (!Equals(storage, value)) { storage = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } |
Und leite deine Models davon ab. In der Modellklasse hast du dann nur noch:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| public class ClassRechnung : BindableBase { private int _betrag; public int Betrag { get { return _betrag; } set { SetProperty(ref _betrag, value); } } } |