Laden...

WPF - MVVM - INotifyPropertyChanged

Erstellt von irgendwas vor 10 Jahren Letzter Beitrag vor 10 Jahren 6.372 Views
I
irgendwas Themenstarter:in
36 Beiträge seit 2013
vor 10 Jahren
WPF - MVVM - INotifyPropertyChanged

Hallo,

ich wollte mal fragen welche Ursachen es geben kann, wenn INotifyPropertyChanged durch Model und ViewModel implementiert wurde, ein PropertyChange vom Model verkündet wird, durch das ViewModel durchgereicht wird, in dem PropertyChangedEventHandler PropertyChanged das Element der View steht an dessen DataContext das ViewModel hängt, aber die View nicht aktualisiert?

Vielleicht beschreib ich es auch gerade blöd, aber ich bin mittlerweile auch ein wenig Matschig im Kopf. Ich schreib dazu morgen mehr.

Vielen Dank soweit.

++++ Tag ein, Tag aus: HTML-Programmierer beklagt monotone Arbeit ++++
++++ Ein Witz auf seine Kosten: Masochist kann nur gequält lächeln ++++
++++ Nichts dran: Model leugnet Magersucht ++++

I
irgendwas Themenstarter:in
36 Beiträge seit 2013
vor 10 Jahren

Hi,

ich poste mal ein bisschen Code um meine Verwirrung etwas zu verdeutlichen oder mich für einen offensichtlichen Fehler hauen zu lassen, den ich nicht sehe. 😉

Die Deklaration in XAML sieht so aus.

<ListBox Name="LB" ItemsSource="{Binding ChildObjects}">
	<ListBox.ItemTemplate>
		<DataTemplate>
			<TextBlock Margin="0" Text="{Binding Path=Name}" />
	        </DataTemplate>
	</ListBox.ItemTemplate>
</ListBox>

Im Code behind hänge ich so das ViewModel ein.

LB.DataContext = new ListViewModel();

Und dann gibt es da noch die restlichen Klassen ... die Struktur ist ein wenig doppelt, das ist allerdings dem DataProvider geschuldet und dem Problem einer sonst auftretenden Ringabhängigkeit.

public class ListViewModel : ViewModelBase
{

	public ListViewModel()
        {
            SomeDataProvier.PropertyChanged += new PropertyChangedEventHandler(notify);
        }

        public List<ObjectViewModel> ChildObjects
        {
            get
            {
                return SomeDataProvider.SomeObjectList.Select(o => new ObjectViewModel(o)).ToList();
            }
        }

        private void notify(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName.Equals("SomeObjectList"))
                RaisePropertyChanged("ChildObjects");
        }

}


public class ObjectViewModel : ViewModelBase
{

	private SomeObject _o;

	public ObjectViewModel(SomeObject o){
		_o = o;
		_o.PropertyChanged += new PropertyChangedEventHandler(RaisePropertyChanged);		
	}

	string Name {
		get {
			return _o.Name;			
		}
		set {
			_o.Name = value;		
		}
	}

}

public class SomeObject : ModelBase
{

	private string _name;
	
	string Name {
		get {
			return _name;
		}
		set {
			_name = value;
			RaisePropertyChanged("Name");
		}
	}

}

public abstract class ModelBase : INotifyPropertyChanged
{
        #region INotifyPropertyChanged Members
	public event PropertyChangedEventHandler PropertyChanged;
	#endregion

        protected virtual void RaisePropertyChanged(string propertyName){
            PropertyChangedEventHandler handler = PropertyChanged;         
            if (handler != null)             
                handler(this, new PropertyChangedEventArgs(propertyName));     
        }
}

public abstract class ViewModelBase : INotifyPropertyChanged
{
        #region INotifyPropertyChanged Members
	public event PropertyChangedEventHandler PropertyChanged;
	#endregion

        protected virtual void RaisePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, e);
        }
        protected virtual void RaisePropertyChanged(string propertyName){
            PropertyChangedEventHandler handler = PropertyChanged;         
            if (handler != null)             
                handler(this, new PropertyChangedEventArgs(propertyName));     
        }
}

Ich hoffe ich übersehe irgendwo etwas. Ich habe im Prinzip die gleiche Struktur an anderer Stelle mit einer TreeView am Laufen bei der das funktioniert - und ich sehe einfach den Unterschied zwischen den beiden Codes nicht.

++++ Tag ein, Tag aus: HTML-Programmierer beklagt monotone Arbeit ++++
++++ Ein Witz auf seine Kosten: Masochist kann nur gequält lächeln ++++
++++ Nichts dran: Model leugnet Magersucht ++++

26 Beiträge seit 2011
vor 10 Jahren

Du scheinst noch ein wenig Probleme mit MVVM zu haben?
INotifyPropertyChanged sollte eigentlich nur das ViewModel implementieren. Außerdem solltest Model und ViewModel trennen.
Die bei der Kommunikation zwischen Model und ViewModel könnte dir dann der Messenger von MVVM Light weiterhelfen.

"Wer mit künstlicher Intelligenz arbeitet, muß auch mit natürlicher Dummheit rechnen." - Klaus Kornwachs

I
irgendwas Themenstarter:in
36 Beiträge seit 2013
vor 10 Jahren

Hallo,

auch wenn ich hier möglicher Weise dadurch das Pattern verwurstet habe -
warum soll ich genau den Messanger nehmen anstatt mich über das Interface selbst zu benachrichtigen?

Fulfills the same purpose than the PropertyChanged event, but in a less tight way.

Schöne Grüße

++++ Tag ein, Tag aus: HTML-Programmierer beklagt monotone Arbeit ++++
++++ Ein Witz auf seine Kosten: Masochist kann nur gequält lächeln ++++
++++ Nichts dran: Model leugnet Magersucht ++++

26 Beiträge seit 2011
vor 10 Jahren

Um Model und ViewModel zu trennen.

Dein Code oben ist noch ziemlich tief verzahnt. Dein Model austauschen ist schwieig, da du auch dein ViewModel ändern müsstest!
Wenn du auf den Messenger setzt kannst du das Model austauschen wie du möchtest. Dein ViewModel schickt nur Messages ab und wartet auf Antwort, wer da konkret die Messages aufnimmt und oder eventuell antwortet ist dem ViewModel egal 😃

eine Art zu antworten wäre über den Messenger ein eigene cutsom messega zu versenden die von MessageBase erbt. Properties sind die Daten die von dem ViewModel an das Model übergeben werden sollen. den rückweg kannst du über einen delegaten in einem private field lösen, den du im model über eine public method auslöst und eben als parameter die daten übergibst die das model an das viewmodel schicken möchte. wie diese daten behandelt werden bestimmt das viewmodel bei erzeugung der message und übergibt dabei an den delegaten die entsprechende methode

"Wer mit künstlicher Intelligenz arbeitet, muß auch mit natürlicher Dummheit rechnen." - Klaus Kornwachs

I
irgendwas Themenstarter:in
36 Beiträge seit 2013
vor 10 Jahren

Hi,

die Einwände sind einzusehen. Allerdings würde ich jetzt gerne auf das ursprüngliche Problem zurückkommen. Gibt es einen offensichtlichen Grund warum die Änderung nicht korrekt übernommen werden?

++++ Tag ein, Tag aus: HTML-Programmierer beklagt monotone Arbeit ++++
++++ Ein Witz auf seine Kosten: Masochist kann nur gequält lächeln ++++
++++ Nichts dran: Model leugnet Magersucht ++++

26 Beiträge seit 2011
vor 10 Jahren

in deiner klasse ObjectViewModel musst du im setter ebenfalls

RaisePropertyChanged("Name");

aufrufen 😉 dann sollte es eigentlich tun

"Wer mit künstlicher Intelligenz arbeitet, muß auch mit natürlicher Dummheit rechnen." - Klaus Kornwachs

1.378 Beiträge seit 2006
vor 10 Jahren

Hallo irgendwas,

mir fallen da ein paar Sachen an deinem Code unangenehm auf:

  • Für Listen im ViewModel verwendet man eher eine ObservableCollection, welche man nicht bei jeder Änderung komplett leert und neu befüllt sondern nur gezielt die Elemente rausnimmt und einfügt die sich geändert haben.

  • Die generierten ObjektViewModels würde ich nur bei einem "Notify" aufruf neu erzeugen und nicht bei jedem Propertyzugriff. (Ich vermute hier auch irgendwo dein Problem)

  • In einer Klasse die INotifyPropertyChanged implementiert macht es nur Sinn die eigenen PropertyChanges zu feuern. Der Aufruf _o.PropertyChanged += RaisePropertyChanged ist daher quatsch, auch wenn es in dem Fall nicht auffällt, da in ObjectViewModel und im SomeObject beide Male das selbe Property Name gibt. Heißt aber das Property im SomeObject zB Vorname und Nachname und im ObjectViewModel einfach nur Name, wird die View nie davon benachrichtigt werden, dass Name sich verändert hat wenn Vor oder Nachname sich geändert haben. -> Hier gehört PropertyChanged genauso implementiert wie bei ListViewModel.

  • Bei Setter, die PropertyChanged aufrufen, würde ich ein if(value != oldValue) einbauen um unnötige Aufrufe zu minimieren.

  • Und um etwas Redundanz zu sparen würde ich NotifyPropertyChanged noch einmal eine Stufe höher ansiedeln.

Hier meine Vorschläge dazu eingearbeitet:



    public class ListViewModel : ViewModelBase
    {
        public ListViewModel()
        {
            ChildObjects = new ObservableCollection<ObjectViewModel>();
            SomeDataProvier.PropertyChanged += new PropertyChangedEventHandler(Notify);
        }

        public ObservableCollection<ObjectViewModel> ChildObjects { get; private set; }

        private void Notify(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName.Equals("SomeObjectList"))
            {
                //hier kannst du auch gezielt Items entfernen und hinzufügen

                //ChildObjects.Clear();

                //foreach (SomeObject o in SomeDataProvider.SomeObjectList)
                //{
                //    ChildObjects.Add(new ObjectViewModel(o));
                //}
            }
        }
    }

    public class ObjectViewModel : ViewModelBase
    {
        private SomeObject _o;

        public ObjectViewModel(SomeObject o)
        {
            _o = o;
            _o.PropertyChanged += SomeObjectPropertyChanged;
        }

        private void SomeObjectPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Name")
                RaisePropertyChanged("Name");
        }

        public string Name
        {
            get { return _o.Name; }
            set { _o.Name = value; }
        }
    }

    public class SomeObject : ModelBase
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    RaisePropertyChanged("Name");
                }
            }
        }
    }

    public abstract class ModelBase : ObjectBase { }

    public abstract class ViewModelBase : ObjectBase { }

    public abstract class ObjectBase : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Lg, XXX

I
irgendwas Themenstarter:in
36 Beiträge seit 2013
vor 10 Jahren

Hallo,

ja, mein Beispiel Code mit dem ich das Problem untersuche bemängelst Du vollkommen zu recht. Die meisten Dinge löse ich eigentlich etwas anders. Mir ging es nur darum, die Verkettung aufzuzeigen um zu klären, ob ich hier einen generellen Denkfehler mache.

Die ViewModels werden eigentlich nicht jedes Mal verworfen sondern zental verwaltet - auch lasse ich jede Änderung prüfen bevor ein PropertyChanged gefeuert wird. Da Objekte deutlich häufiger geändert als angelegt werden habe ich mir die ObservableCollections gespart und filtere stattdessen mein zentrales Dictionary mit Values().ToList() .

Ich konnte mein Problem jetzt soweit eingrenzen, dass sich meine ListView einfach nicht für mein PropertyChanged interessiert, d.h. der PropertyChangedEventHandler der einzelnen Objekte bleibt scheinbar unregistriert. Naja, ich baue mal die ObservableCollections ein.

Schöne Grüße

++++ Tag ein, Tag aus: HTML-Programmierer beklagt monotone Arbeit ++++
++++ Ein Witz auf seine Kosten: Masochist kann nur gequält lächeln ++++
++++ Nichts dran: Model leugnet Magersucht ++++

I
irgendwas Themenstarter:in
36 Beiträge seit 2013
vor 10 Jahren

Hallo,

wie erwartet macht die ObeservableCollection nicht den geringsten Unterschied.
Warum reagiert nur die ListView nicht auf die Changes der Objekte?

Schöne Grüße

++++ Tag ein, Tag aus: HTML-Programmierer beklagt monotone Arbeit ++++
++++ Ein Witz auf seine Kosten: Masochist kann nur gequält lächeln ++++
++++ Nichts dran: Model leugnet Magersucht ++++

1.378 Beiträge seit 2006
vor 10 Jahren

Meine Vermutung steckt wie vorhin angedeutet in der Erzeugung und Verwendung unterschiedlicher ViewModels.

Wenns leicht geht, stell hier mal ein komplettes Minimalbeispiel rauf. Würd mich interessieren worans scheitert.

Lg, XXX

I
irgendwas Themenstarter:in
36 Beiträge seit 2013
vor 10 Jahren

Hallo,

mein Problem war ähnlich gelagert wie vermutet, allerdings deutlich komplexer.
Ich habe durch einen gut versteckten Fehler in meinem lokalen Objektcache unter bestimmten Bedingungen (!Threadsafty!) parallel das gleiche Objekt erstellt, zwar nur einmal im Cache abgelegt, aber anschließend mit den Objekten abwechselnd das ViewModel in meinem ViewModelPool überschrieben und mich anschließend über die fehlerhaften Changes gewundert.

Vielen Dank an alle für die Hilfe.

Schöne Grüße

++++ Tag ein, Tag aus: HTML-Programmierer beklagt monotone Arbeit ++++
++++ Ein Witz auf seine Kosten: Masochist kann nur gequält lächeln ++++
++++ Nichts dran: Model leugnet Magersucht ++++