Laden...

Wie kann ich ein Event werfen, wenn alle Properties initial gebunden wurden?

Erstellt von _Cashisclay vor 4 Jahren Letzter Beitrag vor 4 Jahren 2.955 Views
_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren
Wie kann ich ein Event werfen, wenn alle Properties initial gebunden wurden?

Hallo zusammen,

ich wusste mich schon nicht richtig beim Threadnamen auszudrücken.

Beim Laden meiner Ansicht werfe ich das Event Raise(String.Empty) um nachträglich nochmal alle Propertys zu durchlaufen um mein IDataErrorInfo auch beim neuladen der Daten immer aktuell zu halten.

Nun hab ich das Problem das dadurch auch zum Beispiel die Events für Checkboxen ausgelöst werden und dadaruch Werte von mir verändert werden. Wenn ich mir ein Flag im Loaded Event setze hilft mir das leider nicht, gibt es eine Möglichkeit herauszufinden ob aktuell irgendwelche PropertyChanged Events geraised werden?

Grüße

16.807 Beiträge seit 2008
vor 4 Jahren

Warum denn wieder Events statt MVVM? Wurdest doch schon mehrfach drauf hingewiesen 😃

_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

Also ist es eigentlich gängig gar keine Events zu nutzen?
Und so etwas nur im MVVM im Setter umzusetzen?

*Edit : Aber mal abgesehen davon gibt es trotzdem eine Möglichkeit das überhaupt so umzusetzen? Rein interessehalber

2.078 Beiträge seit 2012
vor 4 Jahren

Zwecks Übersicht solltest Du das nur im Setter machen.
Verteilst Du das überall sonst, musst Du sonst bei jeder Änderung an den Properties erst suchen, wo überall das Event geworfen wird.
Natürlich gibt es auch Ausnahmen, aber die müssen dann schon gut sein 😄

Und ob das Geht: Beinahe alles geht 😉
Ein paar Ideen:*Du könntest ein geeignetes Event vom Control suchen und ans ViewModel durchreichen. *Oder Du registrierst ein Info-Event und wartest dann, bis das ausgelöst wird, z.B. mit einer der WaitHandle-Ableitungen oder der TaskCompletionSource.
Der Nachteil ist aber, dass dieses Info-Event als letztes registriert werden muss, denn nur dann hast Du halbwegs eine Chance, dass es auch als Letztes ausgeführt wurde und selbst das ist nicht 100% sicher.

*Ich persönlich würde mir eine ViewModelBase-Klasse schreiben, mit passenden GetValue- und SetValue-Methoden, die sich dann auch um das PropertyChanged-Event kümmern. Dort könntest Du dann auch ein weiteres Event (z.B. PropertyChangedFinished) einbauen, das dann definitiv nach dem PropertyChanged ausgeführt wird.

Ich behaupte aber, dass man in den meisten Fällen auch eine Alternative finden kann.
Ein klarer Programm-Ablauf wäre z.B. die einfachste Lösung, denn wenn das PropertyChanged-Event (z.B. im Setter) fertig ausgeführt wurde, sind auch alle Listener ausgeführt und die View aktualisiert - für Letzteres würde ich aber nicht die Hand ins Feuer legen 😄

_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

Danke erstmal für die Antwort.

Okay, also wenn das gänig so ist das man solche Events im Setter steuern sollte dann würde ich das mal abändern, das hilft mir schon mal und löst natürlich auch erstmal das Problem.

Und zu der anderen Sachen, durch PropertyChange(String.Empty) löse ich ja nochmal alle Properties aus um die Ansicht zu aktualisieren, allerdings hilft mir da auch kein Event weiter um zu wissen wann auch das letzte Property durchlaufen ist. Und dazu fällt mir auch persönlich keine gute Lösung ein.

Mit deinem zweiten Punkt meins du dann wahrscheinlich ich registiere ein Event oder setze ein Flag quasi beim auslösen des Ganzen und muss das dann selber beim letzten Property dann wieder ändern? Werden die Properties von oben nach unten durchlaufen?

Ich hätte es auch am liebsten in meiner ViewModelBase Klasse allerdings fällt mir grad kein Weg ein wie ich selber herausfinde das gerade das letzte Property durchlaufen ist in der Klasse.

_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

Ich hab mal den Threadnamen nochmal angepasst.

Ich suche also quasi eine Lösung eine Möglichkeit mitzubekommen wenn ich mit Raise(String.Empty) nochmal alle Properties durchlaufe, wann dieser Prozess fertig ist.

5.657 Beiträge seit 2006
vor 4 Jahren

durch PropertyChange(String.Empty) löse ich ja nochmal alle Properties aus um die Ansicht zu aktualisieren

Warum sollte man das tun? Die Ansicht wird doch automatisch durch das DataBinding aktualisiert. Wenn nicht, dann hast du irgendwo einen Fehler gemacht.

Weeks of programming can save you hours of planning

_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

Möchte ich nicht ausschließen wenn ich einen Fehler gemacht habe, eventuell finden wir ihn ja.

Wie erkläre ich das am besten, ich habe verschiedene Nummern hinter jeder Nummer steckt ein Model mit verschiedenen Daten drinne innerhalb der Ansicht kann ich die Nummer ändern, dann werden auch die Daten aus dem Model aktualisiert, aber das ViewModel wurde ja schon instanziert und die UI wurde ja auch bereits fertig geladen, deswegen dache ich bzw. hab das dann mit dem Raise(String.Empty) gelöst.

5.657 Beiträge seit 2006
vor 4 Jahren

innerhalb der Ansicht kann ich die Nummer ändern, dann werden auch die Daten aus dem Model aktualisiert

Die Ansicht ändert nur die Daten aus dem ViewModel. Das ViewModel wird dann erst (bei Bedarf) das Model ändern. Aber ohne Änderung im ViewModel funktioniert das DataBinding nicht.

Schau dir auch mal zur Veranschaulichung das Beispiel-Projekt in [Artikel] MVVM und DataBinding an.

Weeks of programming can save you hours of planning

_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

_Ich guck nebenbei schon im Beispielprojekt nach.
_

Ja, so passiert das bei mir ja auch, nur beim wechsel der Nummer, ändert sich bei mir das Model im Hintergrund wodurch das ViewModel ja nicht weiß das sich was geändert hat.

5.657 Beiträge seit 2006
vor 4 Jahren

Die automatische Aktualisierung der Ansicht bei Änderungen kann nur funktionieren, wenn dadurch das PropertyChanged-Ereignis ausgelöst wurde. Und u.a. genau zu diesem Zweck gibt es das ViewModel. Wenn du Bindings auf das Model setzt, oder in einem Command nur das Model (nicht aber das ViewModel) änderst, dann kann es nicht funktionieren.

Die Lösung ist dann aber nicht ein Aufruf von PropertyChange(String.Empty), sondern ein geeignetes ViewModel zu implementieren.

Im Prinzip muß das ViewModel den gesamten aktuellen Zustand der UI repräsentieren. Das Aktualisieren des Models passiert dann zu einem bestimmten Zeitpunkt (z.B. beim Klick auf "Speichern") völlig unabhängig davon.

Weeks of programming can save you hours of planning

2.078 Beiträge seit 2012
vor 4 Jahren

Für die Zusammenarbeit zwischen Model und ViewModel habe ich immer ReadModel- und WriteModel-Methoden - oder eine passendere Bezeichnung bzw. Definition, je nach Anwendungsfall.

Wenn die Model-Instanz egal ist:

public MyModel CreateModel() { /* ... */ }
public void UpdateModel(MyModel model) { /* ... */ }
public void ReadModel(MyModel model) { /* ... */ }

Wenn die Model-Instanz wichtig ist, könnte man sich entweder irgendwoanders Model und dazu passendes ViewModel merken, oder z.B. anhand einer ID zusammenführen.

Oder das ViewModel bekommt eine Referenz auf das Model und es gibt Methoden wie:

public void UpdateModel() { /* ... */ }
public void ReloadModel() { /* ... */ }

Das ViewModel enthält dann sämtliche Daten, die für die UI relevant sind und verwaltet auch die Änderungen daran, bis man sie dann speichert oder wieder verwirft.

_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

Ich muss ja an irgendeiner Stelle auf dem Schlauch stehen oder etwas falsch machen.

Ich hab das jetzt abgeändert das mein Model im ViewModel abgeändert wird und das jeweilige PropertyChanged Event ausgelöst wird.


public PreparationOfPreparationIdModel  PreparationOfPreparationId
            {
                get { return _PreparationOfPreparationId; }
                set { _PreparationOfPreparationId = value; Raise(nameof(PreparationOfPreparationId)); }
            }

public string                       TextBoxEditorOfDistributionSamplingText
                {
                    get { return PreparationOfPreparationId.EditorOfDistributionSampling; }
                    set { PreparationOfPreparationId.EditorOfDistributionSampling = value; Raise(nameof(TextBoxEditorOfDistributionSamplingText)); }
                }
            


                                            <mah:DateTimePicker Grid.Column="1"
                                                                    HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
                                                                        SelectedDate="{Binding PreparationOfPreparationId.EndOfPreparation.DateTimePickerDateOfDistributionSamplingSelectedDate, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">

                                                <i:Interaction.Triggers>

                                                    <i:EventTrigger EventName="GotMouseCapture">

                                                        <i:InvokeCommandAction Command="{Binding PreparationOfPreparationId.EndOfPreparation.DateTimePickerDateOfDistributionSamplingGotMouseCaptureCommand}"/>

                                                    </i:EventTrigger>

                                                </i:Interaction.Triggers>

                                            </mah:DateTimePicker>



Allerdings aktualisiert er nach Änderung des Models eben nicht die einzelnen Propertys im View ab und mein IDataErrorInfo geht damit auch flöten.

Grüße

F
10.010 Beiträge seit 2004
vor 4 Jahren

Und du bist ganz sicher das DateTimePickerDateOfDistributionSamplingSelectedDate in EndOfPreparation in PreparationOfPreparationId auch ein PropertyChanged auslöst?

2.078 Beiträge seit 2012
vor 4 Jahren

Diese Property-Namen sind eindeutig zu lang ...
Man schreibt im ViewModel auch nicht in den Namen, für welches Control das ist, sondern nur, was der Wert bedeutet und reduziert dann das weg, was z.B. anhand des Klassennamens schon eindeutig ist.

Wegen der Model-Property:
Die würde ich entfernen, das verleitet dazu, die zu nutzen und die eigentlich sinnvollen Properties daneben zu umgehen.

Und genau das tust Du nämlich auch:
Du bidest an die Property vom Model, nicht vom ViewModel und das Model kümmert sich vermutlich nicht um das PropertyChanged.

Daher, Fautregel:
Die View sollte nix von dem Model sehen können, das bleibt private oder wird gar nicht erst gespeichert.

Wenn Du es nicht speicherst, dann steckst Du die Model-Instanz von außen in die Speichern/Laden-Methoden rein. Das ViewModel hat dann für jede Property normal ein private Field, wo jeder Wert drin steht und die Speichern/Laden-Methoden arbeiten dann damit und mit dem Model.
Das hat auch den Vorteil, dass Du einen InMemory-Zustand hast, den Du leicht rückgängig machen kannst.

public class MyViewModel
{
    private MyModel _lastModel;
    private int _field1;
    private string _field2;

    public int Field1
    {
        get => _field1;
        set { _field1 = value; Raise(nameof(Field1)); }
    }
    public int Field2
    {
        get => _field2;
        set { _field2 = value; Raise(nameof(Field2)); }
    }

    public void Load(MyModel model)
    {
        Field1 = model.Field1;
        Field2 = model.Field2;
        _lastModel = model;
    }
    public void Save(MyModel model)
    {
        model.Field1 = Field1;
        model.Field2 = Field2;
        _lastModel = model;
    }
    public void Reset()
    {
        // Sofern notwendig...
        // Wenn nicht, dann diese Methode und "_lastModel" entfernen.

        if (_lastModel != null)
            Load(_lastModel);
    }
}
_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

Funktioniert super, danke.

_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

Wäre es semantisch falsch wenn ich im Set zusätzlich auch noch in mein Model schreibe und das quasi nicht erst über eine extra Funktion wie Save() handhabe?

Weil ich in meinem IDataErrorInfo direkt mit den Models arbeite, weil ich dort ja zuvor auch immer die Daten aktuell hatte und das jetzt dann erst der Fall ist wenn ich die Save() Funktion aufgerufen habe.

Beispiel :

    public int Field1
    {
        get => _field1;
        set { _field1 = value; model.Field1 = value; Raise(nameof(Field1)); }
    }
16.807 Beiträge seit 2008
vor 4 Jahren

Musst Du selbst wissen.

Findest es ne gute Idee, wenn automatisch gespeichert wird?
Die meisten Anwender finden das nicht so toll.

Zusätzlich:
Wenn Du Mechanismen wie Reactive Extensions hast, um Modelle/Änderungen zu propagieren, dann informierst Du dauernd das "System" über änderungen - und alle darauf folgende Events und deren Event Handler.
Meistens will man das nicht.

_
_Cashisclay Themenstarter:in
277 Beiträge seit 2014
vor 4 Jahren

Naja so gesehen würde er nicht permanent speichern bzw. er würde sie zwar immer ins Model schreiben, aber das Model schreibt nicht bei jeder Änderung in die Datenbank.

Mich persönlich würde es aktuell nicht stören, ich würde es sogar begrüßen.

Frag nur nach bevor ich in 2 Wochen irgendein Problem habe und mir jeder sagt was ist das denn für ein Müll 🤔

2.078 Beiträge seit 2012
vor 4 Jahren

Natürlich kannst Du das machen, dann brauchst Du auch das private Feld nicht.

Du hast dadurch viele Stellen, die sich um Model-Instanzen kümmern müssen, z.B. musst Du dich dann auch darum kümmern, dass dein ViewModel damit umgehen kann, dass es plötzlich ein neues Model gibt, weil Du von der DB neu geladen hast. Außerdem könntest Du kein ViewModel ohne Model haben, Du kettest dich also für alle Abläufe an das Model-Layer, oder Du bringst den ViewModels bei, wie sie sowohl mit als auch ohne Model arbeiten können - wieder mehr Komplexität.

Lässt Du die Instanz raus, musst Du zwar eine Stelle schaffen, die zum Model das ViewModel finden und dann Laden/Speichern kann, allerdings ist das dann die einzige Stelle. Alle ViewModels sind im Grunde nur ein UI-Zustand, die keinerlei für die DB relevanten Objekte enthalten und daher sehr einfach getrennt vom Rest erstellt oder entfernt werden können.

Glaub mir, ich hab das auch schon ein paar Mal gemacht und jedes Mal bereut 😄
So Load- und Save-Methoden sind genau zwei Methoden, mehr nicht. Wenn man es abstrahieren will, kann man auch Mapper-Frameworks ausprobieren.
Wenn Du die Model-Instanz im ViewModel hast, reicht das nicht mehr aus, das ViewModel wird komplexer. Außerdem lässt sich ein Methoden-Aufruf viel leichter abstrahieren, als ein Konstruktor-Parameter 😉