Autor Beitrag
lapadula
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 180
Erhaltene Danke: 10



BeitragVerfasst: Mo 04.02.19 14:51 
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.

ausblenden volle Höhe 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<stringobject> propertyValues;


        public ClassRechnung()
        {
            propertyValues = new Dictionary<stringobject>();
        }
        
        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(thisnew PropertyChangedEventArgs(propertyName));
            }
        }
    }


ausblenden volle Höhe 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:

ausblenden 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?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 04.02.19 15:50 
Die Klasse selbst kann nicht wissen wann du von außen fertig bist mit dem setzen von Properties. Aus sich selbst heraus(aus der Klasse selbst) sehe ich nicht wie man das machen sollte da muss man schon explizit nachhelfen.

Das man mehr als eine Property setzen muss ist aber, denke ich,

a.) eher ein Initialisierungsproblem, und kann dann dadurch gelöst werden das man den Changed Handler später verdrahtet.
b.) und es sollte beim ändern auch für jede Property ein Changed Event geworfen werden. Weil ja bei verschiedenen Properties unterschiedliche Receiver des Events reagieren sollen und in den EventArgs die betroffene Property explizit genannt wird.

Stell dir vor du würdest die beiden Labels nicht über die UpdateUI Methode aktualisieren sondern über DataBinding (was jetzt eigentlich super funktionieren sollte sobald INotifyPropertyChanged implementiert ist) du hast jetzt 2 Receiver für den Event das eine Label reagiert wenn in den EventArgs Betrag genannt wird und das andere wenn Mwst genannt wird.
Du hast aber das auf Property Ebene bezogene INotifyPropertyChanged Konzept auf Klassenänderung degeneriert und eine einzelne Änderung wieder auf eine komplette UI Aktualisierung zurückgemappt.

Wenn dich DataBinding nicht interessiert un du das nicht benutzen willst und dein Ziel Klassenänderungen zu verfolgen ist und nicht Propertyänderungen solltest du ein anderes eigenes Interface nehmen das so ähnlich funktioniert wie INotifyPropertyChanged. INotifyPropertyChanged nehmen aber es eigentlich anders benutzen wollen verwirrt andere Entwickler nur. Möglicherweise bleibt es auch nicht dabei nur andere Entwickler verwirrt werden ;)

Für diesen Beitrag haben gedankt: lapadula
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Mo 04.02.19 22:59 
- Nachträglich durch die Entwickler-Ecke gelöscht -

Für diesen Beitrag haben gedankt: lapadula
jfheins
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 918
Erhaltene Danke: 158

Win 10
VS 2013, VS2015
BeitragVerfasst: 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:
ausblenden 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(thisnew PropertyChangedEventArgs(propertyName));
      }
    }

    protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
      PropertyChanged?.Invoke(thisnew PropertyChangedEventArgs(propertyName));
    }
  }


Und leite deine Models davon ab. In der Modellklasse hast du dann nur noch:
ausblenden 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); }
    }
  }

Für diesen Beitrag haben gedankt: lapadula