Laden...

Binding eine Spalte von einer DataTable - Setze ich MVVM korrekt um?

Erstellt von lhyn vor 7 Jahren Letzter Beitrag vor 7 Jahren 4.443 Views
Hinweis von Coffeebean vor 7 Jahren

Abgeteilt von Binding eine Spalte von einer DataTable

Hier gehts langsam Richtung Review...ich hab das mal abgeteilt.

L
lhyn Themenstarter:in
136 Beiträge seit 2015
vor 7 Jahren

Hallo Leute,

Habe die letzten Beiträge rege verfolgt und mich nun gefragt, ob ich das Ganze richtig umsetze...

Vom ErfinderDesRades kam auch der Einwand betreffend "paralleler Listenführung".
Im Projekt im Anhang ist genau diese Listenführung implementiert.

Die GUI mit TreeView und ContextMenu ist selbsterklärend.

Gerne hätte ich dazu Eure konstruktive Kritik bezüglich Fehler in der Umsetzung von MVVM.

Gruss Lhyn

Edit: Oha, habe im zip-File nicht alle .exe entfernt...bitte löscht doch diese von Hand.
Edit: Neu hochgeladen...

Gruss Lhyn

3.003 Beiträge seit 2006
vor 7 Jahren

Eins vorweg, ich habe das Projekt als erstes mal gestartet, und es mag meiner Ungeduld geschuldet sein, aber - ich verstehe nicht, was ich damit machen soll / kann. Ich kann also auch nicht sagen, was es können sollte oder nicht, und kann mich nur auf den Code beziehen.

Bitte beachten, dass ich durchaus falsch liegen kann in einigen Aussagen. Erfahrung mit MVVM habe ich aber durchaus so ein bisschen, auch beruflich 😉 . Auch, wenn die Aussagen etwas, ähm, härter sein sollten - ich will damit niemanden beleidigen, sondern Schwächen deutlich machen.

  1. Architektur
    Ich bin ein bisschen ratlos, ehrlich gesagt. Du hast zwei nahezu-POCO, die eindeutig nicht in die UI-Domäne gehören (eher in BL oder DAL), nämlich ModelGebaeude und ModelProjekt. Wie gesagt, die beiden gehören NICHT in die Domäne der UI, insofern können sie überhaupt nicht das Model deines MVVM-Szenarios sein. Alle drei Schichten der MVVM gehören zur UI. Dasselbe Missverständnis hat, wenn ich ihn nicht komplett falsch verstanden habe, auch ErfinderDesRades. Die beiden POCOs kannst du bedenkenlos in eine eigene lib knallen und auch in einem Service verwenden, ohne dass sie eine Zeile Code zuviel hätten. Die gehören also nicht zum MVVM.

Das Modell der Daten, die für die UI interessant sind, heisst bei dir ViewModelGebäude und ViewModelProjekt. Nur: ein ViewModel modelliert den View, insbesondere seine Mechaniken und Arbeitsprozesse. Das tun die beiden nicht, sollen sie auch nicht - sie modellieren Daten, bzw das Benutzermodell der benutzten Daten. Also das, was der Benutzer glaubt, an Daten zu manipulieren, wenn er die UI benutzt. Die beiden wären (wenn sie korrekt umgesetzt wären, darauf komme ich noch) also das erste M der MVVM.

Zum MainViewModel. Ich schrieb es ja bereits: ein ViewModel modelliert das Verhalten und die Zustände des Views. Das passiert in deinem ViewModel: nirgends. Dein VM weiss zu keiner Zeit, welches Objekt ausgewählt ist, es kennt die Reihenfolge der Einträge nicht, es ist nicht in der Lage, eine beliebige dem Benutzer zur Verfügung stehende Information zu manipulieren. Das wäre aber seine Aufgabe: über das Binding mitbekommen, was der Benutzer macht, und alle notwendigen Aktionen auslösen und steuern, damit der View per Binding das abbilden kann.

Also, aus meiner Sicht sind Ansätze für MVVM da, aber das Projekt zeigt recht deutlich, dass du das Konzept noch nicht verstanden hast. Nix, was nicht mit Übung zu lösen wäre 😉.

  1. Konkrete Umsetzung
    a) Deine Trennung der Zuständigkeiten machst du dir dadurch vollkommen kaputt, dass du ausschließlich RelayCommands verwendest. Die haben (sehr selten) ihre Existenzberechtigung, aber hier machen sie dir die Zuständigkeiten völlig kaputt.
    Faustregel: Wenn du Model oder ViewModel nicht testen kannst, ohne System.Windows einzubinden, dann bist du raus. Denn das bedeutet (meistens), dass dein Code etwas von der UI wissen muss - was er eigentlich gar nicht könnte, wenn er entkoppelt wäre. Ist nicht immer leicht umzusetzen, das gebe ich gern zu 😃.

b) das ViewModel-Interface hast du offensichtlich nicht selbst geschrieben. Für deinen Anwendungsfall überzogen, und ehrlich gesagt ziemlicher Krüppelcode, böse ausgedrückt. Zur Illustration vielleicht mal ViewModel<T> anschauen, den Setter für das gewrappte Model. Das geht besser, und in deinem Fall geht es besser ohne.

c) ganz allgemein merkt man halt am Code, dass du noch nicht die Routine hast. Ich zähle ein paar Sachen auf, die das ganze etwas aufgeräumter machen können. 1 bis 5 Punkte, 5 wichtig, 1 sehr unwichtig.

  • CloneProperties() in ModelGebaeude und ModelProjekt -> dafür gibt's IClonable (4)
  • wenn du deine Membervariablen mit einem Unterstrich beginnend benennst, kannst du this. wirklich weglassen. Mich irritiert das beim Lesen (1)
  • gegen Schnittstellen programmieren, nicht gegen konkrete Objekte (vgl. List<Type> vs. IEnumerable<Type>) (3)
  • unbenutzte Usings entfernen (2)
  • Datum nicht als string übergeben (5) (deinen Stil nennt man ironisch "stringly typed objects")
  • object nicht als Parameter nutzen, wenn man ein INterface nutzen/einführen könnte (4)
  • lazy initialising benutzen, wenn man member über eine Property zugreifbar macht (2-3)
  • namespace ERR_Tool umbennenn 😉 (1)
  • deutsche Bezeichnungen...naja, kann man machen. (0 - ignorieren. Ist eine völlig persönliche Entscheidung, und du hast es ja auch beinahe durchgezogen.)

So. Was mich am meisten stört, ist ehrlich gesagt, dass ich die Oberfläche nicht kapiere. Dem Benutzer sollte ins Gesicht springen, was er tun kann, und wie er es tun kann, und wozu.
Empfehle mal das hier: User Interface Design for Programmers.

So, das waren meine 2 cents. Hoffe, du kannst was mitnehmen 😃.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

L
lhyn Themenstarter:in
136 Beiträge seit 2015
vor 7 Jahren

Hallo LaTino,

Erstmal Danke für die viele Zeit die Du Dir genommen hast (2 cent 😄)

Und nein, ich habe ja um konstruktive Kritik gebeten und fühle mich in keinster Weise angegriffen...kann nur profitieren.

Zur Nutzung der GUI:
Habe im Anhang mal Ein Bild eingefügt...
In der TreeView können mittels ContextMenu (Rechtsklick) Gebäude hinzugefügt, entfernt, verschoben, kopiert und geklont werden.

Architektur:
Nachdem ich Deinen "2 cent" Eintrag mehrmals gelesen habe, verstehe ich glaub nun.

Ich versuche meine Fehler bezüglich MVVM nun mal mit meinen Worten zusammenzufassen 😁
Bitte korriert meine Aussagen, sollte ich diese falsch verstanden haben.

  • ModelProjekt und ModelGebaeude sind im meinem Fall überflüssig
  • Meine ViewModel (ViewModelProjekt & ViewModelGebaeude) sind die Model
    -> Beim Datenimport via z.B. XML werden diese Daten direkt in das Model geschrieben (Das geht dann in die BL hinein, was aber nichts mit MVVM zu tun hat)
  • Die richtigen ViewModel binden nur an die View und stellen die Daten aus dem Model bereit, womit ich auch das aktuelle Problem von parallelen Listen umgehe, da ich nur die Daten übergebe...somit macht in meinem Fall ein ViewModelTreeView und womöglich ein ViewModelDataGrid sinn
  • Ein MainViewModel ist nicht unbedingt nötig, da ich an "ViewModelTreeView" und "ViewModelDataGrid" binde

Umsetzung:
a) Womit soll ich die RelaisCommands ersetzen (direkte Property)? Hat so schön funktioniert...bin deshalb dabei geblieben. 😉
Beim ContextMenu (MoveUp und MoveDown) sind die diese doch nötig, damit bei oberer oder unterer Endlage (siehe Bild Anhang) das Feld "grau bleibt".

b) Hast Du absolut recht, werde ich mir anschauen und umschreiben oder gegebenenfalls entfernen. Gibt es dafür Vorlagen wie diese Common- Dinger wie RelaiCommand, ICommand, ViewModel- Interfaces auszusehen haben?

c)Werde Deine Vorschläge übernehmen wobei noch 2 Fragen offen sind.

  • gegen Schnittstellen programmieren, nicht gegen konkrete Objekte (vgl. List<Type> vs. IEnumerable<Type>) (3)
    -> verstehe ich nicht, Link oder Erläuterung dazu?
    -> ist doch viel Einfacher direkt mit den Listen bzw. Objekten zu Arbeiten
  • object nicht als Parameter nutzen, wenn man ein INterface nutzen/einführen könnte (4)
    -> dabei wird Du die RelaisCommand meinen wobei ich das Object gecastet habe?
    -> Link oder Erläuterung dazu?
  • lazy initialising benutzen, wenn man member über eine Property zugreifbar macht (2-3)
    -> werde ich mal ergoogeln...kenne nur lazy loading bzw. habe davon gelesen

Danke Euch schonmal im voraus 👍

3.003 Beiträge seit 2006
vor 7 Jahren

In weiten Teilen richtig verstanden. Zieh die Command-Properties raus aus den Models und in das zu erstellende ViewModel. Dieses ViewModel kann auch direkt den Code für die ViewModels für DataGrid und Treeview haben, das stört nicht groß. Ansonsten ist es nur eine Codeauslagerung, dh. das Haupt-ViewModel bleibt weiterhin der Meister im Ring. Das Haupt-VM ist auch im Besitz der Magie der Commands, wobei jedes Command tatsächlich nur eine Reaktion auf eine Benutzeraktion darstellen soll. Wenn ich mich recht erinnere, waren einige Commands eigentlich interne Logik für den Aufbau der Controls - die Reihenfolge der Einträge zum Beispiel kennt das ViewModel und gibt dem View halt nur eine Liste von nach Lust und Laune vorsortierten Objekten.

lazy instantiation:


private ICommand _myCommand;
public ICommand MyCommand 
{
    get 
    { 
        if(_myCommand == null) _myCommand = new MyCommandType();
        return _myCommand;
    }
}
//in kurz:
public ICommand MyCommand 
{
    get { return _myCommand ?? (_myCommand = new MyCommandType()); }
}

(lazy: die Erstellung wird so lange verschoben, bis es wirklich gebraucht wird.)

Das mit dem Object bezog sich auf eine Stelle, wo ich an sich sicher war, dass es sich nicht um ein Command gehandelt hat...hm, na okay, in dem Fall gibt die ICommand-Schnittstelle diesen unschönen Haufen mit object als Parameter vor.

Ansonsten die Commands trennen. Ein Command kriegt ein Objekt, und tut was damit. Es weiss nichts von ViewModel, oder View. RoutedComands brechen damit, deshalb...näää. Es hat mehr Nachteile, als nur, dass es nicht ganz wie aus dem Lehrbuch ist. Zum Beispiel kannst du die Commands, völlig unabhängig und sehr einfach testen, wenn sie getrennt sind. Das geht mit RoutedCommands nicht mehr.

OOP, code against an interface ist eines der Grundprinzipien der OOP: man sollte nur die Vertraege, also die Schnittstellen kennen, und nicht die konkrete Implementierung, während man Code schreibt. Denn die Implementierung kann ausgewechselt werden. Hat man dann nicht gegen die Schnittstelle programmiert, sondern sich auf Spezialfeatures verlassen, dann ist man am...Problem 😉.

http://stackoverflow.com/questions/2697783/what-does-program-to-interfaces-not-implementations-mean

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

L
lhyn Themenstarter:in
136 Beiträge seit 2015
vor 7 Jahren

Hallo LaTino,

Jetzt habe ich mal nachgeschaut, weshalb ich das Ganze so programmiert habe.

http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial

Mir fällt dabei auf, dass dieses und auch viele andere Programme MVVM vom Stiel ähnlich aufgebaut sind wie mein Programm.

Bsp. von obigem Link:
M -> Song, V -> View, VM -> ViewModelSong

analog bei mir:
M -> ModelGebaeude, V -> View, VM -> ViewModelGebaeude

Auch inhaltlich sehr ähnlich, soll heissen die binden immer an ViewModel"Objekte" welche dem Model entsprechen wie in meinem Fall.

Ich stehe momentan auf dem Schlauch...welche Umsetzung entspricht den nun der Praxis? 🤔

Gruss Lhyn

P
1.090 Beiträge seit 2011
vor 7 Jahren

OOP, code against an interface ist eines der Grundprinzipien der OOP: man sollte nur die Vertraege, also die Schnittstellen kennen, und nicht die konkrete Implementierung, während man Code schreibt. Denn die Implementierung kann ausgewechselt werden. Hat man dann nicht gegen die Schnittstelle programmiert, sondern sich auf Spezialfeatures verlassen, dann ist man am...Problem 😉.

Schön das in Englisch angefangen hast. Interface im Englischen bedeutet nur Schnittstelle und nicht Interface, was wir in Deutsch @von der Implementierung her kennen. Alle Interfaces (Deutsch) sind Schnittstellen, aber nicht alle Schnittstellen sind Interfaces. Jede/s Public Propertie oder Methode von einem Objekt ist eine Schnittelle. Das ist ein oft gemachter Übersetzungsfehler.
Gegen Schnittellen oder gegen Interfaces zu Programmieren ist was Unterschiedliches. Kent Beck spricht sich in seinem Buch „Implementation Patterns“ darüber, das Interfaces nur eingeführt werden sollten wenn sie wirklich nötig sind.

@lhyn
MVVM ist ein Entwurfsmuster und damit eine „hohe“ Abstraktion. Es gibt halt nicht DIE Implementierung mit der man MVVM RICHTIG umsetzt. Man kann es nur falsch umsetzten.
Um vielleicht mal ein Beispiel aus der Architektur zu nehmen (da kommen die Entwurfsmuster her). Z.B eine Tür. Die Abstraktion ist das es die Möglichkeit ist einen Raum Möglichst einfach zu Betreten und zu Verlassen. Jetzt gibt es in der Umsetzung viele Türen (Haustüren, Autotüren, Schiebetüren u.s.w.) und diese vielen Türen haben jeweils ihre Vor- und Nachteile. Eine Haustür macht sich an einem Auto schlecht und eine Autotür an einem Haus. Was sie aber z.B alle gemeinsam haben, ist das sie keine Fenster sind.

Und genau so ist es mit dem MVVM Pattern, da gibt es einfach nicht DIE Implementierung. Wäre es so könnte der Computer es auch machen. Sondern man muss von Fall zu Fall entscheiden, was die „beste“ (keine Schlechte ist auch schon mal nicht schlecht) Lösung ist.

Bei der Entscheidung hilf es dann Prinzipien der OOP zu kennen (SOLID mal als Schlagwort), CCD bietet auch einiges an Hilfe, andere Pattern (GoF) können auch nicht schaden.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

3.003 Beiträge seit 2006
vor 7 Jahren

Ich weiß, klingt etwas anmaßend, aber insbesondere, wenn du dir mal das Zitat von MSDN anschaust, was ich oben gepostet habe: CodeProject wimmelt von Tutorials, die den Namen nicht wirklich verdienen. Ich kann's leider nicht anders sagen. Gerade zum Thema MVVM ist es aber durchaus schwierig, etwas Gehaltvolles zu finden, das dem Gedanken hinter MVVM gerecht wird. Das bedeutet nicht, dass keiner weiß, wie's gemacht wird 😉.

Zum Punkt: das Modell dieses Tutorials beschreibt ein Model, wie es von Microsoft in der Definition von MVVM ausdrücklich als Ausnahme dargestellt wurde. Der Autor weiß das entweder nicht, oder es ist ihm egal. Wie dem auch sei, seine Implementation scheitert dran, dass er genau das machen muss, was Microsoft für diese Ausnahme vorsieht, und das ist umständlich und nicht als Tutorial geeignet. Sehe ich zumindest so 😉.

LaTino

EDIT: @Palin, sorry, aber mir ist diese deutsche Interpretation des Wortes Schnittstelle im Thema Softwarearchitektur noch nicht untergekommen. Wenn ich mit Entwicklern über Schnittstellen rede, dann entweder über Import/Export oder über Softwaredesign und damit über Interfaces. Ich verstehe, dass man die Bedeutung auch mehr auf die eigentliche Bedeutung des deutschen Wortes Schnittstelle abstellen kann, aber dass das jemand tut (in diesem Kontext), ist mir neu . Man lernt immer dazu 😃. Zur Klarstellung: ich rede oben immer über interfaces im Sinne von "Schnittstellenbeschreibung, die sich Klassen teilen können, indem sie die Schnittstelle implementieren", also schlicht interface. Und Kent Beck hat recht. Wenn es in diesem Fall keinen Sinn ergeben würde, hätte ich es nicht vorgeschlagen. IEnumerable<> statt List<> hat mehr Vorteile als nur, dass man eine Schnittstelle hat, damit man sie hat. Der Unterschied wird schnell klar, wenn man Linq mit ins Spiel bringt.

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

P
1.090 Beiträge seit 2011
vor 7 Jahren

@LaTino mir ging es auch nicht darum, das es im Deutschen keine klare Unterscheidung gibt zwischen Interface und Schnittelle. Sondern das es im Englischen das gleiche ist und bei der Übersetzung oft falsch verstanden wird.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

3.003 Beiträge seit 2006
vor 7 Jahren

ich muss mal auch nochmal korrigieren/erklären.Ich schrieb vorhin was von einem MSDN-Zitat. Dabei habe ich - mein Name ist Chaos - den Thread hier mit dem Thread dort verwechselt. Man denke sich meinen Beitrag mit dem Code-Beispiel auch noch einmal hier 😉

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)