Laden...

Wie trennt man Verantwortlichkeiten in WPF?

Erstellt von Spooner8 vor 2 Jahren Letzter Beitrag vor 2 Jahren 2.042 Views
S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren
Wie trennt man Verantwortlichkeiten in WPF?

Hallo zusammen

Ich bin an einem Projekt (Inhalt: C# / WPF / SQL / XML) und habe dort einen Export gemacht in ein XML. Das hat eigentlich ganz gut funktioniert und gestern habe ich einige Stunden programmiert und später mal allgemein wieder ein Testlauf gemacht. Plötzlich funktioniert das Serializen in die XML nicht mehr, da er mir sagt ich habe einen Zirkulären Verweis in der einen Klasse.
Ich habe mich nun dumm und dusselig gesucht und verzweifle fast. Ich weiss echt nicht wie, wo oder wann ich den rein programmiert haben soll, da ich eigentlich an genau den Klassen gar nichts gemacht habe den ganzen Tag...

Nun die Frage. Wie finde ich diesen Zirkulären Verweis am einfachsten?
Ich bin nicht mit allen möglichkeiten von Visual Studio vertraut, da ich noch nicht so lange programmiere. Ich weiss nur aus welcher Klasse der Verweis angeblich kommen soll =(

Kurz zu der Klasse die Serialized werden sollen:

  • Klasse Calculation wird Serialized und diese beinhaltet im Grunde nur 2 Klassen:
  1. Eine "Headinformation" Klasse die die Kopfdaten der Kalkulation enthält. (Kunde, Nummer, Kommission etc.)
  2. Eine Klasse mit den "Artikeln" welche als ObservableCollection übergeben wird.
    2.1 Die Klasse mit den Artikeln hat eine weitere ObservableCollection mit "Subartikeln"

Das heisst der Artikel ist z.B. ein Schranke auf z.B. 4 Meter und dieser Artikel beinhaltet jeweils eine ObservableCollektion mit den Bestandteilen.
z.B. 1 x Schrankteil 1.5 Meter / 2 x Schrankteil 1 Meter und 1 x Schrankteil 0.5 Meter

Die Headinformation und auch die "Artikelklasse" funktionieren soweit. Er kommt mit dem Zirkulären Verweis, sobald er das erste Element der "Subartikel" Klasse anlegen will.

Wenn ich den Fehler anschaue und in den Details die ganzen Einträge sehe wird mir Sturm, da ich keine Ahnung habe wo ich darin suchen muss woher genau der Fehler kommt.
Wenn mir da jemand helfen könnte wäre ich sehr dankbar. Ich weiss echt nicht mehr weiter.

Liebe Grüsse Swen

2.079 Beiträge seit 2012
vor 2 Jahren

Vorweg:
ObservableCollection?
Die braucht man bei XML-Serialisierung normalerweise nicht, das deutet eher auf ein Vermischen der Schichten hin: View und Data

Plötzlich funktioniert das Serializen in die XML nicht mehr, da er mir sagt ich habe einen Zirkulären Verweis in der einen Klasse.

"Plötzlich" ändert sich sicher nichts 😉
Also hast Du irgendwas zwischen diesem "Plötzlich" und dem letzten funktionsfähigen Test gemacht, was zu dem Fehler führt.

Die Quelle des Fehler sollte aber auch in der Exception stehen - ggf. InnerException?
Darüber hinaus bleibt nur: Suchen. Oder Du kommentierst einzelne Teile aus und guckst, was passiert.
Es hilft auch, ein Klassendiagram zu zeichnen (ich glaube, Visual Studio kann sowas auch generieren), da fallen zirkuläre Referenzen schneller auf.
Aber "Subartikel" klingt, als könnte es eine Referenz auf "Artikel" haben.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Hi Palladin007

Erstmal danke für deine super schnelle Antwort.

Das ist mein erstes Projekt und MVVM muss ich gestehen, habe ich noch nicht begriffen und werde ich später mal genauer anschauen. Es ist also noch weit weg von "guter" programmierung, aber irgendwo muss man mal anfangen =)
Ich habe ObservableCollections damit sich mein Datagrid ordentlich aktualisiert und ich serialisiere dann diese Collection später. Ich wusste nicht dass das nicht gut ist und es hat bisher ja auch gut funktioniert. Also ich mache es so, dass ich aus meinem Window das Grid worin die Header - Informationen stehen an die Klasse "Headinformation" gebunden habe und die ObservableCollection welche an das Datagrid gebunden ist beinhaltet die "Artikel" Klasse. Diese beiden schreibe ich dann in die Klasse "Calculation" welche ich dann serialisieren will.

Du hast natürlich absolut recht, irgendwas muss ich gemacht haben, das es nicht mehr geht, aber ich weiss beim besten Willen nicht was das sein soll.

Die InnerException sagt, dass ein zirkulärer Bezug bei der "Artikel" Klasse gefunden wurde. Das wiederum müsste ja bedeuten, dass wenn er abbricht sobald er das das erste mal eine "Subartikel" Klasse schreibt, dass diese wieder zurück verweisen würde auf die "Artikel" Klasse. Oder verstehe ich da was falsch? Kann es von wo anders kommen als einer Eigenschaft der "Subartikel" Klasse? Denn da habe ich keine Eigeschaft der "Artikel" Klasse... Aus einer Methode kann es ja nicht stammen, da die nicht relevant sind für das XML oder?

Evtl. versuche ich mal raus zu finden wie Visual Studio ein Klassendiagramm zeichnet um da mal genauer nach zu schauen.

Gruss Swen

2.079 Beiträge seit 2012
vor 2 Jahren

Das hat mit MVVM erst mal nichts zu tun, sondern einer simplen Schichten-Architektur. Serialisierung gehört in die Daten-Schicht, MVVM beschäftigt sich ausschließlich mit der UI-Schicht.
Aber eine ObservableCollection ist technisch kein Problem, Du verbaust dir damit nur jede Möglichkeit, irgendwas in den UI-Daten anders zu behandeln, als in der Daten-Schicht, was - meiner Erfahrung nach - aber in ungefähr jedem Projekt nötig wird 😉 Außerdem hast Du durch das Vermischen von UI und Daten eine Abhängigkeit, die dem einen oder anderen Entwickler schon das Genick gebrochen hat - symbolisch gemeint.

Aus einer Methode kann es ja nicht stammen, da die nicht relevant sind für das XML oder?

Die Methoden sind für alles relevant.
Sie definieren auch die Daten, die Du serialisieren willst und da Du alles in einem Tops wirfst (s.o.), hat eine zirkuläre Abhängigkeit in einer Methode natürlich direkte Auswirkungen auf die Daten, die Du serialisieren willst. Stichwort: Referenz

Also wenn Artikel A einen Sub-Artikel B hat, der einen Sub-Artikel A hat und Artikel A zufällig die gleiche Instanz ist - wo hört er dann mit der Serialisierung auf? A hat B hat A hat B hat A hat B hat A hat B hat A hat B hat A ...

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Der Fehler ist gefunden!!!

Ich war so doof und habe bei den Variablen eine "Artikel" Klasse instanziiert um ein anderes Problem zu lösen und habe das in der #Region übersehen weil ich die Variablen meist zugeklappt habe =/

Jetzt geht zwar alles wieder, aber beim Deserialisieren habe ich dann wieder das "Problem", dass er bei den "Subartikel" Klassen nicht mehr weiss zu welcher "Artikelklasse" er gehört und dann später in der Verwendung ein falsches Verhalten erzeugt. Darum habe ich in der "Subartikel" Klasse eine "Artikel" Klasse instanziiert und nach dem Deserialisieren habe ich ihm quasi gesagt foreach(Subartikel sub in Artikel) --> sub.Artikel = Artikel, damit wer weiss zu wem er gehört... Da muss ich einen anderen Weg finden, da ich damit eine Methode angesprochen habe um alle Daten korrekt weiter zu verarbeiten. Diese Methode hat 2 wichtige Eigeschaften bereinigt um sie dann im Datagrid wieder korrekt an zu zeigen bzw. dass er auch weiss wohin er gehört.

Somit ist 1 grosses Problem gelöst und ein mittleres wieder entstanden, aber immerhin weiss ich jetzt wo weiter machen =)

Danke nochmal und einen guten Start in die neue Woche dann.

Gruss Swen

2.079 Beiträge seit 2012
vor 2 Jahren

habe das in der #Region übersehen weil ich die Variablen meist zugeklappt habe

Ich hasse Regions und Du solltest sie dir ganz schnell wieder abgewöhnen - einen ganz wesentlichen Grund hast Du gerade selber erkannt 😉
Es gibt kein Grund für Regions, die man nicht mit einem besseren Code-Design lösen könnte und dabei muss man noch nicht mal fit mit Architekturen und Patterns sein.
Naja - einen Grund gibt es: Chaos-Projekte, die man nicht aufräumen kann, da können Regions helfen, nicht völlig im Chaos verloren zu gehen.

Und was deine Beschreibung angeht: Ich kann nicht folgen.
Für mich klingt das so, als würdest Du einen Dirty-Workaround um andere Dirty-Workarounds bauen, weil Du irgendwann am Anfang etwas falsch aufgebaut hast.
Eine Vermutung: Datenklassen sollten keine Logik haben, sie sollten nur die Daten beschrieben - was häufig von der Beschaffenheit der Logik abweichen kann.
Ein mögliche Lösung: Reine Datenbeschreibung und eine Klasse, die das Lesen und Schreiben der Daten realisiert, sie arbeitet ausschließlich mit Listen dieser reinen Daten-Klassen. In der UI liest Du darüber die Daten und erstellst auf der Basis dann deine ViewModels (wenn wir bei MVVM bleiben), auf der Ebene kannst Du dann auch Beziehungen zwischen Klassen erstellen, die es in den Daten nicht gibt oder nicht geben darf.

Wenn z.B. ein Artikel viele Sub-Artikel haben kann, die wiederum als normale Artikel aufgeführt sind:


<Articles>
    <Article Id="1" Name="Computer">
        <SubArticles>
            <ArticleRef Id="2" />
            <ArticleRef Id="4" />
        </SubArticles>
    </Article>
    <Article Id="2" Name="Graphics Card">
        <SubArticles>
            <ArticleRef Id="3" />
        </SubArticles>
    </Article>
    <Article Id="3" Name="GPU">
    </Article>
    <Article Id="4" Name="CPU">
    </Article>
</Articles>

Die Daten-Klassen sehen dann exakt genau so aus.
Auf UI-Ebene nimmst Du die Artikel-Liste und baust mit einem Dictionary ein ID-Artikel-Mapping auf.
Wenn Du mehr Infos zu den Sub-Artikeln brauchst, schau in dem Sub-Artikel nach und hole dir den tatsächlichen Artikel anhand der ID. Oder Du weist direkt die Referenzen zu, je nachdem.
Beim Speichern muss das ganze natürlich umgekehrt ablaufen.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Ich verstehe natürlich wieso du #Regions nicht gut findest und glaub mir ich kann meinen Code nicht wirklich schöneer Designen.
Ich habe nicht #Regions weil ich aufräumen will, sondern weil ich zu faul zum Scollen bin haha. Ich habe im Grunde immer die selben 4 #Regions damit ich normal nicht zu weit suchen muss. Das sind "Variablen", "Eigenschaften", "Konstruktoren" und "Methoden". Ich bin selbst schuld, dass ich eine Klasse public instanziiert habe und sie zu den Variablen oben geschoben habe.

Zum anderen Thema. Das habe ich etwas ungünstig formuliert. Natürlich weiss die "Subartikel" Klasse zu welcher "Artikel" Klasse sie gehört, sie steht ja dort in der Collection drin und ist klar zugewiesen. Es ist nur so, dass beim Deserialisieren mit jeder Eigeschaft die beschrieben wird, auch die "OnPropertyChanged" ausgelöst wird und bei gewissen Eigeschaften sind dabei auch weitere Methoden angesprochen. Prozesstechnisch muss das so sein. Die wirklichen "Daten" - Klassen kommen aus der SQL und haben selbstverständlich keine Logig nachträglich programmiert.
Oder meinst du, dass die Klassen die später vom Datagrid gebunden werden ebenfalls keine Methoden enthalten sollten? Wie gesagt will ich nach diesem Projekt das Thema MVVM mal genauer anschauen und verstehen, aber ich habe bisher im Window selbst nur Logik drin, wenn es Buttons beinhaltet. Der Rest ist bereits mit Bindings geregelt.

Mein XML sieht nun genau so aus wie es sein soll. (Wie dein Beispiel)

Ich glaube mein aktuelles "Problemchen" ist nicht wie erst gedacht, dass sich die Klassen nicht zugeordnet fühlen, sondern dass beim Deserialisieren einfach gewisse Methoden ausgelöst werden wovon eine halt ein Feld temporär ändert. Sobald ein weiterer Subartikel später hinzugefügt wird, korrigiert sich das von selbst. Nun muss ich das nur noch direkt nach dem Deserialisieren machen. Das finde ich noch raus, woher dieser Wert genau kommt und vor allem wann.

Du scheinst mir mit MVVM fit zu sein. Wenn du einen guten Link zu einem Video (vorzugswiese deutsch) kennst, bin ich daran interessiert. Vor 2 Jahren hatte ich das mal angeschaut und das war mir zu wenige für Anfänger erklärt, darum habe ich nach einer Auszeit von den vielen Infos einfach mal angefangen zu programmieren. So lernt man auch sehr viel mit dem ganzen Googeln und Youtube + Videokurse die ich gemacht habe.

Mit dem Projekt habe ich mich auch ganz leicht übernommen. C#, WPF, XML, Exel, SQL --> alles nötig dafür aber zu viel auf einmal =)

16.834 Beiträge seit 2008
vor 2 Jahren

Ich habe nicht #Regions weil ich aufräumen will, sondern weil ich zu faul zum Scollen bin haha.

Das ist ein Grund, wieso #Regions evil und in den aller meisten (Open Source) Repositories und mittlerweile auch Firmen durch Guidelines strikt verboten sind.
Klassen sollten klein sein, dann muss man auch nicht ewig Scrollen. Musst Du ewig Scrollen, dann kann das ein Zeichen für ein Fehldesign sein.

MVVM Artikel: [Artikel] MVVM und DataBinding
Videos kannst selbst bei YouTube suchen; bei Videos und Büchern muss jede:r für sich seine Quelle suchen, wie man am besten davon lernt.

Deserialisieren einfach gewisse Methoden ausgelöst werden wovon eine halt ein Feld temporär ändert.

Deswegen sollten Datenklassen "dumm" sein und keine Methoden haben.
Diese sollten strikt getrennt sein von Logik und View.

Die Namings sind oft:

  • *Entity für Datenmodelle, sofern sie in einem Storage (XML, DB..) liegen
  • *Model für logische Modelle
  • *ViewModel für ViewModels
2.079 Beiträge seit 2012
vor 2 Jahren

glaub mir ich kann meinen Code nicht wirklich schöneer Designen.

Das stimmt garantiert nicht - schon das Entwirren der verschiedenen Schichten wäre ein großer Punkt.

Es ist nur so, dass beim Deserialisieren mit jeder Eigeschaft die beschrieben wird, auch die "OnPropertyChanged" ausgelöst wird und bei gewissen Eigeschaften sind dabei auch weitere Methoden angesprochen.

sondern dass beim Deserialisieren einfach gewisse Methoden ausgelöst werden wovon eine halt ein Feld temporär ändert. Sobald ein weiterer Subartikel später hinzugefügt wird, korrigiert sich das von selbst.

Fällt dir nicht auf, wie Du die ganze Zeit gegen die Nachteile deiner Daten-Logik-Misch-Konstruktion ankämpfst?
Es hat einen Grund, warum jedem halbwegs erfahrenen Entwickler bei sowas das Gesicht einfriert. ^^

Der Rest ist bereits mit Bindings geregelt.

Das Binding alleine ist allerdings kein MVVM, sondern nur ein Werkzeug. Du kannst auch alles perfekt mit Bindings aufbauen und trotzdem grandios an MVVM vorbei schießen.

Binding ist die Trennung in drei Bereiche: Model, ViewModel und View.
Model
Dumme Transfer-Klassen (ich nenne sie oft "*Dto" für "DataTransferObject" oder stumpf "Model"), die die Daten je Situation passend aus der Business-Schicht zum ViewModel transportieren. Bei kleinen Projekten sieht man auch häufig, dass das direkt die Daten-Klasse aus der Daten-Schicht (meist "*Entity" genannt) sind, ob das gut oder MVVM-konform ist, kann man drüber streiten.
View
Reine View ohne Logik. Einzige Ausnahme: Reine UI-Logik, z.B. Fokus-Kontrolle oder Farb-Änderungen. Bei WPF geht das meist größtenteils in XAML.
ViewModel
Verbindung zwischen Model und View. Das ViewModel sieht häufig anders aus als das Model, übernimmt die Daten aus dem Model und implementiert so Automatismen, dir für die UI notwendig sind. Das könnte z.B. sein, dass die Änderung eines ausgewählten Artikels dafür sorgt, dass bei dem zuvor ausgewählten Artikel die Änderungen rückgängig gemacht werden.
Wenn das ViewModel Daten laden soll, bekommt es X Models und baut sich passende ViewModels dazu.
Wenn das ViewModel Daten speichern soll, nimmt es X ViewModels, baut die Models dazu und gibt sie weiter.
Außerdem ruft das ViewModel natürlich Business-Logik auf und zeigt ggf. Ergebnisse oder Fehler an.

Und dazu gibt die Business-Schicht mit Business-Services. Die bekommen die DTOs (oder geben sie zurück), setzen die Logik um, die nicht mit der UI zu tun hat und arbeiten ggf. mit den Entities und den tatsächlichen Daten. Bei kleinen Projekten wird das auch gerne mal weg gelassen, aber wie gesagt: Kann man drüber streiten.

Ich habe z.B. häufig drei Models pro ViewModel: ArticleViewModel, ArticleDto, ArticleCreationDto. ArticleUpdateDto. Die sind alle verschieden, vielleicht nur ein bisschen, aber alle perfekt auf das zugeschnitten, wofür ich sie brauche. Z.B. hat das ArticleDto alle Daten, das ArticleCreationDto hat keine ID und auch keine generierten Daten und das ArticleUpdateDto hat nur Daten, die man ändern können muss.
Und darunter gibt's dann noch eine ArticleEntity, die nochmal anders aussieht, damit zum richtigen Datenbank-Schema passt und EFCore damit arbeiten kann - oder in deinem Fall XML.

Wenn du einen guten Link zu einem Video (vorzugswiese deutsch) kennst, bin ich daran interessiert.

Kenne ich nicht, ich hab's auf die "harte Tour" gelernt, also zig Quellen und Meinungen lesen und zig verschiedene Wege ausprobieren, bis ich den Weg gefunden habe, der sich für mich bewehrt hat. Andere machen das sicher nochmal etwas anders, so hat jeder seinen Stil, aber solange die wichtigen Grundregeln eingehalten werden, funktioniert auch MVVM_

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Musst Du ewig Scrollen, dann kann das ein Zeichen für ein Fehldesign sein.

Gebe ich dir recht. Es gibt aber nunmal auch Klassen wie meine die sehr viele Eigenschaften haben müssen, da es keinen Sinn macht die Klasse in viele Stücke zu zerkleinern wenn ohnehin die selbe Klasse diese Eigeschaften braucht um überhaupt funktionieren zu können.

Deswegen sollten Datenklassen "dumm" sein und keine Methoden haben.

Die Datenklassen kommen wie gesagt aus der SQL und da habe ich keine Logik ergänz oder ähnliches. Die sind und bleiben Dumm.

Fällt dir nicht auf, wie Du die ganze Zeit gegen die Nachteile deiner Daten-Logik-Misch-Konstruktion ankämpfst?
Es hat einen Grund, warum jedem halbwegs erfahrenen Entwickler bei sowas das Gesicht einfriert. ^^

Ja das fällt mir auf und ich werde daran arbeiten. Wie gesagt bin ich "Anfänger" und um gute Tips froh. Ich habe versucht in den Views keine Logik ausser die nötigsten zu verwenden. DAs ist mir noch nicht ganz gelungen, aber der Ansatz stimmt.

Also wenn ich dich richtig verstanden habe Palladin007 hätte ich die "Artikel" Klasse einmal also "Dumme" Klasse nur mit den Eigeschaften und ohne INotifyPropertyChanged erstellen sollen und einmal die quasi selbe Klasse aber mit den Methoden etc. die dann auch die Berechnungen durch führt.
Es ist eben so, dass mein Artikel an sich nicht so einfach ist wie in einem ERP wo man sagt Artikel mit Nur. 12345 hat den Namen "Schokoriegel" und einen Preis X.
Er ist leider sehr komplex und muss mti jeder Änderung an einer Eigenschaft mit einer neuen Kalkulation auf seine Werte reagieren. Darum habe ich diese Klasse auch an die View gebunden um jede Eingabe in Echtzeit als Preis dar zu stellen. Aber so wie ich das verstehe, habe ich da den falschen Weg gewählt. Mache ich nächstes Mal anders oder versuche es zumindest.

2.079 Beiträge seit 2012
vor 2 Jahren

Die Datenklassen kommen wie gesagt aus der SQL und da habe ich keine Logik ergänz oder ähnliches. Die sind und bleiben Dumm.

Die XML-Klassen auch Daten-Klassen, nur dass sie nicht für ein SQL-Mapping sondern für ein XML-Mapping gedacht sind.
Die haben schon eine ganz entscheidende Aufgabe: Sie definieren, wie die Daten aussehen. Mehr Aufgaben sollte sowieso kaum etwas haben.

Es gibt aber nunmal auch Klassen wie meine die sehr viele Eigenschaften haben müssen, da es keinen Sinn macht die Klasse in viele Stücke zu zerkleinern

Das kommt sicher vor, aber ich behaupte, dass es eher selten wirklich keinen Sinn ergibt, die Daten aufzusplitten.
Auf Daten-Ebene (XML und SQL) gebe ich dir Recht: Nicht aufteilen, denn wie gesagt: Die sollen die Daten beschreiben - und zwar exakt so, wie sie auch wirklich aussehen sollen.
Auf Business- bzw. View-Ebene (Model und ViewModel) kann das aber durchaus nützlich sein, wenn Du die Daten inhaltlich trennst, einfach um es übersichtlicher zu machen.

Und Eigenschaften sollten auch generell nicht so viel tun. Die dürfen ein PropertyChanged-Event werfen, Daten prüfen, etc. - aber auf keinen Fall aufwäbdige Logik, Du verlierst sonst nur den Überblick.
Dann mach die Properties lieber readonly (oder private set) und biete eine Methode an, die irgendeine komplexere Aktion durchführt und dabei den eigenen Zustand aktualisiert
Und siehe da, Du hast die vielen komplexen Properties auf eine lange Liste langweiliger Auto-Properties (oder wie auch immer die NotifyPropertyChanged-Implementierung aussieht) reduziert. Wenn das nicht geht, weil zu komplex oder es würden zig verschiedene Methoden bei raus kommen, dann vermischst Du vermutlich wieder Aufgaben.

hätte ich die "Artikel" Klasse einmal also "Dumme" Klasse nur mit den Eigeschaften und ohne INotifyPropertyChanged erstellen sollen und einmal die quasi selbe Klasse aber mit den Methoden etc. die dann auch die Berechnungen durch führt.

Ja und nein. "Berechnungen" klingt nach etwas, was man wunderbar auslagern kann und - weil wichtige Business-Logik - auch sollte.
Du hast dann die dumme Models, die quasi dummen ViewModels und einen Business-Service, der z.B. den Preis berechnen kann.
Das dumme Model liefert die Daten, das ViewModel bekommt sie, füllt die eigenen Properties und fragt beim Service nach dem Preis. Wenn sich im ViewModel etwas ändert, fragt es wieder nach dem Preis und so weiter. Zwischen ViewModel und Service kann wieder ein anderes Model liegen, zumindest gehe ich davon aus, dass eine Preis-Berechnung nicht alle Daten (wie z.B. den Namen) braucht und vermutlich unterscheiden sich die benötigten Daten je Berechnung.
Das ganze machst Du dann für jede nötige Berechnung und die verbleibende Komplexität im ViewModel ist ein simples: Welche Property-Änderung erzwingt welche Berechnungen?
Das hat dann auch gleich den schönen Nebeneffekt, dass Du den Service leicht UnitTesten kannst und - um beim Beispiel zu bleiben - bei Preisen lohnen die sich definitiv.

Wie gesagt: Das ViewModel spielt Bindeglied zwischen Business und View, mehr nicht.
Im ViewModel hat keine komplexe Logik etwas verloren. Dort gehört so Logik rein wie "Welcher Dialog muss bei Ergebnis X angezeigt werden und welcher bei Y?"
Das ViewModel übernimmt das, was die Business-Services nicht können, weil sie nichts mit der UI tun dürfen.

Und ja, das INotifyPropertyChanged ist (meistens - kann ja auch anders genutzt werden) auch UI, denn dadurch greifst Du direkt in UI-Abläufe ein und sowas wie der aktuelle Thread wird plötzlich ein brandgefährliches Thema.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Danke für deine wirklich konstruktiven Antworten, das hilft mir immer ein Stück weiter im Verständnis.

Bei meinen Eigenschaften der "Artikel" Klasse sind im Grunde nur die Setter mit der OnPropertyChanged() angesprochen. Es gibt diverse Eigenschaften die im selben Zug dann eine Methode ansprechen. Das müsste ja soweit auch in Ordnung sein, ausser das diese Methoden vielleicht besser in ein Model ausgelagert werden können/sollten.

Ich habe eine Frage bezüglich OnPropertyChanged:
Wenn ich jetzt eine View mache und dem stumpf ein ViewModel zuweise welche in meinem Fall folgende Properties haben wird:

  • Headinformation Head
  • ObservableCollection<Artikel> ItemList

Beide Properties sind geladen aus einem Model und dessen Properties werden dann mit Binding z.B. ItemList.Name an ein Feld gebunden.
Nun ist es ja so, dass OnPropertyChanged nur dann bei der ObservableCollection ausgelöst wird, wenn ein Eintrag hinzu kommt oder gelöscht wird, nicht aber wenn eine Property innerhalb der Artikel-Klasse mit dem Binding geändert wurde.
Wenn ich nun im Model selbst wiederum INotifyPropertyChanged implementieren müsste ist das doch wieder falsch dort, da das ins ViewModel gehört.
Wie muss ich also mit diesem Binding das OnPropertyChanged der Collection auslösen wenn ein Wert vom Model geändert wird ohne das ich im Model selbst nochmal INotifyPropertyChanged implementiere? Oder muss ich das wirklich so machen? Dann wäre das doch das ViewModel vom ViewModel und das Model muss nochmal zusätzlich angelegt sein =/ das verwirrt mich noch etwas.

F
10.010 Beiträge seit 2004
vor 2 Jahren

Indem du ein ArtikelVM erstellst.

16.834 Beiträge seit 2008
vor 2 Jahren

Hilf Dir selbst mit einem besseren Naming die Übersicht zu behalten. Du hast offenbar immer noch einen riesen Spaghetti-Code.

Eine Entität gilt als Datenquelle und hat nichts anderes als Eigenschaften.


public class ArtikelEntity
{
   public Guid Id { get; set; }
   public string Name { get; set; }
   public decimal Preis { get; set; }
}

// Kurzschreibweise als record
public record class ArtikelEntity(Guid Id, string Name, decimal Preis);

Eine Entität kennt niemals Business Logik oder UI.

Ein Model gilt als Umgang für die Business Logik, kann Eigenschaften haben, die sich vom Modell unterscheiden und kann auch Methoden haben.
Ein Modell kennt aber niemals die UI-Technologie, hat daher auch kein OnPropertyChanged


public class ArtikelModel
{
   public Guid Id { get; set; }
   public string Name { get; set; }

   public int ItemsInLager { get; set; }
}

Modelle kann man auch wegabstrahieren, braucht dann aber eine anderen Struktur, zB. anhand von Projections statt Business Modells zusammen mit einer Service-Architektur.

Ein ViewModel repräsentiert den Inhalt einer View und hat eine Abhängigkeit zur UI Technologie, also WinForms, ASP.NET, WPF...
Hier darfst / sollst Du im Falle von WPF auch Abhängigkeiten zB in Form von OnPropertyChanged darstellen.


public class ArtikelViewModel : INotifyPropertyChanged
{
   private Guid _id;
   public Guid Id
   {
      get { return _id; }
      set
      {
            if (value != _id)
            {
                  _id= value;
                  OnPropertyChanged(nameof(Id));
            }
       }
   }
}

Das wurd Dir nun schon so mehrfach gesagt und steht auch im Artikel [Artikel] MVVM und DataBinding
Du musst das so machen, wie die Technologie das vorsieht, egal ob das nun Serialisierung ist, das dumme Datenmodelle will oder eben WPF, das ViewModels will.
Und wenn Methoden beim Serialisieren aufgerufen werden, dann serialisierst Du in der falschen Code-Verantwortlichkeit.

PS: sowas geht immer nur mit Eigenschaften, und nicht mit Feldern!
Felder sind nicht für Bindungen gedacht.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Indem du ein ArtikelVM erstellst.

Dann ist es also doch so, das es auch mal ein ViewModel vom ViewModel gibt, da deine Antwort bedeutet ich soll ein VieModel von der "Artikel" Klasse machen die dann im ViewModel vom Window (View) als ObservableCollection steht.

Hilf Dir selbst mit einem besseren Naming die Übersicht zu behalten

Die hier geschriebenen "Namen" sind nur beispielhaft damit ich mich so verständlich wie möglich ausdrücken kann. Ich verwende natürlich ausschliesslich englische Bezeichnungen die auch nicht abgekürzt sind.

Das wurd Dir nun schon so mehrfach gesagt und steht auch im Artikel [Artikel] MVVM und DataBinding

Das weiss ich und ich versuche ja nur das ganze zu verstehen, darum frage ich konkrete Dinge vor denen ich stehen werde wenn ich das so baue. z.B. ViewModel vom ViewModel ob das so gemacht wird oder ob ich das falsch verstanden habe. Ich bin ein Mensch der alles gut umsetzen kann wenn ich es nicht nur theoretisch verstanden habe, sondern auch weiss wie in bestimmten Fällen in der Praxis umgegangen werden muss.

Ich werde mein Programm neu aufsetzen und umstrukturieren. Vieles kann ich ja auch kopieren und anderes muss ich ganz neu machen.
Es wird schon langsam zu gross um mit meinem "Spaghetticode" weiter zu machen 😉
Einiges von euren Tipps sind auch schon sauber und richtig umgesetzt, vieles aber auch noch nicht und das werde ich besser machen um die Übersicht zu behalten.

Ich danke allen für Ihre Hilfe, es ist sehr toll so schnell Antworten zu bekommen.

16.834 Beiträge seit 2008
vor 2 Jahren

Eine View hat immer nur ein ViewModel, daher gibt es für eine Darstellung auch nicht ViewModel vom ViewModel.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Gut das ist doch eine klare Aussage mit der ich arbeiten kann. Also kein VM vom VM. Das bringt dann aber die oben gestellte Frage wieder in den Vordergrund.
Im VM habe ich ja Instanzen der Klassen Headinformation und die Collection von Artikeln. Nur beim hinzufügen oder löschen eines Artikels in der Collection wird die View aktualisiert. Da ja nur dann der Setter davon angesprochen wird. Nicht aber bei einer Wertänderung von einem Artikel in der Collection welche ja an das Datagrid gebunden ist.

Wäre das über ein Event vom Datagrid ab zu feuern? z.B. Gibt es da ja sowas wie TargetValueChanged oder so ähnlich.
Damit könnte ich im ViewModel manuel das OnPropertyChanged der Collection auslösen. Soweit meine Idee.

Wäre das korrekt?

Sorry ich hab da echt ein Knopf.

4.939 Beiträge seit 2008
vor 2 Jahren

Ich glaube, Abt hat sich etwas ungeschickt ausgedrückt.
Man kann schon verschiedene ViewModels miteinander kombinieren, also z.B.


class MainViewModel
{
  public ObservableCollection<SubViewModel> Items { get; private set; }
}

bzw. bei dir ObservableCollection<ArticleViewModel> Articles.

Das MainViewModel ist dabei direkt an die View gebunden und Items/Articles z.B. an ein enthaltenes DataGrid oder ItemsControl.

16.834 Beiträge seit 2008
vor 2 Jahren

Ich glaube, Abt hat sich etwas ungeschickt ausgedrückt.

Nein, ich habe mich bezogen auf

ViewModel vom ViewModel

Hier steht deutlich "vom", was also eine Vererbung wäre. Und so hab ichs verstanden und darauf geantwortet.
Natürlich wäre aber "ViewModel **mit **ViewModel" möglich.

Das ist ein Unterschied.

4.939 Beiträge seit 2008
vor 2 Jahren

Spooner8 hat aber erklärt, was er genau damit meint (also genau so, wie mein Code):

Dann ist es also doch so, das es auch mal ein ViewModel vom ViewModel gibt, da deine Antwort bedeutet ich soll ein VieModel von der "Artikel" Klasse machen die dann im ViewModel vom Window (View) als ObservableCollection steht.

Ich habe also so geantwortet, damit Spooner8 deine Aussage nicht darauf bezieht und evtl. falsch weiterprogrammiert.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Danke euch beiden. Vom und Mit, ja da kann ein Stolperstein entstehen.
Habe kurz ein Beispiel geschrieben wie ich das im Kopf habe.


Class CalculationViewModel //Wird der DataContext von der View mit einem Datagrid
{
   private ObservableCollection<ArticleViewModel > itemList;

   public ObservableCollection<ArticleViewModel > ItemList
   {
      get { return itemList; }
      set
      {
         if (value != itemList)  
                {  
                    itemList= value;  
                    OnPropertyChanged();  
                } 
      }
   }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}


Class ArticleViewModel //Ist das dann auch als ViewModel zu bezeichnen wenn es in ein anderes ViewModel geladen wird?
{
   private Article name;

   public Article Name
   {
      get { return name; }
      set
      {
         if (value != name)  
                {  
                    name= value;  
                    OnPropertyChanged();  
                    Article.CalculatePrice();
                } 
      }
   }
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}


Class Article
{
   public string Name { get; set; }
   public string Price { get; set; }

   public void CalculatePrice
   {
      blabla
      Price = blabla
   }
}

So ungefähr meinte ich das mit ein ViewModel vom ViewModel oder auch ein VieModel** im** ViewModel.
Wäre das so sauber? Damit würde auf jeden Fall das Datagrid auch bei Wertänderung aktualisiert werden. Weil zu 90% habe ich das jetzt so auch.
Die Methode CalculatePrice würde dann aus dem ArticleViewModel ausgelöst werden wenn sich eine bestimmte Eigenschaft ändert richtig?

16.834 Beiträge seit 2008
vor 2 Jahren

Dein Code zeigt zwei ViewModels, die sich gegenseitig nicht kennen - passt also nicht zu Deiner Frage.
Meinst Du vielleicht


 private ObservableCollection<ArticleViewModel> itemList;

Dann bekommst Du zumindest im CalculationViewModel mit wenn sich Deine ItemList aktualisiert; aber Du musst auch die Event-Informationen aus den ArticleViewModels nach oben weiter reichen.
Dafür gibts zb EventAggregatoren, zB in MVVM Light.

Das gleiche Spiel bei Article in ArticleViewModel .
Du hast nun den Event, wenn sich das Article-Property aktualisiert; aber Du bekommst nichts mit, wenn sich die Eigenschaft von Article ändert.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Dein Code zeigt zwei ViewModels, die sich gegenseitig nicht kennen - passt also nicht zu Deiner Frage.
Meinst Du vielleicht

  
 private ObservableCollection<ArticleViewModel> itemList;  
  

Oh ja, hab ich grade noch korrigiert und eine Frage zur Methodenabfrage angehängt. Danke für den Hinweis =)

T
2.224 Beiträge seit 2008
vor 2 Jahren

@Spooner8
Du hast scheinbar deine Antwort an das Ende des Zitat gepackt.
Solltest du korrigieren, sonst liest sich die Antwort komisch 🙂

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

@Abt

Ich verstehe was du meinst und habe gerade noch bemerkt, dass es im ArticleViewModel wenig Sinn ergibt eine Eigeschaft vom Typ Article zu haben die Name heisst...
das müsste bzw. ist aktuell bei mir ein string.
Stand jetzt ist meine ganze Logik im ArticleViewModel. Das Article Model gibt es stand jetzt nicht aber wenn ich das ArticleModel erstelle, müsste ich im ArticleViewModel sowas machen:


Class ArticleViewModel
{
   private ObservableCollection<ArticleModel> article;

   public ObservableCollection<ArticleModel> Article
   {
      get { return article; }
      set
      {
         if (value != article)
                {
                    article = value;
                    OnPropertyChanged();
                    Article.CalculatePrice();
                }
      }
   }
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

2.079 Beiträge seit 2012
vor 2 Jahren

Bezogen auf den Code in diesem Beitrag: Beitrag #3834228

Wenn ich die Article-Name-Property übersehe, könnte man das so machen, ja.
Dennoch würde ich einiges anders machen:

  1. OnPropertyChanged in eine Basisklasse oder eine Implementierung von einem MVVM-Framework
  2. Die ObservableCollection ohne Setter.
  3. Die Article-Referenz entfernen und stattdessen die Properties einzeln aufführen, dann musst Du auch keine Änderungen in der Article-Referenz selber behandeln
  4. Ich würde mehrere Models nutzen, möglichst viel readonly machen ((und sie Dto nennen)

Punk 1 habe ich nur wegen der Vollständigkeit erwähnt, im Forum würde ich das auch so schreiben, wie Du, um mögliche Verständnisprobleme zu vermeiden.
Punkt 2 ist nicht notwendig, beseitigt aber die Notwendigkeit, neue ObservableCollection-Instanzen beachten zu müssen, für den Fall, dass Du im C#-Code darauf reagieren muss.

Punkt 3:
Du könntest eine Article-Referenz als Variable behalten, um z.B. eine Undo-Funktionalität auf der Basis zu entwickeln.

Wenn die Properties nicht geändert werden können, würde ich mir das ganze OnPropertyChanged-Zeug sparen.
Es kann ja auch ReadOnly-ViewModels geben, die dann kein INotifyPropertyChanged brauchen, also z.B. so:


public class ArticleViewModel
{
    private readonly Article _article;

    public string Name
    {
        get { return _article.Name; }
    }

    // Oder die Kurz-Schreibweise:;
    public string Name => _article.Name;
}

Punkt 4:
Beispiel:


namespace MyProject.Data;

public class ArticleEntity
{
    public int Id { get; }
    public string Name { get; }

    public ArticleDto(ArticleEntity entity)
    {
        Id = entity.Id;
        Name = entity.Name;
    }
}

namespace MyProject.Business;

public class ArticleDto
{
    public int Id { get; }
    public string Name { get; }

    public ArticleDto(ArticleEntity entity)
    {
        Id = entity.Id;
        Name = entity.Name;
    }
}
public class ArticleUpdateDto
{
    public int Id { get; }
    public string Name { get; set; }

    public ArticleUpdateDto(int id)
    {
        Id = id;
    }
}
public class ArticleCreateDto
{
    public string Name { get; }

    public ArticleCreateDto(string name)
    {
        Name = name;
    }
}
public class ArticleCalculationDto
{
    public int Many { get; set; }
    public int Important { get; set; }
    public int Calculation { get; set; }
    public int Values { get; set; }
}

Der Aufbau ist aber - zugegeben - ziemlich aufwändig und wird eigentlich erst bei größeren Projekten nützlich.
Dafür kann ich so aber für jede einzelne Aufgabe (Read, Create, Update) spezifisch steuern, wie die Daten aufgebaut sind.

Im ViewModel hast Du einige Aufgaben:

  1. Daten Laden
    Du bekommst ArticleDto, erstellst damit ArticleViewModels und aktualisierst deine Liste.

  2. Daten ändern
    Für den geänderten Artikel erzeugst Du ein ArticleUpdateDto und gibst ihn an einen Business-Service weiter, der anhand der ID den Artikel sucht, ändert und wieder speichert.

  3. Daten erstellen
    Für einen neuen Artikel erzeugst Du ein ArticleCreateDto und gibst ihn an ein einen Business-Service weiter, der einen Artikel erstellt, speichert und dir ein ArticleDto zurück gibt, den Du dann deiner Liste hinzufügst.

  4. Daten löschen
    Dafür brauchst Du nur die Id.

  5. Berechnung aktualisieren
    Das kannst Du entweder per Button-Klick (nicht automatisch) machen, oder bei jeder Property-Änderung.
    Diese Berechnung macht aber auch nichts anderes, als ein ArticleCalculationDto zu erzeugen und an einen Business-Service weiter zu geben. Der wiederum antwortet dir mit einem Preis oder für komplexe Berechnungen mit einem ArticleCalculationResultDto, wo dann alle Ergebnisse drin stehen.

Du hast dann also fünf Business-Methoden, die laden, ändern, erstellen, löschen und berechnen, alle unabhängig von jeder UI-Komplexität.
Und die Entity-Klasse ist praktisch leer (bis auf die Daten-Properties) und macht keine Zicken beim Serialisieren.
Das ViewModel sieht aber nichts von den Entities, es tauscht Daten immer über die Dtos aus und arbeitet selber mit ViewModels.

Und dann zeichnen sich auch die Grenzen der Schichten genauer ab:
Ich versuche mal eine kleine Tabelle zu skizzieren (Abt, wir brauchen ein eine Tabellen-Funktion 🙂):

Schicht => Inhalt => Aufgabe
===============================
Daten => Entity-Klassen => Dumm, reines Daten-Layout
Business => Business-Service-Klassen => Arbeiten mit den Entities und den Dtos und enthalten die wertvolle Logik, wegen der es das Programm überhaupt gibt
Business/View => Dto-Klassen => "Vermittler" zwischen Business und View für jede Situation
ViewModels => ViewModels-Klassen => Logik "unter" der View, bereitet die Daten für die View auf und ruft Business-Methoden auf
View => XAML, HTML, etc. => Wird an ViewModels gebunden und arbeitet mit ViewModel-Commands

In MVVM wird das erste M als Model bezeichnet, ich habe sie hier aber Dto genannt, weil "Data Transfer Object" die Aufgabe mMn. besser beschreibt und der Unterschied im Lesefluss größer ist.
Außerdem wird gerne gesagt, dass so viele Dtos/Models "zu viel" sind. Das kann sein, aber nach Lehrbuch-MVVM gehört das so und bei größeren Projekten lohnt sich das auch. Ob man bei kleineren Projekten darauf verzichten sollte ... ich würde es nicht tun.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

4.939 Beiträge seit 2008
vor 2 Jahren

@Palladin007: Sehr gut geschrieben!

Nur würde ich keine 3 verschiedenen DTO-Klassen (Read, Update, Create) erstellen. Der Aufwand ist doch viel zu groß (insb. wenn noch mehr Eigenschaften dazukommen). Und evtl. hat man nicht nur Artikel, sondern auch noch andere Daten und dafür dann auch jedesmal verschiedene Klassen erstellen?! (Und wenn, dann wenigstens per Ableitung voneinander!)

2.079 Beiträge seit 2012
vor 2 Jahren

@Th69: Danke 🙂

Aber ja, die DTOs sollten ein Extrembeispiel sein, damit klar wird, was ich mein.

Zu viel Aufwand ist es aber nicht, zumindest nicht pauschal.
Bei kleinen Projekten, wo die DTOs im Grunde gleich sind: Ja, definitiv übertrieben.

Bei größeren Projekten, wo sich die DTOs unterscheiden, kann das aber sehr nützlich sein.
Aktuell arbeite ich auch an so einem Projekt, in dem alle Daten gelesen, aber nicht alle Daten beim Create angegeben oder beim Update geändert werden können.
Außerdem kann ich auf diese Wiese steuern, welcher Wert Optional ist (public set und nullable) und welcher nicht (Konstruktor), auch mit Blick auf Nullable reference types.

Man könnte auch mit Vererbung arbeiten, aber wie gut das funktioniert, hängt wohl vom Projekt ab.
Bei meinem aktuellen Projekt wäre das kein Vorteil.
Oder man splittet in mehrere Klassen auf und referenziert dann darauf.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

4.939 Beiträge seit 2008
vor 2 Jahren

OK - ich wollte nur darauf hinweisen, daß dies nicht unbedingt der Standard ist, wenn man mit CRUD-Operationen und DTOs arbeitet.

2.079 Beiträge seit 2012
vor 2 Jahren

Natürlich 🙂

@Spooner8:
Wirklich in einem Pattern oder einer Empfehlung definiert ist das Vorgehen bei dem Punkt nicht - oder ich kenne es nicht.
Es ist wichtig, dass die Zusammenhänge gewahrt werden, wie die Details dann aussehen, muss je Situation entschieden werden - das ist dann deine Aufgabe.
Das kann mit vielen DTOs sein, oder ein DTO je Entity oder mehrere DTOs je Funktion (nicht auf eine Entity beschränkt), etc. Oder Du gibst die Entities weiter, allerdings hat das auch ein paar versteckte Nachteile.

mehrere DTOs je Funktion (nicht auf eine Entity beschränkt)

Z.B. habe ich erst heute ein solches DTO geschrieben, in dem ich mehrere Daten aus verschiedenen Entities aufbereitet zusammenfasse.
Es muss also keine exakte Entity-Kopie sein, ein DTO sollte so aussehen, wie es der Aufrufer (in dem Fall das ViewModel) braucht.

Oder Du gibst die Entities weiter, allerdings hat das auch ein paar versteckte Nachteile.

Dabei denke ich z.B. an das Entity Framework.
Die Entities weiter zu geben ist da zwar kein Problem, aber im ViewModel kann dann ein unbedachter Zugriff auf eine Referenz-Property dazu führen, dass Daten nachgeladen werden (Stichwort: Lazy Loading) - je nachdem, wie Du das aufgebaut hast.
Ohne Entity hat der Business-Service oder das Repository die volle Kontrolle, wann welche Daten geladen werden und kann ggf. spezifisch optimieren.
Bei kleinen Projekten mit einfachen Daten und nur ein paar tausend Datensätzen ist das herzlich egal, aber wenn die Referenzen komplexer werden und es um ein paar Millionen Datensätze geht, dann kann daraus plötzlich ein echtes Performance-Problem werden.

Welchen Weg Du wählst, hängt am Ende davon ab, was Du brauchst.
Bei mir (mit den vielen DTOs) kommt das hauptsächlich daher, dass ich verhindern möchte, dass ein DTO missverstanden wird.
Alle Properties im DTO werden bei der jeweiligen Funktion auch gebraucht und alles, was nicht im Konstruktor enthalten ist, ist optional. Das kommt daher, dass ich schon ein paar Projekte gesehen habe, wo ich erst mal in der Implementierung nachlesen musste, was nun wichtig ist und was nicht und wie die Zusammenhänge sind und am Ende war es doch nicht richtig. Das möchte ich mit meinen DTOs eindeutig machen, dass keine Fragen offen sind. Dafür nehme ich den verhältnismäßig großen Aufwand in Kaufe, allerdings sind meine Daten auch verhältnismäßig einfach, sodass es nicht sehr ins Gewicht fällt.

Aber das ist nur mein Weg, Andere machen es anders, doch als Beispiel, um die Abläufe zu erklären, taugt es definitiv.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Hi zusammen

Erstmal wieder danke für die interessanten und lehrreichen Antworten. Das ist für mich ganz schön viel Input aber ich werde natürlich mein bestes geben die Sturktur nach diesen Angaben zu erstellen. Dazu habe ich nun auch mein Projekt neu aufgesetzt und beschäftige mich gerade noch mit den Grundeinstellung die mir von der View eh nicht ganz gepasst ahben.
Stichwort: Menüleiste. Die ist jetzt aber gut so und kann mit einer Zeile sauber in jede View als UserControl geladen werden. Darin sind dann auch das Menü, Minimieren, Maximieren und Schliessen einmalig behandelt.

Kurze Zwischenfrage. Ein Usercontrol ist an sich ja keine View, auch wenn es optisch programmiert wird. In diversen Videos habe ich nun gesehen, dass die immer die komplette Logig von Buttons etc. in dem UC auch im Codebehinde abwickeln. Macht man das so und behandelt ein UC wie ein ViewModel in dieser Hinsicht?
Denkbar wäre für mich auch Für alle Menüpunkte und die 3 Windowsteuerungsbuttons je ein Command zu erstellen. Sofern das sinnvoll ist in einem UC.

@Palladin007: Zum Thema OnPropertyChanged -> Ja da werde ich nun ein BaseViewModel oder ObservableObject erstellen dafür. Wie mans auch immer nennen will. Wusste ich bisher nicht, habe es aber vorgestern zufällig in einem Video gesehen und finde das sehr gut. Baue ja eh grade neu auf =)

Bei den Punkten 2 + 3 bin ich noch nicht ganz gestiegen, aber werde ich mir wenn ich an dem dran bin genauer nochmal anschauen und selber testen.
Punkt 4... Klingt plausibel hab ich aber noch kein klaren Durchblick, weil ich das noch nie so geschrieben habe. Mein Projekt ist wohl verhältnismässig auch zu klein.
Das Grösste was ich da rein lade sind wohl Kundendaten aus einem Excel. ~5900 Datensätze die ich per Import vom Excel in die SQL schreibe.

2.079 Beiträge seit 2012
vor 2 Jahren

Usercontrol ist an sich ja keine View, auch wenn es optisch programmiert wird. In diversen Videos habe ich nun gesehen, dass die immer die komplette Logig von Buttons etc. in dem UC auch im Codebehinde abwickeln.

Deshalb mag ich solche Videos nicht.

Nein, ist natürlich nicht richtig - zumindest meistens.
Du musst zwischen UI-Logik und UI-Logik unterscheiden 😜 (Mir fallen keine geeigneten Namen ein. ^^

Zum Einen gibt es die UI-Logik, die einzig und allein für die UI notwendig ist, also z.B. Animationen oder dass in einer Tabelle immer das ausgewählte Item mittig angezeigt wird und so weiter. Diese Logik hängt direkt an der View und braucht häufig auch View-Objekte, um zu funktionieren.
Und dann gibt es die Logik, die tatsächliche Business-Beziehungen hat, wie z.B. wann ein bestimmter Dialog angezeigt werden soll, oder nach welchen Einstellungen welche Business-Funktion ausgeführt werden soll, oder welche Property-Änderung zu welcher Neu-Berechnung führt und so weiter.

Die "erste" UI-Logik "darf" im Code-Behind stehen, aber ich würde es möglichst vermeiden, da WPF nicht darauf ausgelegt ist und Du dann nur "gegen" WPF arbeitest. Außerdem geht es da leicht unter.
Es gibt aber zig andere Möglichkeiten, diese Logik passend in WPF zu integrieren, z.B. Converter, (Microsoft.Xaml.Behaviors.Wpf) Behaviors oder Trigger. Von Triggern gibt's allerdings eine Menge: Control-Trigger, Template-Trigger, Style-Trigger und "Behavior-"Trigger (Microsoft.Xaml.Behaviors.Wpf), die ersten drei sind die gleichen, aber in unterschiedlichen Situationen. Und dann gibt's noch MarkupExtensions (z.B. Binding oder StaticResource sind MarkupExtensions), da werden die Möglichkeiten erst recht kompliziert und man sollte es mit ihnen nicht übertreiben.
Alles in allem kein leichtes Thema, aber wenn man die Tricks kennt, kann man auf diese Weise sehr einfach solche Logik im XAML nutzbar machen und auch wiederverwenden.

Die zweite Art der UI-Logik ist häufig (nicht immer) besser im ViewModel aufgehoben, die erste Art darf da aber auch landen. Hierfür verwendet man hauptsächlich klassische Properties und Commands, es gibt aber noch ein paar kleine Tricks mehr.

Was nun der richtige Weg ist, muss man sich je Situation anschauen, das Hauptziel ist dabei Übersicht.
Deshalb sollte auch möglichst nichts im CodeBehind stehen, denn da geht es leicht unter und der C#-Code mit WPF-Objekten wird leicht unübersichtlich - und man kann es nur schwer wiederverwenden.
Wenn man View-Objekte braucht, sollte es aber auf keinen Fall ins ViewModel, das sollte möglichst unabhängig von der View bleiben. Für kleine Projekte könnte ich es aber auch verstehen, wenn man bei dem Punkt eine Ausnahme macht, ich würde es aber nicht machen.

Ja da werde ich nun ein BaseViewModel oder ObservableObject erstellen dafür

Guck dir Microsoft.Toolkit.Mvvm an, das verwende ich aktuell auch.
Perfekt finde ich sie nicht, aber das ist wohl eher Geschmackssache.

Mein Projekt ist wohl verhältnismässig auch zu klein.

Das ist ein häufiger Knackpunkt bei MVVM, da es sich hauptsächlich an größere Projekte richtet.
Zum Verstehen ist das natürlich ein Problem (deshalb auch mein Extrem-Beispiel mit den DTOs), mittlerweile würde ich aber auch bei kleinen Projekten MVVM nutzen, einfach weil es fast schon reflexartig funktioniert ^^

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Du musst zwischen UI-Logik und UI-Logik unterscheiden Mir fallen keine geeigneten Namen ein. ^^

Was hältst du von UI-Logik und UI-Controls-Logik? 🙂

Also aktuell habe ich im UserControl folgendes gemacht:

  • Links: Menu inkl. allen MenuItems
  • Rechts: Buttons -> Minimieren, Maximieren und Schliessen
  • Mitte: Ein Grid damit ich später in der View je nach View auch noch weitere Elemente hinzufügen könnte

Im View dann ungefähr so:


<uc:MainMenuUserControl DockPanel.Dock ="Top">
    //Hier können direkt weitere Elemente eingefügt werden.
</uc:MainMenuUserControl>

Die Buttons habe ich im Codebehinde vom UserControl per DelegateCommand gebunden. Was das DelegateCommand macht habe ich denn aber auch direkt mitgegeben. (Es gibt aktuell kein Command für z.B. Close)
Das weil ich durch die ganze Trennerei angestanden bin die View via Usercontrol via Command zu schliessen weil ich mit Window.GetWindow(this) aus dem command kein Window bekomme. Für euch sicher einfach, für mich eine neue Hürde die ich aber (vertretbar) gelöst habe.

Sieht im UC Codebehinde dann etwa so aus:


public DelegateCommand CloseCommand { get; set; }

public MainMenuUserControl()
{
	InitializeComponents();
	
	CloseCommand = new DelegateCommand((o) => { Window.GetWindow(this).Close() });
}

//Der DelegateCommand nimmt eine Action<object> entgegen. Überladen mit Action<object> und Predicate<object>


<uc:MainMenuUserControl DockPanel.Dock ="Top">
    <Button Command="{Binding CloseCommand}"/>
</uc:MainMenuUserControl>


Jetzt darfst du mir sagen ob meine Ansicht von "Vertretbar" stehen bleiben darf oder ob das keine gute Idee ist lach. MVVM killt mich noch 😜 Aber spannend ist es ja schon.

2.079 Beiträge seit 2012
vor 2 Jahren

Close? Das kann doch das Window selber?
Wenn Du eine eigene Close-Optik haben willst, lohnt sich ggf. der Blick auf MahApps, das bietet eine eigene flexiblere Window-Ableitung.

Streng genommen ist deine Verletzung aber eine Verletzung von MVVM, da dein ViewModel direkten Zugriff auf die UI-Objekte benötigt.

Bevor ich jetzt alles doppelt und dreifach erkläre, hier wurde es schon erklärt:
how-to-bind-close-command-to-a-button/5634716
Bedenke: Das ist einige Jahre alt, vieles ist gleich, aber nicht alles, doch das Prinzip bleibt.

In jedem Fall sollte das ViewModel nichts mit dem Window anfangen. Es darf aber in einem Command passieren, nur darf der nicht im ViewModel sein.
Und ich würde alle Umsetzungen vermeiden, die CodeBehind benötigen, meine Favoriten sind also alle die, die den Code ausgelagert haben, entweder als Trigger, Command oder Behavior.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Close? Das kann doch das Window selber?

Da hätte ich vielleicht erwähnen sollen, dass ich ein Flat UI-Design anstrebe und daher das Window kein Frame oder Kopfleiste mehr hat.
Im Window ist erst ein Border mir Roundet Corners und darin ist dann das UC welches die 3 Buttons beinhaltet.
Darum mein Code von vorhin.

Streng genommen ist deine Verletzung aber eine Verletzung von MVVM, da dein ViewModel direkten Zugriff auf die UI-Objekte benötigt.

Dachte ich mir schon. Da ich das aber so in der Form einmalig mache und die Menüleiste immer wieder verwende stelle ich mir die Frage wie tragisch diese Ausnahme ist und ob ich das einfach so lassen soll. Ich würde es eigentlich schon gerne in einen externen Command auslagern

Es darf aber in einem Command passieren, nur darf der nicht im ViewModel sein.

Also du meinst der Command selbst ist schon im ViewModel initialisiert, nur die Definition vom Command wird in einer eigenen Klasse definiert, worin dann auf die View zugegriffen wird, richtig? Genau da liegt aktuell noch der Hund begraben weil der Command selbst die View bzw. Window ja nicht kennt. Da schaue ich gerade noch wie ich dort die beste Möglichkeit schaffe. Wäre es "ok" wenn das ViewModel über den CloseCommand dann ein View an den Command übergibt oder muss der Command sich das View "zwingend" selbst holen, damit im ViewModel wirklich keinerlei View-Thematik vor kommt?

Danke noch für den Link, evtl. finde ich ja da noch weitere Inputs wie ich das machen muss. Die Videolösungen brauchen in meinen Augen enorm viel Code nur damit ich ein Close Befehl geben kann. Da muss einfacher gehen.

Ist es eigentlich sinnvoll einen DelegateCommand wie ich ihn geschrieben habe zu benutzen und situativ auch noch einen BaseCommand der nur (object parameter) annimmt zu haben?
Oder sollte es vom vererbaren Command nur eine Variante geben die dann für jeden Command funktionieren sollte?

2.079 Beiträge seit 2012
vor 2 Jahren

Da hätte ich vielleicht erwähnen sollen, dass ich ein Flat UI-Design anstrebe und daher das Window kein Frame oder Kopfleiste mehr hat.

Ich hab sowas in der Art mit dem MetroWindow von MahApps gemacht. Ist etwas umständlich, aber dafür muss ich die Minimize, Close und Drag-Funktion nicht nachbauen.
Aber ja, als eigenständiger Command geht das natürlich auch, was nun besser ist, weiß ich nicht.

Da ich das aber so in der Form einmalig mache und die Menüleiste immer wieder verwende stelle ich mir die Frage wie tragisch diese Ausnahme ist und ob ich das einfach so lassen soll.

Das musst Du entscheiden, der Gedankengang ist zumindest richtig - also die Frage, ob das hier sinnvoll oder übertrieben ist.
Kein Pattern ist eine fixe Regel, es sind nur Empfehlungen und - noch wichtiger - Erklärungen der Ziele dahinter.
Du musst dir überlegen, wie weit Du der Regel folgen bzw. ab wann Du nicht mehr folgen magst und welche Vorteile oder Nachteile das mit sich bringt.

Also du meinst der Command selbst ist schon im ViewModel initialisiert, nur die Definition vom Command wird in einer eigenen Klasse definiert, worin dann auf die View zugegriffen wird, richtig?

Wenn irgendwelcher ViewModel-Code auf UI-Funktionen zugreift, die nicht abstrahiert werden kann, dann ist das eine MVVM-Verletzung.
In deinem Fall sorgt der Command dafür, dass ein direkter Zugriff auf ein Window erfolgt, damit zerstörst Du die Ziele hinter MVVM: Austauschbarkeit und Testbarkeit.
By the way, Du hast ja auch schon gemerkt: Window.GetWindow() will ein WPF-DependencyObject und kein ViewModel 😉 Da beginnt dann das Chaos, wie kommst Du an ein Control?

Ein Command muss aber nicht zum ViewModel gehören, er kann auch unabhängig sein.
In den Stackoverflow-Antworten gab's die Variante auch, dass die UI sich eine statische Command-Instanz besorgt, dessen Aufgabe es ist, den Parameter als Windows zu casten und zu schließen. Im XAML bindest Du darauf (x:Static) und als Parameter bindest Du an das Window (RelativeSource). Oder Du lässt den Parameter weg und nutzt einfach das MeinWindow.

Oder eben mit einer TriggerAction (ich hatte früher mal so eine CloseWindowTriggerAction) oder als Behavior, beide kennen das Control, für das sie angewendet werden und können dann auch Window.GetWindow() nutzen. Oder Du lässt das weg und nutzt einfach das MeinWindow.
Der Command ist vermutlich die einfachste Variante.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

So ich habe eine glaub ganz gute, schlanke und glaub auch MVVM einhaltende Lösung gefunden =)
Das einzige was gerade noch nicht ganz geht ist die übergabe von 2 Parameter via MultiBinding an den Command. Ich muss da nicht nur das Window, sondern auch den Button selbst übergeben, da sich das Icon und auch der Tooltip ändert zwischen Normal und Maximized.
Wenn da gerade jemand den aktuellen Fehler sieht, gerne melden 😁
Es liegt auf jeden Fall mal daran, dass ich gem. BaseCommand ja ein Object bekommen soll und ich ihm aber ein Object[] liefere. Ich weiss leider nicht genau wie ich das noch schlank regeln kann. Sonst passts jetzt ganz gut.


   <Control.Resources>
        <c:MaximizeConverter x:Key="MaximizeConverter"/>
    </Control.Resources>

    <Border Background="{StaticResource CompanyBlue}" CornerRadius="{StaticResource MenuBorderCorner}" MouseLeftButtonDown="Border_MouseLeftButtonDown">
        <DockPanel LastChildFill="True" Margin="5,5">
            <Menu DockPanel.Dock="Left" Background="Transparent">
                <MenuItem Icon="{md:PackIcon Kind=HamburgerMenu}">
                    
                </MenuItem>
                <MenuItem Icon="{md:PackIcon Kind=HelpCircleOutline}"/>
            </Menu>

            <Button DockPanel.Dock="Right" Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" 
                         ToolTip="{x:Static lang:Lang.Close}" Background="Transparent">
                <md:PackIcon Kind="Close"/>
            </Button>
            <Button DockPanel.Dock="Right" Command="{Binding MaximizeCommand}" ToolTip="{x:Static lang:Lang.Maximize}" Background="Transparent">
                <md:PackIcon Kind="WindowMaximize"/>
                <Button.CommandParameter>
                    <MultiBinding Converter="{StaticResource MaximizeConverter}">
                        <Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}"/>
                        <Binding RelativeSource="{RelativeSource Self}"/>
                    </MultiBinding>
                </Button.CommandParameter>
            </Button>
            <Button DockPanel.Dock="Right" Command="{Binding MinimizeCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" 
                         ToolTip="{x:Static lang:Lang.Minimize}" Background="Transparent">
                <md:PackIcon Kind="WindowMinimize"/>
            </Button>

            <Grid x:Name="MenuGrid" Margin="-5"/>

        </DockPanel>
    </Border>


public class MaximizeConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return values;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }


public class MaximizeCommand : BaseCommand
    {
        public override void Execute(object parameter)
        {
            Window window = parameter[0]; //Fehler: Eine Indizierung mit [] kann nicht auf einen Ausdruck vom Typ "object" angewendet werden.
            Button button = parameter[1]; //Fehler: Eine Indizierung mit [] kann nicht auf einen Ausdruck vom Typ "object" angewendet werden.

            if (window.WindowState == WindowState.Normal)
            {
                PackIcon icon = new PackIcon();
                icon.Kind = PackIconKind.WindowRestore;
                window.WindowState = WindowState.Maximized;
                button.ToolTip = Lang.Restore;
                button.Content = icon;
            }
            else
            {
                PackIcon icon = new PackIcon();
                icon.Kind = PackIconKind.WindowMaximize;
                window.WindowState = WindowState.Normal;
                button.ToolTip = Lang.Maximize;
                button.Content = icon;
            }
        }
    }


public abstract class BaseCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return true;
        }
        public abstract void Execute(object parameter);
        protected void OnCanExecutedChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }

16.834 Beiträge seit 2008
vor 2 Jahren

Musst halt (sicher) casten.

2.079 Beiträge seit 2012
vor 2 Jahren

Du kriegst einen object-Parameter und willst über Array-Indices darauf zugreifen und typisierten Variablen zuweisen.
Das Thema liegt weit vor WPF und MVVM irgendwo bei den Grundlagen 😜

Du kannst aber auch ohne Parameter arbeiten und aus dem Command ein DependencyObject mit zwei DependencyProperties machen, die Du dann normal binden kannst.
Ich persönlich würde das eher mit einem Trigger lösen, aber das ist vermutlich Geschmackssache.

By the way:
Wozu der Converter?
Zum Debuggen oder umgeht der ein Problem?

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Du kriegst einen object-Parameter und willst über Array-Indices darauf zugreifen und typisierten Variablen zuweisen.
Das Thema liegt weit vor WPF und MVVM irgendwo bei den Grundlagen

Das habe ich so auch als Problem beschrieben, sprich ich weiss was der Fehler war und das ich natürlich einem Object nicht ein Object Array geben kann.
Soweit ist die "Grundlage" schon verstanden und auch klar, dass das nichts direkt mit MVVM zu tun hat.

Wozu der Converter?

Weil ein MultiValueBinding einen MultiValueConverter verlangt.

Ich habe es nun gelöst in dem ich ganz simpel ein Model (MaximizeCommandModel) erstellt habe welches die beiden Eigenschaften Window und Button hat.
Diese weise ich im MultiBinding entsprechend im Converter zu und übergebe nicht mehr ein Array, sondern ein Object vom Typ MaximizeCommandModel.
So kann ich im Command dann ganz einfach auf die beiden Eigenschaften zugreifen und das Fenster korrekt steuern =)
Und ich würde mal behaupten dass so die Funktion im Sinne von MVVM programmiert ist. Für mich passt das so.

Die Übersicht zeichnet sich jetzt schon deutlich besser ab als bei der ersten Version. Auch wenn es viel mehr Klassen geben wird, ist das durch die vielen kleinen Klassen dann schon besser und Wartbarer. War die richtige Entscheidung das neu strukturiert zu machen. Danke für den "Arschtritt" 😜

16.834 Beiträge seit 2008
vor 2 Jahren

das ich natürlich einem Object nicht ein Object Array geben kann.

Doch, kannst Du. Weil alles in .NET auf object basiert, auch ein object array.
Daher muss man zurück casten, wenn man das Original haben will.

Es ist daher Teil des Konzepts, dass solche Schnittstellen object annehmen - und Du dann casten musst.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Hi, ich bins mal wieder =)

@Abt: Danke das war mir so nicht ganz bewusst, dass sich das so casten lässt.

Nun tue ich mich doch etwas schwerer als ich selbst erwartet hätte mit dem trennen der Verantwortlichkeiten =( Die einfachsten Sachen habe ich das gefühl zerfressen mein Hirn in kleine Fetzen. Oder ich bin überarbeitet.

Folgendes bekomm ich einfach nicht geschissen weil ich den richtigen Weg nicht sehe:

  • Die View hat 4 Textboxen und 1 Datagrid
  • Das ViewModel hat die Eigeschaften ObservableCollection mit Items (ItemsList) und ein Objekt vom Typ "FilterViewModel" welches 4 Eigenschaften für das Binding auf die Textboxen hat welche auch OnPropertyChanged implementiert haben.

Eigentlich will ich ja nur folgendes:

  • Bei jeder Tasteneingabe in einem der Texboxen soll in der "ItemsList" entsprechend alles gefiltert und umgehend angezeigt werden was den Eingaben der Textboxen entspricht. Im Grunde will ich das "TextChanged" event der Textboxen dafür nutzen, da dies ja bei jeder Eingabe reagiert und nicht erst beim verlassen der Textbox.

  • Noch geiler wäre eine einzige Suchbox in der mit "" als Platzhalter gesucht werden kann. z.B. Eingabe "PetMuster*Ber" und es zeigt alle "Peter Mustermann" aus "Berlin" an.

Als ich das direkt in der View gemacht habe ging das (nicht das mit dem "*"), aber da das ViewModel die View nicht kennt und auch nicht soll, bekomm ich das nun nicht mehr hin und will das nicht zurecht wursteln, sondern einen sauberen Weg umsetzen.
Ich habe es mit Events im ViewModel versucht aber ich wie es genau funktioniert bzw. die Videos hab ich schon begriffen aber weiss nicht wie ich das in meinem Fall einsetzen muss, damit das richtig funktioniert.

Danke schonmal für eure Geduld 🙄

Dann noch zwei kleine Fragen.

  1. Seit ich das ViewModel im Xaml als DataContext angebe sagt er mir, dass er keinen ConnectionString für meine Datenbank findet (Womit ich oben die ObservableCollection füttere). Aber es gibt beim Debug kein Fehler und er findet auch die Daten der Datenbank... In der AppConfig steht der ConnectionString auch genau so drin und das habe ich ja auch über VS erstellen lassen und nicht selbst gefummelt. (Database first) Einer ne Ahnung wieso er das dann meldet, wenn er es dann doch findet?

  2. Nach jedem Debug is der Designer auf "Fehler" und ich muss ihn kurz neu Starten, was er auch sofort und ohne Zicken macht. Hängt evtl. mit dem Punkt 1 gleich zusammen.

Wie gesagt, laufen tut es einwandfrei

2.079 Beiträge seit 2012
vor 2 Jahren

Die einfachsten Sachen habe ich das gefühl zerfressen mein Hirn in kleine Fetzen. Oder ich bin überarbeitet.

Tja 😜
WPF und MVVM sind halt kein Einsteiger-Thema, auch wenn diverse Video-Tutorials es gerne so aussehen lassen.
Wer effektiv mit WPF und MVVM arbeiten will, sollte die Grundlagen von C# und OOP drauf haben.

Bei jeder Tasteneingabe in einem der Texboxen soll in der "ItemsList" entsprechend alles gefiltert und umgehend angezeigt werden was den Eingaben der Textboxen entspricht. Im Grunde will ich das "TextChanged" event der Textboxen dafür nutzen, da dies ja bei jeder Eingabe reagiert und nicht erst beim verlassen der Textbox.

Das Binding hat eine UpdateSourceTrigger-Property. LostFocus ist der Default, daher das Verhalten.

Noch geiler wäre eine einzige Suchbox in der mit "*" als Platzhalter gesucht werden kann.

Das musst Du selber bauen, das hat nichts mit WPF oder MVVM zu tun.
Du bindest nur den Filter und das Filtern implementierst Du selber.

Wie die gefilterten Ergebnisse angezeigt werden, geht auf zwei Wege.
Einer wäre eine CollectionViewSource, ich persönlich nutze die aber eigentlich nur zum einfachen Sortieren.
Ich mache sowas eigentlich immer im ViewModel oder (je nach Anforderungen) in der Daten-Schicht um. Bei Letzterem kannst Du eventuell das LIKE hier missbrauchen, das kann ähnliche Funktionen.

Seit ich das ViewModel im Xaml als DataContext angebe sagt er mir, dass er keinen ConnectionString für meine Datenbank findet

Das liegt - wenn ich dich richtig verstehe - daran, dass der Designer eine Instanz von deinem ViewModel erzeugt und das Binding auch durchführt. So kann er Daten anzeigen und sicher sagen, wo es Probleme gibt.

Deshalb sollte man auch keine Logik in Properties umsetzen - Properties erlauben Zugriff auf Werte, sie validieren, setzen Zustände, etc. aber sie machen nichts mit einer Datenbank oder ähnlich komplexe Dinge.
Der vermutlich einfachste Umweg wäre, Du reagierst auf ein Event (z.B. Window.Loaded) und triggerst dann das Laden über einen Command. Das ViewModel ist also leer, bis das Window tatsächlich geladen wurde. Wenn der Designer auch was zum Anzeigen bekommen soll, musst Du ihm Dummy-Daten bieten, da gibt's Wege, herauszufinden, ob der Code vom Designer ausgeführt wird (DesignMode), oder ob das Programm tatsächlich läuft. Ich persönlich würde aber ganz auf den Designer verzichten (nur XAML - der Designer taugt eh nix), dann brauchst Du sowas auch nicht.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Ja stetig höhlt den Stein oder? 😉 ich übe einfach weiter und mit der Zeit wird alles klarer werden. Und so lange frage ich eben munter weiter.

UpdateSourceTrigger hab ich auch grad gefunden. Evtl. kann ich mit dem das bauen wie ich das brauche.
Habe noch ein Video gefunden wo man mit ICollectionView arbeitet. Wenn ich das aber so anschaue und ich mit 4 Feldern ja jede Situation abfangen müsste, wird der Filter irgendwo 24 Optionen haben... Mein Filter ging da einfacher und evtl. kann ich den dann mit dem UpdateSourceTrigger auch anpeilen.
An sich ja nur eine Methode im ViewModel. Wenn ich nun die 4 Eigenschaften nicht in einem Model liegen habe sondern im ViewModel, kann ich ja ganzu einfach anstelle von OnPropertyChanged, meine Filtermothode ansprechen (oder beide, was es nicht unbedingt braucht)

Von der Datenbank hole ich ja nur im Konstruktor vom ViewModel die Daten für die ObservableCollection. sonst mache ich da erstmal gar nichts.
Es geht hier nur drum im Datagrid den Eintrag zu finden. Mit Doppelklick darauf wird dann das eigentliche Fenster geladen und dort kann dann auch mit den Daten gearbeitet werden.
Dazu mach ich dann noch Models oder evtl. arbeite ich dann dort auch mit den Entities der DB, die sind ja auch dumm.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Ah noch was zum UpdatsSourceTrigger. Ich habe mich schon ein paar mal gefragt wieso das nicht zieht wenn ich folgendes habe:

In dem ViewModel habe ich ja evtl. nicht nur Eigenschaften bei denen ich OnPropertyChanged abfeuern kann, sondern um das ViewModel auch nicht zu überladen wenn mal mehr Eigenschaften benötigt werden, auch einfach eine Klasse als Property, welche wiederum ja Properties hat die den OnPropertyChanged implementiert haben.
Wie kann ich nun im ViewModel von der Klasse z.B. Item das OnPropertyChanged abfeuern, wenn sich vom Item die "Name" Property ändert? Oder einfach den Setter triggern um darin dann die besagte Filtermethode zu triggern.

Also folgender Aufbau:

Eine der Properties im ViewModel


private MainViewFilterViewModel mainViewFilterVM;
public MainViewFilterViewModel MainViewFilterVM
        {
            get { return mainViewFilterVM; }
            set
            {
                if (mainViewFilterVM != value)
                {
                    mainViewFilterVM = value;
                    OnPropertyChanged();
                }
            }
        }


public class MainViewFilterViewModel : BaseViewModel
    {
        private string itemName;
        public string ItemName
        {
            get { return itemName; }
            set
            {
                if (itemName!= value)
                {
                    itemName= value;
                    OnPropertyChanged();
                }
            }
        }
}


<TextBox Text="{Binding MainViewFilterVM.ItemName, UpdateSourceTrigger=PropertyChanged}"/>

Wenn nun in der Textbox etwas eingegeben wird, wird zwar der Trigger der TextBox getriggert, aber im ViewModel der Setter nicht ausgelöst, weil er direkt in das ViewModel der Property MainViewFilterVM schreibt und erst dort dann der Setter getriggert wird.
Wieso ist das so? Im Grunde ändert sich ja eine Property innerhalb der Property aber halt nicht die Property selbst. --> OK das beantwortet eigentlich die Frage, aber ich weiss trotzdem nicht wie ich das machen kann. 😜 😲

2.079 Beiträge seit 2012
vor 2 Jahren

Wenn ich das aber so anschaue und ich mit 4 Feldern ja jede Situation abfangen müsste, wird der Filter irgendwo 24 Optionen haben

Deshalb im ViewModel.
ItemsSource => ViewModel
FilterExpression => ViewModel
ReloadCommand => ViewModel

Theoretisch könntest Du in der FilterExpression-Properts auch die Daten direkt laden, das Binding hat dafür eine IsAsync-Property.
Ich persönlich bin da aber kein Freund von, da die Property dann wieder zu viel tut.
Ohne generell asynchrone Abläufe wüsste ich aber auch keinen anderen Weg, wie man das machen könnte.
Oder der Filter greift eben erst, wenn man auf einen Reload-Button klickt, das wäre dann die super einfache (und meist ausreichende) Möglichkeit.

Wenn ich nun die 4 Eigenschaften nicht in einem Model liegen habe sondern im ViewModel, kann ich ja ganzu einfach anstelle von OnPropertyChanged, meine Filtermothode ansprechen (oder beide, was es nicht unbedingt braucht)

Kannst Du natürlich auch machen, aber als Command und nicht als Methode. Methoden gehen zwar auch, aber das ist ziemlich umständlich.
Ich war gedanklich bei einer Filter-TextBox, in der man alles filtern kann und das habe ich bei mir mit einem SQL-LIKE je Column gelöst.
Wenn dir ein InMemory-Filter reicht, kannst Du das natürlich auch im ViewModel machen, wie Du willst.

Von der Datenbank hole ich ja nur im Konstruktor vom ViewModel die Daten für die ObservableCollection. sonst mache ich da erstmal gar nichts.

Für den Konstruktor gilt das gleiche: Nichts aufwändiges mach.

Oder was meinst Du, wie der Designer an eine Instanz von dem ViewModel kommt?

Dazu mach ich dann noch Models oder evtl. arbeite ich dann dort auch mit den Entities der DB, die sind ja auch dumm.

Für kleine Projekte reichen vermutlich auch Entities, MVVM macht dazu jedenfalls keine Aussage.
Die Trennung in Entities und Models ergibt dann Sinn, wenn sich beides unterscheidet oder Du aus anderen Gründen die Daten-Schicht nicht mit der View-Schicht verbinden willst.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

2.079 Beiträge seit 2012
vor 2 Jahren

In dem ViewModel habe ich ja evtl. nicht nur Eigenschaften bei denen ich OnPropertyChanged abfeuern kann, sondern um das ViewModel auch nicht zu überladen wenn mal mehr Eigenschaften benötigt werden, auch einfach eine Klasse als Property, welche wiederum ja Properties hat die den OnPropertyChanged implementiert haben.

Mach das nicht nur, weil dir die Properties zu viel werden, sondern weil die Aufteilung auch inhaltlich Sinn ergibt.
Ansonsten würde ich so eine Property generell readonly machen - erspart das Behandeln der geänderten Instanzen.

OK das beantwortet eigentlich die Frage, aber ich weiss trotzdem nicht wie ich das machen kann.

Gar nicht.

Erfinde ein eigenes Event, das Du in der Property auslöst und reagiere im "Parent-ViewModel" darauf.
Oder reagiere auf Property-Änderungen anhand des PropertyChanged-Events, das es ja schon gibt.
Was nun besser ist - ich bevorzuge das eigene Event, dann ist immer offensichtlich: Da gibt's ein Event, das hat eine bestimmte Aufgabe und muss im Auge behalten werden.
Du willst nämlich nicht den Überblick über deine "Event ruft Event auf, was ein Event aufruft, ..."-Ketten verlieren, so ein Wirrwarr aufzuräumen, kann sehr grausig werden 😉

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Erfinde ein eigenes Event, das Du in der Property auslöst und reagiere im "Parent-ViewModel" darauf.

Das war eben auch schon mein Gedanke. Ich weiss halt noch nicht so richtig wo das Event platziert sein muss und wie ich es korrekt auslöse. Da habe ich noch einen Knopf.
Ohne zusätzliches Event wäre schon gut, da die OnPropertyChanged ja schon vorhanden ist. Wie ich dann auf das ParentViewModel komme weiss ich nicht, hätte ich aber schon öfters brauchen können.

2.079 Beiträge seit 2012
vor 2 Jahren

Dann solltest Du dir das Grundwissen aneignen, wie man mit Events arbeitet.
Dann wird dir auch klar, dass "Wie ich dann auf das ParentViewModel komme" eine ziemlich unsinnige Frage ist, da Events ja extra für den umgekehrten Weg gedacht sind.

Wie gesagt:

WPF und MVVM sind halt kein Einsteiger-Thema, auch wenn diverse Video-Tutorials es gerne so aussehen lassen.
Wer effektiv mit WPF und MVVM arbeiten will, sollte die Grundlagen von C# und OOP drauf haben.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

S
Spooner8 Themenstarter:in
40 Beiträge seit 2020
vor 2 Jahren

Hiho zusammen

So, nun habe ich doch einige Fortschritte gemacht und habe das wesentliche in der neuen Version wieder erstellt.
Das mit den Events habe ich soweit auch kapiert, wenn auch noch nicht sattelfest, aber das kommt schon noch.

Ich hätte eine Frage für die Kalkulation meiner Elemente. Mit dem neuen Aufbau der ViewModels ist das nun aufgekommen.

Folgender Aufbau:


public class ItemViewModel : BaseViewModel
{      
        public DimensionsViewModel Dimensions { get; set; }
        public AddonsViewModel Addons { get; set; }

        public ItemsViewModel()
        {
            Dimensions = new DimensionsViewModel();
            Addons = new AddonsViewModel();
        }
}


public class AddonsViewModel : BaseViewModel
{
        private double left;

        public double Left
        {
            get { return left; }
            set
            {
                if (left != value)
                {
                    left = value;
                    OnPropertyChanged();
                }
            }
        }

        public void CalculateAreas(DimensionsViewModel dim)
        {
                //Berechne aktulle Flächen anhand der neuen Itembreite
        }

Meine Frage ist jetzt, wie ich am einfachsten aus dem AddonsViewModel auf die Instanz vom ItemViewModel zugreifen kann, ohne dass ich z.B. sowas wie unten machen muss, nur damit die sich kennen, bzw. ich darauf ganz einfach zugreifen kann. Das geht bestimmt schöner und ohne übergabe vom "Parent ViewModel" oder?


public class AddonsViewModel : BaseViewModel
{
        private double left;
        private ItemViewModel _viewModel;

        public double Left
        {
            get { return left; }
            set
            {
                if (left != value)
                {
                    left = value;
                    OnPropertyChanged();
                }
            }
        }
        public ItemViewModel ViewModel { get; set; }

        public AddonsViewModel(ItemViewModel viewModel)
        {
                _viewModel = viewModel;
        }

        public void CalculateAreas(DimensionsViewModel dim)
        {
                //Berechne aktulle Flächen anhand der neuen Itembreite
        }

Danke für die Hilfe. Habe es im Google nicht gefunden weil ich leider nicht weiss nach was ich genau suchen sollte.