Fragen zu NotifyPropertyChanged --- Model-View-ViewModel MVVM-Pattern?

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 16 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Fragen zu NotifyPropertyChanged --- Model-View-ViewModel MVVM-Pattern?

    Moin,

    ich hab die letzten Tage versucht zu verstehen was das MVVM-Pattern ist und wir ich das nutzen kann. Grund ist der, dass ich ab einer gewissen Komplexität meiner Projekte Schwierigkeiten habe, diese weiter zu entwickeln, da ich den Überblick verliere. Nicht ganz aber es wird immer schwerer weiter zu machen.

    Mein Ziel ist es Projekte für Blazor zu schreiben. Mein Wissenstand jetzt nach Recherche-Reisen auf Google, Youtube, Stackoverflow, Github, ... .

    ... dass dort das Interface INotifyPropertyChanged benutzt wird, um UI und Daten zu "binden" -- "uptodate zu halten".

    Jetzt wollte ich mir erstmal INotifyPropertyChanged ansehen und bin dabei bei doc.microsoft.com gelandet mit diesem Codebeispiel (der Code kann komplett copypaste in ein C# Winforms Projekt übertragen werden. Alle Steuerelemente sind schon drin. Nur NAMESPACE anpassen mit eigenem Projektnamen)


    Ich hab dann das MS Beispiel mal für mich in C# Winforms nachgestellt und minimal geändert.

    In der Form sind 2xBtn 1xRichTextBox 2xDataGriedViews. Leider kein CopyPaste wie beim obigen Bsp.

    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Linq;
    7. using System.Runtime.CompilerServices;
    8. using System.Text;
    9. using System.Threading.Tasks;
    10. using System.Windows.Forms;
    11. namespace WF_MVVM_My_INotify
    12. {
    13. public partial class FrmTwo : Form
    14. {
    15. public FrmTwo()
    16. {
    17. InitializeComponent();
    18. DgvResult.DataSource = LstCounterClassObjs;
    19. DgvResultNoEvent.DataSource = LstCounterClassObjs_NoEvents;
    20. }
    21. readonly BindingList<CounterClass> LstCounterClassObjs = new BindingList<CounterClass>();
    22. readonly BindingList<CounterClass_NoEvents> LstCounterClassObjs_NoEvents = new BindingList<CounterClass_NoEvents>();
    23. //Normal Class
    24. public class CounterClass_NoEvents
    25. {
    26. public int Count { get; set; }
    27. public CounterClass_NoEvents(int count)
    28. {
    29. Count = count;
    30. }
    31. }
    32. //Same now as -> MVVM Class
    33. public class CounterClass : INotifyPropertyChanged
    34. {
    35. //-----EventStuff : INotifyPropertyChanged
    36. public event PropertyChangedEventHandler PropertyChanged;
    37. private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    38. {
    39. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    40. }
    41. //^^^^^EventStuff
    42. //Private ClassField
    43. private int countValue;
    44. //Public ClassProperty
    45. public int Count
    46. {
    47. get
    48. {
    49. return this.countValue;
    50. }
    51. set
    52. {
    53. if (value != this.countValue)
    54. {
    55. this.countValue = value;
    56. NotifyPropertyChanged();
    57. }
    58. }
    59. }
    60. //Constructor
    61. public CounterClass(int count)
    62. {
    63. Count = count;
    64. }
    65. }
    66. public CounterClass myCounterObj = new CounterClass(0);
    67. private void BtnFrm2Start_Click(object sender, EventArgs e)
    68. {
    69. myCounterObj.Count += 1;
    70. LstCounterClassObjs.Add(new CounterClass(myCounterObj.Count));
    71. LstCounterClassObjs_NoEvents.Add(new CounterClass_NoEvents(myCounterObj.Count));
    72. RtxbxResult.Text += $"{myCounterObj.Count}\n";
    73. }
    74. private void BtnChangeLastItem_Click(object sender, EventArgs e)
    75. {
    76. //Set the last Item to Zero -- with Event DatagriedView gets Updated -- without NOT
    77. var itemCounter = LstCounterClassObjs.Count;
    78. if (itemCounter > 0 )
    79. {
    80. LstCounterClassObjs[itemCounter-1].Count = 0;
    81. }
    82. var itemcounter_NoEvents = LstCounterClassObjs_NoEvents.Count;
    83. if (itemcounter_NoEvents > 0)
    84. {
    85. LstCounterClassObjs_NoEvents[itemcounter_NoEvents-1].Count = 0;
    86. }
    87. }
    88. }
    89. }​


    Ich hab zur schnellen Veranschaulichung ein .gif gemacht. Da sieht man was der Unterschied ist. Das ein DataGridView wird geupdated und das andere nicht.

    Jetzt meine Fragen:

    1. Ist das der programmiertechnisch schwierigste Teil des MVVM (das implementieren von INotifyPropertyChanged)?

    2. konkret in Blazor oder eventuell ja auch bei ASP.net: Wo kommt jetzt die BusinessLogic hin?
    a. Ins Model (hab eigntlich gedacht, dass Model soll auch wie die View ohne Methoden daherkommen, damit es dann super einfach In/Out DB de/serialisiert werden kann)
    b. alles in ViewModel das wird da dann aber auch schnell voll und unübersichtlich. Da ja auch noch das Konvertieren/Übergabe der Klassen ViewModel und Model stattfindet.
    c. In den ganzen Tutorial hab ich unterschiedliches gelesen. "..bloss nicht ins ViewModel ... vs ... Model super einfach keine Methoden..."

    3. Meiner bescheidenen Meinung nach müsste es nicht MVVM heißen sondern Model-LogikModel-View-ViewModel, wobei Model nur Propertys hat und LogiModel nur Methoden. Dann wird es aber auch schnell unübersichtlich, da man ja für jede Webside, jede Klasse auf einmal 5 Dateien hat.

    4. Wie schaff ich es mehr Übersicht in meine Projekte zu bekommen? Ist MVVM eins der wichtigsten Ecksteine wenn ich Blazor-Zeugs machen möchte?

    5. Gibt es etwas das ich noch vor MVVM Pattern angehen sollte, um mehr Struktur zu schaffen? Oder ist das der richtige Weg?
    Im Moment bin ich happy, dass ich OOP einiger maßen mache und Dont't Repeat Yourself ist etwas das ich verfolge. Alles natürlich so gut wie ich es kann ;)


    Bin leider aus einer anderen Branche als IT. Bin ziemlich alleine unterwegs auf meinen ProgrammierAbenteuern. Danke Euch für Ideen und Ratschläge.

    Hier noch das .gif
    Bilder
    • VB-BlazorINotify.gif

      518,71 kB, 806×482, 109 mal angesehen
    codewars.com Rank: 4 kyu
    zu 1: Nein - INotifyPropertyChanged ist noch eine der leichteren MVVM-Übungen. Man programmiert das auch nur einmal, und dann vererbt man es an alle Viewmodel-Klassen.

    Blazor, Asp.net kenne ich nicht.

    nogood schrieb:

    Wo kommt jetzt die BusinessLogic hin?
    Ins Viewmodel - nirgends sonst.

    nogood schrieb:

    In .. Tutorial hab ich ... gelesen. "..bloss nicht ins ViewModel..."
    HeideBimbam! - in welchem Tut haste das denn gelesen?

    nogood schrieb:

    Meiner bescheidenen Meinung nach müsste es nicht MVVM heißen sondern Model-LogikModel-View-ViewModel
    Naja - wenn dir Model-View-ViewModel nicht kompliziert genug ist - tu dir keinen Zwang an!

    nogood schrieb:

    Wie schaff ich es mehr Übersicht in meine Projekte zu bekommen? Ist MVVM eins der wichtigsten Ecksteine wenn ich Blazor-Zeugs machen möchte?
    MVVM ist ausgezeichnet geeignet, um Ordnung zu schaffen - wie gesagt: wie das im Zusammenhang mit Blazor aussieht kannichnich sagen.
    Aber guck dir mw. Kein Pong - Erstaunliches mit ItemsControl.Itemspanel an.
    Ich finde, den VB-Code kann jedermann verstehen. Eben weil das reine Logik ist, ohne iwelches Rumgefuchtel mit Controls, die das dann darstellen müssen.
    Ok, gegen schluss die Physik-Engine geht ans Eingemachte. Aber das liegt nicht an architektonischer Unordnung, sondern die Mathematik des schiefen Stosses ist numal sehr anspruchsvoll (findich).
    Oder gugge TicTacToe - da ist eiglich alles völlig banal (ausser der GetWinner-Methode, die im Tut nicht gezeigt wird).
    Und es bleibt banal, obwohl am Ende das Teil nicht nur die gemachten Züge anzeigt und den Gewinner ermittelt, sondern die Züge auch durchnumeriert und den Gewinner-Zug markiert - versuch sone Oberfläche mal mit WinForms!

    Allerdings habe ich eine etwas andere Auffassung von MVVM als @Nofear23m:
    Ich erstelle explizite Viewmodel-Klassen nur, wenn es für Viewmodel-Klassen auch was zu tun gibt. Ansonsten binde ich stantepede an Model-Klassen. Wodurch das dann ja keine Model-Klassen mehr sind, sondern gleichzeitig auch Viewmodel.
    Nofear hingegen folgt strikt der Vorschrift: An Model wird nicht gebunden - nur an Viewmodel. (Hat den Nachteil, dass viele Viewmodel-Klassen entstehen, die eigentlich nichts tun.)

    Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von „ErfinderDesRades“ ()

    @ErfinderDesRades Danke erstmal für deine Hilfe!
    @'ErfinderDesRades' Ins Viewmodel - nirgends sonst.


    Ich hab die Stelle wo ich das gelsen habe, dass BusinessLogik nicht ins ViewModel soll doch noch eben wiedergefunden!

    Hier mal der Link zu Stackoverflow Thema"MVVM: ViewModel and Business Logic Connection" und die excepted Answere mit 56 +Votes

    Putting business logic inside the viewmodel is a very bad way to do things, so I 'm going to quickly say never do that and move on to discussing the second option.

    Putting the logic inside the model is much more reasonable and it's a fine starting approach. What are the drawbacks? Your question says...



    :evil: :saint: oder eben andersherum :)

    ------
    Ist es den richtig, dass man alle Propertys dann so machen muss wie ich das im Beispiel gemacht habe mit "extra Setter" (der im Change-Fall dann das Event ausführt)?
    codewars.com Rank: 4 kyu

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „nogood“ ()

    Na, ok - ich nehms zurück und behaupte das Gegenteil: Busynesslogic immer ins Model - nie ins Viewmodel!

    Ist für mich nicht soo der Unterschied weil ich ja wie gesagt, Model und Viewmodel eher selten überhaupt auseinanderdividiere.

    Jo, INotifyPropertyChanged erzwingt alle Properties explizit ausprogrammiert.

    Aber c# muss man ja nicht so platzverschwenderisch programmieren - es kann ja auch sehr kompakt:

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Linq;
    7. using System.Runtime.CompilerServices;
    8. using System.Text;
    9. using System.Threading.Tasks;
    10. using System.Windows.Forms;
    11. namespace WF_MVVM_My_INotify {
    12. public partial class FrmTwo : Form {
    13. public FrmTwo() {
    14. InitializeComponent();
    15. DgvResult.DataSource = LstCounterClassObjs;
    16. DgvResultNoEvent.DataSource = LstCounterClassObjs_NoEvents;
    17. }
    18. readonly BindingList<CounterClass> LstCounterClassObjs = new BindingList<CounterClass>();
    19. readonly BindingList<CounterClass_NoEvents> LstCounterClassObjs_NoEvents = new BindingList<CounterClass_NoEvents>();
    20. public CounterClass myCounterObj = new CounterClass(0);
    21. private void BtnFrm2Start_Click(object sender, EventArgs e) {
    22. myCounterObj.Count += 1;
    23. LstCounterClassObjs.Add(new CounterClass(myCounterObj.Count));
    24. LstCounterClassObjs_NoEvents.Add(new CounterClass_NoEvents(myCounterObj.Count));
    25. RtxbxResult.Text += $"{myCounterObj.Count}\n";
    26. }
    27. private void BtnChangeLastItem_Click(object sender, EventArgs e) {
    28. //Set the last Item to Zero -- with Event DatagriedView gets Updated -- without NOT
    29. var itemCounter = LstCounterClassObjs.Count;
    30. if (itemCounter > 0) { LstCounterClassObjs[itemCounter - 1].Count = 0; }
    31. var itemcounter_NoEvents = LstCounterClassObjs_NoEvents.Count;
    32. if (itemcounter_NoEvents > 0) { LstCounterClassObjs_NoEvents[itemcounter_NoEvents - 1].Count = 0; }
    33. }
    34. } // class FrmTwo
    35. public class CounterClass_NoEvents {
    36. public int Count { get; set; }
    37. public CounterClass_NoEvents(int count) { Count = count; }
    38. } // class CounterClass_NoEvents
    39. public class CounterClass : NotifyPropertyChanged {
    40. private int _Count;
    41. public int Count { get { return _Count; } set { ChangePropIfDifferent(value, ref _Count); } }
    42. public CounterClass(int count) { Count = count; }
    43. } // class CounterClass
    44. public class NotifyPropertyChanged : INotifyPropertyChanged {
    45. public event PropertyChangedEventHandler PropertyChanged;
    46. protected void ChangePropIfDifferent<T>(T value, ref T backingField, [CallerMemberName] String propertyName = "") {
    47. if (object.Equals(value, backingField)) return;
    48. backingField = value;
    49. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    50. }
    51. } // class NotifyPropertyChanged
    52. }

    Also 2 Zeilen pro Property - Ich find, das kann man aushalten.:

    C#-Quellcode

    1. private int _Count;
    2. public int Count { get { return _Count; } set { ChangePropIfDifferent(value, ref _Count); } }
    Sieht anfangs bischen krank aus - aber weils immer immer dasselbe ist, gewöhnt man sich dran. ich hab mir da sogar ein Snippet für gebastelt.

    Was ich dir empfehlen würde, wenn du mehrere Klassen in eine Datei stopfst - so EndKommentare:

    C#-Quellcode

    1. public class CounterClass_NoEvents {
    2. public int Count { get; set; }
    3. public CounterClass_NoEvents(int count) { Count = count; }
    4. } // class CounterClass_NoEvents
    5. public class CounterClass : NotifyPropertyChanged {
    6. private int _Count;
    7. public int Count { get { return _Count; } set { ChangePropIfDifferent(value, ref _Count); } }
    8. public CounterClass(int count) { Count = count; }
    9. } // class CounterClass
    Sonst verhaspelt man sich bei c# total schnell mit den }, die da überall herumfahren, und die sich vlt. auf eine { beziehen, die 100 Zeilen weiter oben ist..

    ach, und hier noch ein klein:

    C#-Quellcode

    1. private void BtnChangeLastItem_Click(object sender, EventArgs e) {
    2. LstCounterClassObjs.LastOrDefault()?.Count = 0;
    3. LstCounterClassObjs_NoEvents.LastOrDefault()?.Count = 0;
    4. }

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „ErfinderDesRades“ ()

    @ErfinderDesRades Tausenddank für die ausführliche, aussagekräftige Antwort. Ich werde das so umsetzen.
    Danke für das Zeigen, dass das auch Platzsparender geht. Mein Code-Beispiel hat mich schon arg abgeschreckt MVVM gut zu finden.

    Also:
    1. Schreiben einer NotifyPropertyChanged : INotifyPropertyChanged Klasse inkl. Event und alle Klassen erben das dann. Anstatt bei jedem Property die If Abfrage ob "Value Changed"
    2. 1-Zeilen-If - (keine Ahnung ob ich mich daran gewöhnen kann) :)
    3. 2-Zeilen Property - Werde ich so machen
    3. Kommentierung in der Zeile der Closing-Class Klammer super Idee
    4. Null-conditional Abfrage mit ? JoJo vergesse ich immer wieder zu benutzen und auch If X ?? A : B etc.


    Hab ich alles gesehen?

    In deiner ersten Antwort sagtest Du, dass die Sache mit dem Property-Change nicht unbedingt das Schwierigste am MVVM Pattern ist. Müsste man mir als Anfänger auf dem Gebiet noch unbedingt etwas mit auf den Weg geben, oder ruckelt sich das mit mehr Übung zurecht?

    Es ist ja nicht so, dass mir die Fragen ausgehen.

    Ist so zusagen ab jetzt Schluss mit der Benutzung von List<T> und ich nehme ab jetzt dann immer BindingList<T> oder verstehe ich da im Moment noch was falsch. Ich frag nur, da ich gefühlt zu 99% in allen meinen bisherigen Projekten immer List<T> benutze und noch fast nie eine BindingList<T>. Das würde sich jetzt umdrehen richtig?
    codewars.com Rank: 4 kyu

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „nogood“ ()

    nogood schrieb:

    1-Zeilen-If - (keine Ahnung ob ich mich daran gewöhnen kann)
    Dassis aberdoch ziemlich cool.
    Dazu noch eine Regel: Wenn es nur einen Befehl enthält, kann man die Klammern weglassen.
    Dann aber wirklich einzeilig schreiben - manchmal sieht man ja sowas:

    C#-Quellcode

    1. if (displaySelector1 == null)
    2. displaySelector1 = o => o.ToString();
    Da übersieht man sehr schnell, dass zeile#2 ja nur bedingt ausgeführt wird.

    Hingegen auch mehrere Anweisungen gehen in eine Zeile, wenn sie nicht zu lang sind:

    C#-Quellcode

    1. if (displaySelector1 == null) { displaySelector1 = o => o.ToString(); return; }




    Wie gesagt: Ich keine Ahnung, wie Blazor ist. Wenns wie Wpf ist, dann gibts im Wpf-Bereich so einige Tuts. Wo "Grundlagen", oder "MVVM" dransteht - da kann man immerhin mal reingucken.



    In Wpf verwendet man übrigens lieber die ObservableCollection<T> - das ist eine modernere Variante der BindingList<T>
    Moinsen.

    Ich möchte eine vereinfachte Darstellung einmal Kund tun.

    Das MVVM ist eine Torte mit drei Schichten.

    Die Glasur ist die View. Das ist da, wo die Kinder immer drauf datschen, sowie der Programmbenutzer auf die Knöpfe der UI.

    Das ViewModel ist die Füllung. Das ist das "eigentliche Programm", wo die leckeren Sachen sind. Hier geht die Geschmacksverirrung los... :)

    Das/die Model(s) sind die Böden der Torte, oder Kuchen je nach Region...

    Um das "MVVM-Prinzip" einzuhalten, gibt es "Glaubenskriege", wo was in welcher Tortenschicht ist. Dazu später mehr...

    Das Wichtigste ist, die Trennung der Zuständigkeiten, die mittels Klassen erziehlt wird.

    Jetzt wird aber mit der Spaltung, keine Torte sehr geschmackvoll.

    Daher werden Techniken benutzt, die eine "lose Kopplung" ermöglichen.

    Von der View zu den ViewModels werden Databindings benutzt, und diese brauchen Unbedingt das "INotifyPropertyChanged-Event".

    Analogie:

    Zwischen View und ViewModel
    , die meist "1:1" oder "1:n" vorliegen, werden kleine Röhrchen gelegt.

    Diese Röhrchen sind die DataBindings.

    Wird nun Etwas am "Property" (Eigenschaft im ViewModel) geändert,
    klopft es am Röhrchen, und signalisiert nur das sich Etwas geändert hat, und das genau auf dem benannten "Property".

    Somit kann die "lose Kopplung" hergestellt werden.

    Analogie-Ende...

    Zum Thema "Glaubenskrieg"...

    Es gibt immer Anwendungsfälle, wo Ausnahmen erlaubt sind und das Prinzip umgangen wird.

    Meine Ansicht dazu ist folgende:

    In meinen ViewModels findet auch die Businesslogik statt, aber nicht immer...

    Daher ist bei mir das ViewModel "Das Programm".

    Bei der Kopplung vom ViewModel(s) zu den Model(s) ist nur das ViewModel zuständig.

    In den meisten Fällen sind die Models eher als komplexe Variablen angelegt, mit wenig bis gar keinen Klassen-Methoden (Klassen-Funktionen).

    Zwischen diesen beiden Schichten wird nicht mit Databinding gearbeitet, weil das ViewModel die Ereignisse erzeugt, und nicht das Model.

    Es gibt sehr oft Gründe das anders zu handhaben.

    Gängiges Beispiel ist der Zugriff auf externe Daten, wie Datenbanken, wo es wenig Sinn macht das auch noch in dem ViewModel einzubauen, wenn es das Model schon gut erledigen kann.

    Zudem kann der Vorteil genutzt werden, nur das Model abzuändern, statt das ViewModel (Programm), wenn nur der Datenbank-Anbieter sich geändert hat.



    Wichtig ist es, das "Code-Behind" abzugrenzen.

    Im Code-Behind darf und sollte nur das Programmiert werden, was Sachlich nur im View geschieht/manipuliert werden soll.

    Konkreter.
    Das Scrollen in einer ListBox macht mehr Sinn im Code-Behind, als im ViewModel.
    Was in Vegas passiert, bleibt in Vegas. => Folglich: Was in der UI passiert, bleibt in der UI.



    Weiterer Beitrag zum Thema Commands in MVVM.

    vb-paradise.de/index.php/Threa…ostID=1116044#post1116044


    Ich hoffe das hilft ein wenig... Ist ja noch Früh am Morgen...

    c.u. Joshi aus Hamburg, Hey und Hi und so... :whistling:
    Hallo

    Da ich markiert wurd klinke ich mich auch kurz mal ein.
    Was ErfinderDesRades sagte stimmt soweit, auch das ich hier "strenger" bin was MVVM und die Layertrennung angeht, alleine schon um der Testbarkeit wegen.
    Aber ich will darauf jetzt gernicht näher eingehen, auch nicht auf das thema wo die Businesslogik hin soll, denn das kommt für mich aufs Projekt an ob hier ein weiterer Layer geschaffen wird.

    Denn:

    nogood schrieb:

    Mein Ziel ist es Projekte für Blazor zu schreiben.

    Wenn wir hier über Blazor oder ASP.Net reden dann reden wir ja sowieso am Thema vorbei. Warum? Weil du in diesem Fall ja erst gar kein iNotifyPropertyChanged benötigst.
    Bei Blazor bin ich jetzt nicht zu 100% sicher aber bei ASP.Net und ASP.Net Core auf jeden Fall nicht. Blazor Basiert aber auch auf das MVC Pattern (nicht verwechseln mit MVVM - ja sind beides Pattern, also Rezepte).

    MVC = Model - View - Controller

    Wozu hier iNotrifyPropertyChanged wenn es dir nicht bringt auf eine änderung einzugehen da die View nicht direkt gebunden ist sondern eine lose Binding besitz. Du bekommst beim Post das Model in dem Zustand zurück wie es in veränderter Form ist, aber direkte änderungen an einer Eigenschaft bekommst du nicht mit bis der Post ausgeführt wird. somit ist das hinfällig, deshalb gibt es auch kein ViewModel im klassischem Sinne wie es bei MVVM der Fall wäre, sondern eben nur ein Model, jetzt kann man (oder sollte auch des öfteren) eine Klasse schaffen welche Logik in Verbindung mit dem Model schafft. Ob man die nun Businesslogik oder ViewModel-Klasse nennt sei jedem selbst überlassen, ich nenne diese bei ASP.Net Projekte gerne ViewModel weil ich es einfach gewohnt bin aus der Welt der WPF, muss aber nicht so sein. aber diese Klassen implementieren kein iNotifyPropertyChanged. Wozu auch, es bringt dir genau nichts.

    Falls hier weiter über MVVM wie es in der WPF zum Einsatz kommt oder über die Verwendung einer Businesslogik geplaudert werden will bin ich ab sofort gerne dabei und habe hierzu natürlich auch eine Meinung, aber ich will nicht so weit vom Uhrsprungsthema wegkommen.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    @Joshi Danke für das schöne Veranschaulichen des MVVM Patterns :thumbsup: Ich bin wirklich am Anfang davon Pattern zu benutzen egal welches. Das theoretische Verstehen ist ganz okay (Tutorial durchlesen/schauen), aber bei der Umsetzung und den Auswirkungen von Entscheidungen fehlt es deutlich. Und wenn ich unterschiedliche Meinungen zu dem Thema sehe kann ich halt so gar nicht entscheiden was "richtiger" wäre. Was wann besser stimmt. So wie Du es geschrieben hast, verstehe ich MVVM auch, also nicht als echte Torte :) sondern die Layer mit den unterschiedlichen Aufgaben.

    @Nofear23m Danke das Du hier schreibst. Wie ich eben schon geschrieben habe bin ich in der Phase, dass das Thema Pattern mich noch gedanklich überfordert. So ähnlich wie am Anfang als ich mit VB.net oder C# angefangen habe (vor 2 Jahren). Ja, ich möchte gerne Anwendungen für Blazor schreiben. ASP.net finde ich bei der Suche nach Blazorinfos natürlich im Internet immer viel (da das wohl schon lange "am Markt ist"). Das dort MVC benutzt wird, hab ich schon wahrgenommen. Ich hab dazu gelesen, dass MVVM "besser" wäre als MVC und das Blazor nicht extra für MVC ausgelegt ist und ich bin mir nicht sicher, ob MVC das Pattern der Wahl ist für Blazor? Falls es so wäre bitte mir nochmal deutlich sagen, da ich MVC im Moment eher ignoriere (weil es halt noch mehr "neues" wäre).
    Es ist mir im Moment nicht klar, ob Blazor jetzt INotifyPropertyChanged benötigt oder eben doch nicht.

    Ich Poste hier mal die Stellen, wo ich meine Wissenskrümmel her hab, falls Ihr Lust und Zeit habt könnt Ihr ja kurz rüber schauen.

    YoutubeVideos:

    Carl Franklin's Blazor Train: MVVM Part 1
    // 15 Min -> verstehe ich und werde das mal so nach bauen mit leichten Änderungen
    Carl Franklin's Blazor Train: MVVM Part 2
    // 23 Min -> relativ schnell nix mehr geblickt hab das aber auch nur einmal geschaut und dann gedacht ich mal erstmal Teil1

    Curious Drive
    Blazor #MVVM #AcrhitectureBlazor WebAssembly : MVVM Architecture - EP07
    //20 Min -> verstehe ich und hab das in Teilen nachgeahmt

    Dazu hab ich unteranderem diesen Ansatz: MvvmBlazor Github // NuGet Projekt:
    BlazorMVVM is a small framework for building Blazor and BlazorServerside apps. With it's simple to use MVVM pattern you can boost up your development speed while minimizing the hazzle to just make it work.


    Naja in diesem Wissen-Baramuda-Dreieck schaukele ich her rum.
    -------------
    Ich merke beim schreiben hier gerade, dass ich wohl nicht weiß was "binding" wirklich ist und macht :cursing: .

    Hier noch kurz mein aktuellster Blazor MVVM Test:
    View:
    Spoiler anzeigen

    HTML-Quellcode

    1. ​@page "/"
    2. @inject ICustomerViewModel CustomerViewModel
    3. <div class="col-12">
    4. <input type="text" @bind-value="CustomerViewModel.Id" class="form-control" placeholder="SearchTerm ID" />
    5. <br />
    6. <input type="text" @bind-value="CustomerViewModel.LastName" class="form-control" placeholder="last name" />
    7. <br />
    8. <input type="text" @bind-value="CustomerViewModel.BirthYear" class="form-control" placeholder="birth year" />
    9. <br />
    10. <button class="btn btn-primary" @onclick="(() => CustomerViewModel.GetProfile(CustomerViewModel.Id))">Get Profile</button>
    11. <button class="btn btn-primary" @onclick="CustomerViewModel.UpdateProfile">Update Profile</button>
    12. <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>@CustomerViewModel.Message
    13. <br />
    14. <br />
    15. <p>Age:<span>&nbsp;&nbsp;</span>@CustomerViewModel.Age</p>
    16. </div>


    ViewModel zur Page oben:

    Spoiler anzeigen

    C#-Quellcode

    1. using BZ_MVVM_FirstTry.Controller;
    2. using BZ_MVVM_FirstTry.Models;
    3. using System;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using System.Threading.Tasks;
    7. namespace BZ_MVVM_FirstTry.ViewModels
    8. {
    9. public class CustomerViewModel : ICustomerViewModel
    10. {
    11. private readonly MongoDbApi _mongoDbApi;
    12. public CustomerViewModel(MongoDbApi mongoDbApi)
    13. {
    14. _mongoDbApi = mongoDbApi;
    15. }
    16. public int Id { get; set; }
    17. public string LastName { get; set; }
    18. public int BirthYear { get; set; }
    19. public int Age { get; set; }
    20. public string Message { get; set; }
    21. // Convert CustomerViewModelObj -> CustomerObj // so that I can use "this" -> _mongoDbApi.UpdateCustomerById(Id, this);
    22. public static implicit operator Customer(CustomerViewModel customerViewModel)
    23. {
    24. return new Customer(customerViewModel.Id, customerViewModel.LastName, customerViewModel.BirthYear);
    25. ////TODO warum geht das nicht auch so ?!?
    26. //return new Customer
    27. //{
    28. // Id = customerViewModel.Id,
    29. // LastName = customerViewModel.LastName,
    30. // BirthYear = customerViewModel.BirthYear
    31. //};
    32. }
    33. public void UpdateProfile()
    34. {
    35. var match = _mongoDbApi.UpdateCustomerById(Id, this);
    36. if (match != null)
    37. {
    38. LastName = match.LastName;
    39. BirthYear = match.BirthYear;
    40. Age = match.Age;
    41. Message = "CustomerProfile updated successfully";
    42. return;
    43. }
    44. Message = "CustomerProfile not found";
    45. }
    46. public void GetProfile(int id)
    47. {
    48. var match = _mongoDbApi.GetCustomerById(id);
    49. if (match != null)
    50. {
    51. LastName = match.LastName;
    52. BirthYear = match.BirthYear;
    53. Age = match.Age;
    54. Message = "CustomerProfile loaded successfully";
    55. return;
    56. }
    57. Message = "CustomerProfile not found";
    58. }
    59. }
    60. }



    und das Model:

    Spoiler anzeigen

    C#-Quellcode

    1. ​using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Threading.Tasks;
    5. namespace BZ_MVVM_FirstTry.Models
    6. {
    7. public class Customer
    8. {
    9. public int Id { get; set; }
    10. public string LastName { get; set; }
    11. public int BirthYear { get; set; }
    12. public int Age { get => CalcAge(); }
    13. public Customer(int id, string lastName, int birthYear)
    14. {
    15. Id = id;
    16. LastName = lastName;
    17. BirthYear = birthYear;
    18. }
    19. private int CalcAge()
    20. {
    21. return DateTime.Now.Date.Year - this.BirthYear;
    22. }
    23. }
    24. }


    und noch das der fake MongoDatenBank-Zugriff

    Spoiler anzeigen

    C#-Quellcode

    1. using BZ_MVVM_FirstTry.Models;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Threading.Tasks;
    6. namespace BZ_MVVM_FirstTry.Controller
    7. {
    8. public class MongoDbApi
    9. {
    10. //MongoDB :)
    11. public List<Customer> Customers = new List<Customer>
    12. {
    13. new Customer(1,"Growy", 2007),
    14. new Customer(2,"Slacken", 1991),
    15. new Customer(3,"Dot", 2010),
    16. new Customer(4,"Brown",1975),
    17. };
    18. //Call to MongoDB
    19. public Customer GetCustomerById(int id)
    20. {
    21. foreach (var c in Customers)
    22. {
    23. return Customers.Where(cust => cust.Id == id).FirstOrDefault();
    24. }
    25. return null;
    26. }
    27. public Customer UpdateCustomerById(int id, Customer customer)
    28. {
    29. foreach (var c in Customers)
    30. {
    31. var upatedCustomer = Customers.Where(cust => cust.Id == id).FirstOrDefault();
    32. if (upatedCustomer != null)
    33. {
    34. upatedCustomer.Id = customer.Id;
    35. upatedCustomer.LastName = customer.LastName;
    36. upatedCustomer.BirthYear = customer.BirthYear;
    37. return upatedCustomer;
    38. }
    39. }
    40. return null;
    41. }
    42. }
    43. }​



    Also UI-Page hat keinen "Code" außer Bindings. ViewModel get/sets Model Variablen. Model ist getrennt vom View

    Ist das dann doch schon alles und ich brauch gar kein INotifyPropertyChange etc...

    Hier noch kurz ein .gif was das Ding macht

    Bilder
    • VB-BZ-FirstMVVM.gif

      764,81 kB, 504×380, 89 mal angesehen
    codewars.com Rank: 4 kyu
    Hallo

    die Videos habe ich jetzt noch nicht angesehen, aber du sagst es ja richtig, dein code implementiert kein iNotifyPropertychanged. wird auch nicht benötigt.
    Im Grunde ist das (wenn man so will) MVVM obwohl es genauso im MVC Pattern gemacht werden könnte. Je nachdem was man braucht.

    Entweder willst du eine Schicht drüber oder nicht.
    Wie Joshi super erklärt hat ist ein Pattern ein Rezept - Du kannst eine Bohnensuppe auf viele verschiedene Arten machen. die Grundzutaten sind immer gleich. du enscheidest ob du verfeinerst, obs scharf oder mild sein soll usw.
    du musst für das jeweilige Projekt (!!) den besten Weg finden.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Also das @bind-value="xxx" int

    HTML-Quellcode

    1. <input type="text" @bind-value="CustomerViewModel.Id" class="form-control" placeholder="SearchTerm ID" />
    verstehe ich als Binding - also was im Wpf einem DataBinding entspricht.
    Es ist aber wohl kein Databinding im Wpf-Sinne - vermutlich merkt die Site (im gegensatz zu einem Wpf-Window) nicht automatisch, wenn die Property changed.
    Dassis der Unterschied.
    Also Blazor hat Databinding ohne INotifyPropertyChanged (nicht, dass @Nofear23m das nicht schon gesagt hätte ;) ).

    @TE: Ich würde mich an das MVVM halten, wie es hier im Forum verstanden wird. Da gibts Leuts, die können was dazu sagen.
    Ich würde nicht soo viel in er Welt rumschauen, was andere Kopfeten zum Thema erlassen haben.
    Aber natürlich Blazor-Sites konsultieren - dazu kann dir hier höchstens @Nofear23m helfen.

    Insbesondere auf Youtube habich bislang zu 99% Mist gesehen. Wenns um vb.net ging - vlt. hat die Blazor-youtube-Gemeinde ja ein besseres Niveau.
    Hey, in diesem Thema kam jetzt ja wirklich schon einiges an Antworten und Content, vieles davon auch schon richtig. Ich will jetzt gar nicht groß daherkommen und bereits gesagtes wiederholen. Was mir, und vielen anderen Anfängern, damals beim Thema MVVM am meisten gefehlt hat, war das generelle Verständnis, warum man das Pattern benutzt und wo genau die Grenzen zwischen den 3 Schichten (M-V-VM) liegen. Meiner Meinung nach ist letzteres das Wichtigste an dem ganzen Thema und gleichzeitig das, was es Anfängern wirklich schwer macht. Und es deckt sich auch mit einigen Fragen von dir:

    nogood schrieb:

    Müsste man mir als Anfänger auf dem Gebiet noch unbedingt etwas mit auf den Weg geben [...]?


    nogood schrieb:

    Meiner bescheidenen Meinung nach müsste es nicht MVVM heißen sondern Model-LogikModel-View-ViewModel, wobei Model nur Propertys hat und LogiModel nur Methoden. Dann wird es aber auch schnell unübersichtlich, da man ja für jede Webside, jede Klasse auf einmal 5 Dateien hat.



    Ich gehe erstmal komplett abstrakt an das Thema. MVVM ist ein Pattern, eine Art "Guideline" Code zu organisieren um bestimmte Vorteile zu erreichen. Was wichtig ist: Patterns sind komplett unabhängig von der Programmiersprache! Du kannst MVVM in C# benutzen, aber auch in Java, JavaScript, etc. Deshalb ist es wichtig, das Pattern selbst zu verstehen - ohne Implementierungsdetails. INotifyPropertyChanged ist zum Beispiel nur eine von vielen Möglichkeiten, MVVM in C# umzusetzen (die eben in C# so gut wie immer genutzt wird, da pseudo-standardisiert).

    High Level Overview

    @Joshi hat es schon geschrieben, aber ich wiederhole es nochmal und versuche, mehr darauf einzugehen. Welche Vorteile bringt MVVM und wie setzt es sich zusammen?

    Wofür MVVM?
    Der Grund, warum MVVM am häufigsten eingesetzt wird ist, dass die Business Logic, die Kernfunktionalität einer App, idealerweise getrennt von dem Code ist, der sich um das View, das User Interface, kümmert. Gründe dafür gibt es viele, aber der wichtigste ist, dass du durch die Trennung unfassbar flexibel wirst. Wenn deine Business Logic auf einmal nichts mehr mit dem UI zu tun hat bedeutet das, dass du sie sehr einfach in anderen Projekten wiederverwerten kannst. Ein Beispiel dafür hast du vor ein paar Wochen selbst gebracht: Du hast Business Logic deines MongoDB-Projektes in eine Library verpackt und in 2 verschiedenen Projekten mit 2 verschiedenen UIs benutzt, einmal Blazor und einmal WinForms. Der größte Vorteil von MVVM ist derselbe: Trenne die verschiedenen Bereiche einer App mit UI in die zwei Kategorien "UI Code" (-> View) und "Business Logic Code" (-> Model) und erhalte dadurch Vorteile wie Wiederverwertbarkeit, erleichtertes Testen (ein wichtiger Punkt!), leichteres Austauschen der Business Logic (zum Beispiel ein leichter Wechsel von einer DB zu einer anderen), etc.
    Das Problem ist an diesem Punkt allerdings, dass die 2 Segmente "View" und "Model" bisher nichts miteinander zu tun haben. Du hast zwar beispielsweise ein UI mit Buttons, Textboxen und fancy Animationen und gleichzeitig ein Projekt, mit dem du die Daten für das UI auslesen und erstellen kannst (also deine Business Logic), aber es fehlt die Brücke zwischen den beiden. Dein UI kann die Daten nicht anzeigen, da es nichts von dem "Model" Bereich kennt. Hier kommt das "ViewModel" als Brücke, als Kommunikator zwischen den beiden Bereichen ins Spiel und sorgt dafür, dass dein View am Ende des Tages auch etwas tut, wenn Buttons gedrückt werden. Und ab jetzt wird es komplizierter.

    Die Rollen der 3 Schichten
    Bevor es zum ViewModel geht, nochmal ein kleiner Schritt nach hinten, um klar zu definieren, was die anderen beiden Schichten machen.

    Das View: Das View ist der, und nur der, Code, der sich ausschließlich mit dem UI beschäftigt. Dazu gehören zum Beispiel in WPF XAML, in Web Apps HTML/CSS und alles, was "Bewegung" in dein UI bringt (also zum Beispiel in Web Apps JavaScript-Code, mit dem du Animationen steuerst, wenn du auf einen Button klickst). Der Code hat eines gemeinsam: Er ist unglaublich schwer zu testen. Versuch zum Beispiel mal, einen Test für einen Button zu schreiben, der sicherstellt, dass sich die Farbe eines geklickten Buttons für 2 Sekunden in grün ändert. Faktisch unmöglich bzw. sehr schwer. View Code wird deshalb sehr gerne manuell getestet (mit den Augen sieht man eben direkt, ob sich die Farbe ändert oder nicht).

    Das Model: Das Model ist die gesamte Business Logic deiner App. Und das ist ein wichtiger Punkt! In deinem ersten Post hast du gefragt, warum das Pattern nicht "Model-LogikModel-View-ViewModel" heißt. Das liegt daran, dass das Wort "Model" sehr generisch ist. Du denkst bei deiner Frage wahrscheinlich an Entity Framework oder einfach Datenbank Models. Das sind stupide Datenklassen, die potentiell ein bisschen Funktionalität haben. Aber MVVM versteht unter "Model" viel mehr, nämlich, wie gesagt, alles, was mit der Business Logic zu tun hat. Das bedeutet beispielsweise, dass Entity Framework Models ein Teil des MVVM Models sind. Aber eben auch der dazugehörige DBContext. Auch alles was beispielsweise mit Settings zu tun hat, oder mit Internetkommunikation, mit externen Events, ...

    Das ViewModel: Hier kommen wir zum interessanten Teil. Der Name "ViewModel" sagt schon ganz genau aus, was es denn eigentlich tut:
    > Das ViewModel modelliert das View.
    Nicht mehr und nicht weniger. Das ganze ist allerdings, natürlich, sehr abstrakt. Was genau steckt denn jetzt dahinter?
    Das ViewModel ist der Kleber zwischen deinem View und deinem Model. Dein ViewModel "kennt" dein View implizit. Implizit insofern, als dass das ViewModel weiß, wie dein View "aussieht", also welche Daten das View braucht, welche Aktionen passieren sollen, wenn der User etwas im UI macht, aber nicht weiß, wie dein View genau programmiert ist (also mit welcher UI Technologie). Das ganze ist jetzt wieder recht abstrakt und am besten an einem Beispiel erklärt:



    Auf dem Bild siehst du ein View. Das View hat 3 Bereiche, die Funktionalität bereitstellen. (1) Der Title. (2) Den String des neuen Namens, der eingegeben werden kann. (3) Eine Aktion um die Änderung zu speichern.
    Ein ViewModel modelliert genau das: Es weiß, dass es einen Titel gibt, es weiß, dass es einen String gibt und es weiß, was passieren soll, wenn der Button zum Namen ändern gedrückt wird. Aber es weiß nicht, wie das View genau implementiert ist. Es weiß nicht, wie der Code zum Updaten des Titels im View aufgerufen wird. Es weiß nicht, wie die Änderungen im View (beispielsweise die Änderung des Textfeldes) zum ViewModel selbst kommen. Ein ViewModel ist, in C# als Beispiel, nichts anderes als eine Klasse die vereinfacht so aussehen kann:

    C#-Quellcode

    1. class ViewModel : INotifyPropertyChanged {
    2. public event PropertyChangedEventHandler PropertyChanged;
    3. private string _title;
    4. public string Title {
    5. get => _title;
    6. set {
    7. _title = value;
    8. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
    9. }
    10. }
    11. private string _newName;
    12. public string NewName {
    13. get => _newName;
    14. set {
    15. _newName = value;
    16. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewName)));
    17. }
    18. }
    19. // Anmerkung:
    20. // "Aktionen" werden in C# oft als sogenannte Commands implementiert.
    21. // Mit neueren UI Technologien (Blazor, WinUI, UWP) muss man das nicht mehr.
    22. // Nur der Vollständigkeit halber.
    23. public void ChangeName() {
    24. // Fiktiver Aufruf des "Models" wo noch ganz ganz ganz viel Business Logic passieren kann.
    25. new DatabaseConnection().UpdateNameOfCurrentUser(NewName);
    26. // Der Titel muss sich auch im View updaten:
    27. Title = $"Hallo {NewName}!";
    28. }
    29. }


    Das coole daran ist, dass das ViewModel somit quasi dein View repräsentiert und testbar ist. Die Klasse enthält alle Infos die das View braucht um richtig zu funktionieren, aber ist völlig davon entkoppelt. Das sorgt dann dafür, dass du das ganze super einfach testen kannst. Als Beispiel hier Code, der testet, ob der Titel des Views tatsächlich geupdated wird, wenn der Button gedrückt würde:

    C#-Quellcode

    1. [TestCase]
    2. public void Test() {
    3. var viewModel = new ViewModel();
    4. // Wir können die Texteingabe im Feld simulieren:
    5. viewModel.NewName = "Peter";
    6. // Wir können das klicken des Buttons simulieren:
    7. viewModel.ChangeName();
    8. // Wir können sehen, dass sich der Titel geändert hat:
    9. Assert.Equal(viewModel.Title, "Hallo Peter!");
    10. }


    Wie du siehst ist es möglich, den kompletten "UI Flow" zu testen, ohne jemals ein UI benutzen zu müssen. Und das ist wirklich schön. Aber es geht nicht nur ums Testen, sondern auch um andere Dinge. Da es ne Trennung zwischen UI und "Logik" gibt, kannst du zum Beispiel ganz einfach in Teams arbeiten. Ein Designer kann sich um das View kümmern während du als Entwickler für die tatsächliche Logik kümmert. Und ein dritter im Bunde sorgt dafür, dass die DB und Business Logic ans Laufen kommt. Und selbst wenn du nicht im Team arbeitest, wird die gesamte Projektstruktur dadurch um einiges übersichtlicher, also hast du selbst solo noch Vorteile davon.

    Putting Things Together
    Mittlerweile haben wir alle 3 Schichten: View, ViewModel und Model, aber eine Frage bleibt noch: Wie verkoppelt man View und ViewModel? Speziell diese Fragen:

    1) Wie bekommt das ViewModel updates aus dem View? (Beispiel: Woher weiß unser ViewModel, wenn sich der Name im Textfeld ändert oder wenn der Button gedrückt wird?)
    2) Wie bekommt das View Änderungen im ViewModel mit? (Beispiel: Woher weiß unser View, dass es das Titel-Label updaten muss, wenn in der ChangeName Methode die Title Property geändert wird?)

    Nun, das ganze ist Aufgabe des Views und hier gibt es starke Unterschiede abhängig davon, welche UI Technologie du benutzt. Alle haben aber gemeinsam, dass es auf beiden Seiten, also im View und im ViewModel, eine Benachrichtigung geben muss, wenn sich etwas ändert. Und hier kommt in C# INotifyPropertyChanged ins Spiel: Eine Klasse, die das Interface implementiert, schickt immer eine Benachrichtigung, ein Event ab, wenn es Änderungen am State gibt. Und das View hört, beispielsweise in WPF/UWP/WinUI, auf dieses Event und updated beim Reinkommen des Events alle UI Komponenten, die von den geänderten Properties abhängen.
    Das View kennt außerdem seine eigenen Events. Wenn beispielsweise ein Button gedrückt wird, weiß das View das - und kann dann zum ViewModel gehen und, hier, die ChangeName Methode aufrufen.
    In WPF/WinUI/UWP wird diese Interaktion zum Beispiel über sogenannte "Bindings" realisiert. Du "bindest" eine Property des ViewModels im UI Code (XAML) an dein View. Und das View (bzw. das Framework) macht dann im Hintergrund automatisch alles, was oben beschrieben ist: Es hört auf das PropertyChanged Event des ViewModels und updated automatisch die Properties im ViewModel, wenn sie sich im View updaten (zum Beispiel durch die Texteingabe).

    Zusammengefasst könnte der Flow des beschriebenen Programms zum Beispiel so aussehen:
    1. Die App startet.
    2. Eine neue ViewModel Instanz wird erstellt.
    3. Das View wird angezeigt und "verbindet" sich mit dem ViewModel. Es fängt automatisch an, auf Property changes zu hören.
    4. Der User macht eine Eingabe im View Textfeld. -> Das View bekommt das natürlich mit und updated automatisch die NewName Property im ViewModel.
    5. Der User klickt auf den Button. -> Das View bekommt das natürlich mit und ruft automatisch die ChangeName Methode im ViewModel auf.
    6. Das ViewModel übernimmt: Es triggert die nötige Business Logic im Model (die DB Änderung) und updated danach die Title Property so, dass es den neusten Stand in der DB repräsentiert. Dabei wird das PropertyChanged Event für Title gefeuert.
    7. Das View fängt das Event ab und weiß, dass es das Title-Label updaten muss, da es an die Title Property im ViewModel gebunden ist.

    Das ist im Groben MVVM. Eine Separierung der 3 Kategorien in jeweils unabhängige, wartbare Blöcke die dann per UI Framework verkoppelt werden. Das coole an der Separierung ist u.a. auch, dass du, wenn du es richtig machst, dein UI komplett austauschen kannst. Mit dem obigen View Model könntest du zum Beispiel sowohl ein Blazor Frontend, als auch eine WPF App zum Laufen bekommen, ohne eine Zeile ViewModel/Model Code zu ändern (fast, zumindest).


    Der Schritt zu Blazor
    Disclaimer: Ich bin kein Blazor Experte. Ich habe noch kein Blazor Projekt erstellt (auch wenn ich es vor habe), aber ich habe schon einiges an Code gelesen und aufgeschnappt und auch Erfahrung mit anderen SPA Frameworks wie React.

    Zuallererst: Blazor ist NICHT an MVVM gebunden. Blazor hat außerdem NICHTS mit MVC zu tun.
    Blazor IST hingegen komponentenbasiert (hier gibts Ähnlichkeiten zu React und dem assoziierten Top-Down Data Flow) und kann mit MVVM benutzt werden.
    Wichtig: Du musst MVVM nicht benutzen. Soweit ich weiß, gibt es noch einige andere Modelle, aber bevor ich darauf eingehe, würde ich mich lieber selbst erstmal damit beschäftigen (was allerdings noch ein paar Wochen dauern kann).

    Blazor kann, wie jede andere UI Technologie auch, dazu verleitet werden, mit ViewModels und .NETs INotifyPropertyChanged Interface zu interagieren. Du hast ja schon einen Link zu einem MVVM Projekt auf GitHub gepostet. Hab nur kurz drübergeschaut, aber wie es aussieht ermöglicht die Library Two-Way Bindings, also sowohl View -> ViewModel, als auch ViewModel -> View (Blazor selbst unterstützt mit @bind erstmal nur View -> ViewModel). Intern macht die Library nichts anderes, als auf das PropertyChanged Event des ViewModels zu hören und der Blazor Komponente diesen Change mitzuteilen, damit sie neu rendern kann (siehe github.com/klemmchr/MvvmBlazor…/MvvmComponentBase.cs#L71 - die StateHasChanged wird aufgerufen, wenn sich ein Binding, ausgelöst durch PropertyChanged, updatet. Das sorgt dann für einen neuen Render Cycle.).
    Sieht eigentlich nach ner ganz coolen Sache aus - wenn du Blazor mit MVVM nutzen willst, solltest du dir ernsthaft überlegen, die Lib einfach zu nutzen. Sieht ganz gut gepflegt aus für den Einstieg.

    Du hast außerdem noch ein Beispiel gepostet, wie ViewModels mit Blazor auch ohne INotifyPropertyChanged funktionieren. Ich glaube das nicht, bzw. zweifle an, dass es genau so funktioniert, wie du es dir vorstellst (hab es aber nicht probiert).
    Dein Beispiel funktioniert (wie man im GIF sehen kann), aber nur, weil Blazor dir versteckt unter die Arme greift. Damit will ich folgendes sagen:
    Dein UI updated sich automatisch, weil Blazor nach jedem Event einer Komponente (hier eben die Button Klicks) automatisch die Komponente neu rendert (siehe docs.microsoft.com/en-us/aspne…entions-for-componentbase ). Sprich, Blazor merkt dass du einen Button geklickt hast, löst deinen Callback aus, dein ViewModel updatet die Werte und Blazor rendert danach deine Komponente neu. Deshalb werden die Werte auch richtig angezeigt.
    Jetzt aber folgendes Szenario: Dein ViewModel updatet sich im Hintergrund, nicht durch eine UI Änderung (!), sondern beispielsweise durch ein internes Event deines Models, und die Name Property ändert sich - wie soll Blazor das denn jetzt mitbekommen? Die Antwort kann eigentlich nur "Gar nicht" sein, da dein ViewModel es nicht mitteilt.
    Kurz: Du musst irgendwie die Brücke zwischen ViewModel <> Blazor schaffen (eben über INotifyPropertyChanged), da Blazor sonst nicht in allen Szenarios die Updates bekommen wird. Und da kommt dann z.B. wieder die Library ins Spiel, die das automatisch für dich macht.


    Andere Anmerkungen, die nirgendwo richtig reinpassen
    Deine Frage im Code:

    C#-Quellcode

    1. // Convert CustomerViewModelObj -> CustomerObj // so that I can use "this" -> _mongoDbApi.UpdateCustomerById(Id, this);
    2. public static implicit operator Customer(CustomerViewModel customerViewModel)
    3. {
    4. return new Customer(customerViewModel.Id, customerViewModel.LastName, customerViewModel.BirthYear);
    5. ////TODO warum geht das nicht auch so ?!?
    6. //return new Customer
    7. //{
    8. // Id = customerViewModel.Id,
    9. // LastName = customerViewModel.LastName,
    10. // BirthYear = customerViewModel.BirthYear
    11. //};
    12. }


    Das geht nicht, weil dein Customer keinen parameterlosen Konstruktor hat (was gar nicht schlecht ist, weil es dich dazu zwingt, Werte anzugeben, die wichtig sind).

    Zum Thema @bind. Schau dir mal die Seite hier an: docs.microsoft.com/en-us/aspne…r/components/data-binding
    Ein interessantes Zitat von der Seite ist übrigens Folgendes:
    > "​The text box is updated in the UI only when the component is rendered, not in response to changing the field's or property's value. Since components render themselves after event handler code executes, field and property updates are usually reflected in the UI immediately after an event handler is triggered."

    Das bestätigt nochmal, was ich oben geschrieben habe und erklärt, warum dein Beispiel oben funktioniert. Blazor bindet zwar das UI -> ViewModel, aber bekommt Änderungen in die andere Richtung (ViewModel -> Blazor) nur "zufällig" mit, weil die Änderung durch ein UI-Event getriggert wurden. Deswegen nochmal die Vermutung: Wenn sich dein ViewModel asynchron ändert wird sich dein Frontend vermutlich nicht updaten. Die "korrekte" MVVM Implementierung per PropertyChanged event würde das ändern.


    Abschließend...

    ...hoffe ich, dass ich das Thema für dich nicht komplizierter gemacht habe. Das war jetzt sehr viel Text - Respekt wenn du ihn ganz gelesen und verstanden hast! Ich bin nicht immer der Beste im Erklären und hoffe einfach, dass ich das Thema weit genug runtergebrochen habe, damit du von hier an die Grundbausteine verstanden hast und auch weißt, warum manche Anleitungen online das tun, was sie eben tun.
    Wenn du Fragen hast stelle sie einfach - hab zur Zeit nicht viel Luft, aber hin und wieder schaue ich hier auch rein und kann antworten. Andere können vielleicht auch helfen und meinen Text hier korrigieren, wenn er zu kompliziert ist oder gar Fehler enthält (was hoffentlich nicht der Fall ist ^^ ).
    @shad

    shad schrieb:

    ... Ich bin nicht immer der Beste im Erklären und hoffe einfach, dass ich das Thema weit genug runtergebrochen habe,...


    größte Untertreibung von 2021 :thumbup:

    Was soll ich noch schreiben, ... Danke! Für die tolle, ausführlich und absolut strukturierte Antwort. Leider kann ich nur einmal auf "gefällt mir/hilfreich" klicken.

    Jetzt nach dem zweiten mal Lesen würde ich sagen, alle Fragen die in meinem Kopf rumgeschwirrt sind, sind beantwortet.


    Ich kann das jetzt nicht so gut die Du in Worte fassen (fehlende "Fachbegriffen" oder falsche Benutzung der selben). Aber das da etwas mit dem .gif-Beispiel-Code nicht ganz passt hatte ich schon im Gefühl. Aber ich konnte es nicht ausdrücken. Du hast mir ja alle Hinweise gegeben. Ich glaube mit dem neuen Wissen aus diesem Thread kann ich das jetzt neu angehen. Ich freu mich auf MVVM.
    Toll das Du auch eine Einschätzung zu dem Git-Repo dagelassen hast. Ich denke, dass werde ich dann nutzen, wenn ich noch besser verstehe was da passiert. Besonders an der Stelle die Du ja auch beschrieben hast. Wie funktioniert die Kommunikation/Update/Refresh von View <-> ViewModel im Fall das ein Property einen neuen Wert hat. Das ist eine Stelle an der es bei mir noch an Wissen fehlte.


    Danke, Danke, Danke
    Ich versprechen/versichere das Eure Energie/Arbeit nicht verloren geht - auf fruchtbaren Boden fällt.

    ---
    @shad deine Antwort inkl. der Posts der Vorposter hat Tutorial Qualität! ...
    codewars.com Rank: 4 kyu

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „nogood“ ()

    Da Ihr mir so ausführlich geholfen habt, hier eine Rückmeldung wie es läuft mit meinem Plan MVVM in einem Blazor Projekt zu benutzen. Ich hab im Grunde keine direkte Frage.

    Idee(reine Übungsaufgabe): Stoppuhr Componente umsetzen, wobei der Timer im Model sitzt. Also das UI muss aufgrund eines Events im Model geupdatet werden. Nicht nur aufgrund eines Btn-Clicks im UI.

    Außerdem würde das alle Fälle von Änderungen innerhalb des MVVM abdecken:

    View -> ViewModel -> Model (Name der StoppuhrInstanz wird im UI eingegeben und im Model gespeichert)

    Model -> ViewModel -> View (Verstrichene Zeit muss via Event vom Model ins UI)

    Gefühlslage: Das Ganze ähnelt im Moment eher "Seiltanzen" jeder Schritt/Zeile muss erkämpft/erarbeitet werden. Nicht wie bei einem schönen breiten Waldweg wo man mit den Gedanke woanders sein kann.

    Quellen:
    Natürlich dieser Thread (eure Antworten) :) und MVVM Github Repo wie schon oben i.wo erwähnt // Ich hab hier nur die Idee mit der Uhrzeit her genommen aber das Repo nicht benutzt. Das Umsetzen des Pattern hab ich hier her: BlogPost von Syncfusion die eine Implementierung von MVVM in Blazor zeigt.

    Hier nur mal die wichtigsten Teile:

    Model:
    Spoiler anzeigen

    C#-Quellcode

    1. //Model -> ViewModel : Notify that Event TimerOnElapsed has happend via : BaseViewModel
    2. public class StopWatchModel : BaseViewModel
    3. {
    4. private Timer _timer;
    5. private System.Diagnostics.Stopwatch _sw;
    6. private string _timerElapsedString;
    7. private string _finalSwString;
    8. public string SwName { get; }
    9. public string TimerElapsedString
    10. { get => _timerElapsedString;
    11. set => SetValue(ref _timerElapsedString, value);
    12. }
    13. public string FinalSwString
    14. {
    15. get => _finalSwString;
    16. set => SetValue(ref _finalSwString, value);
    17. }
    18. public void Start()
    19. {
    20. _timer = new Timer(50);
    21. _timer.Elapsed += TimerOnElapsed;
    22. _timer.Start();
    23. _sw = new System.Diagnostics.Stopwatch();
    24. _sw.Start();
    25. }//Start StopWatch and Timer
    26. private void TimerOnElapsed(object sender, ElapsedEventArgs e)
    27. {
    28. TimeSpan tSpan = _sw.Elapsed;
    29. TimerElapsedString = tSpan.ToString();
    30. }//Event every xx ms Timer has finished -> update UI at this point
    31. public void Stop()
    32. {
    33. _timer.Stop();
    34. _sw.Stop();
    35. _finalSwString = _sw.Elapsed.ToString();
    36. }//Stop StopWatch
    37. public StopWatchModel(string swName)
    38. {
    39. SwName = swName;
    40. }//Constructor
    41. }


    ViewModel:
    Spoiler anzeigen

    C#-Quellcode

    1. public class StopWatchViewModel : BaseViewModel, IStopWatchViewModel
    2. {
    3. //View <---> ViewModel
    4. public bool ShowComp { get; set; } = false;
    5. public bool SwRunning { get; set; } = false;
    6. private StopWatchModel _stopWatchModelObj;
    7. private string _name;
    8. private string _finalTimeString;
    9. private string _timerElapsedString;
    10. public string Name
    11. {
    12. get => _name;
    13. set => SetValue(ref _name, value);
    14. }
    15. public string FinalTimeString
    16. {
    17. get => _finalTimeString;
    18. set => SetValue(ref _finalTimeString, value);
    19. }
    20. public string TimerElapsedString
    21. {
    22. get => _timerElapsedString;
    23. set => SetValue(ref _timerElapsedString, value);
    24. }
    25. public void Start()
    26. {
    27. SwRunning = true;
    28. _stopWatchModelObj.Start();
    29. }
    30. public void Stop()
    31. {
    32. _stopWatchModelObj.Stop();
    33. SwRunning = false;
    34. this.FinalTimeString = _stopWatchModelObj.FinalSwString;
    35. }
    36. //View <---> ViewModel && ViewModel <---> Model
    37. public void AddSw()
    38. {
    39. if (_name != null)
    40. {
    41. _stopWatchModelObj = new StopWatchModel(_name);
    42. _stopWatchModelObj.PropertyChanged += StopWatchModelObj_PropertyChanged;
    43. ShowComp = true;
    44. }
    45. }
    46. //ViewModel <---> Model
    47. private void StopWatchModelObj_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    48. {
    49. TimerElapsedString = _stopWatchModelObj.TimerElapsedString;
    50. }
    51. }


    View der BlazorComponente:
    Spoiler anzeigen

    C#-Quellcode

    1. @inject StopWatchViewModel ViewModel
    2. @implements IDisposable
    3. @using System.ComponentModel
    4. @* Custom Css -> CssIsolation: in the same folder create a .css file sameName.razor.css *@
    5. <div class="mybox">
    6. <h3>StopWatchComponent</h3>
    7. @if (ViewModel.ShowComp == false)
    8. {
    9. <div class="container">
    10. <br />
    11. <h3>@ViewModel.Name</h3>
    12. <br />
    13. <div class="row">
    14. <div class="col-md-6">
    15. <button class="btn btn-info" @onclick="ViewModel.AddSw">Create StopWatch</button>
    16. </div>
    17. <div class="col-md-6">
    18. <input @bind-value=ViewModel.Name @bind-value:event="oninput" />
    19. </div>
    20. </div>
    21. <br />
    22. <br />
    23. </div>
    24. }
    25. @if (ViewModel.ShowComp)
    26. {
    27. <div class="container">
    28. <br />
    29. <h3 style="color:mediumpurple">@ViewModel.Name</h3>
    30. <br />
    31. <div class="row">
    32. <div class="col-md-4">
    33. <button class="btn btn-info" @onclick="@ViewModel.Start">Start</button>
    34. </div>
    35. <div class="col-md-4">
    36. <button class="btn btn-info" @onclick="ViewModel.Stop">Stop</button>
    37. </div>
    38. <div class="col-md-4">
    39. @if (ViewModel.SwRunning)
    40. {
    41. <h3 style="color:mediumpurple">@ViewModel.TimerElapsedString</h3>
    42. }
    43. @if (ViewModel.SwRunning == false)
    44. {
    45. <h3 style="color:mediumpurple">@ViewModel.FinalTimeString</h3>
    46. }
    47. </div>
    48. </div>
    49. </div>
    50. <br />
    51. }
    52. </div>
    53. @code {
    54. //FROM:
    55. //https://www.syncfusion.com/blogs/post/mvvm-pattern-in-blazor-for-state-management.aspx
    56. protected override async Task OnInitializedAsync()
    57. {
    58. ViewModel.PropertyChanged += async (sender, e) =>
    59. {
    60. await InvokeAsync(() =>
    61. {
    62. StateHasChanged();
    63. });
    64. };
    65. await base.OnInitializedAsync();
    66. }
    67. async void OnPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
    68. {
    69. await InvokeAsync(() =>
    70. {
    71. StateHasChanged();
    72. });
    73. }
    74. public void Dispose()
    75. {
    76. ViewModel.PropertyChanged -= OnPropertyChangedHandler;
    77. }
    78. }


    View "echte" Page:
    Spoiler anzeigen

    HTML-Quellcode

    1. @page "/stopwatch"
    2. @* Custom LayOut -> Create a LayoutFile in Shared -> @inherits LayoutComponentBase
    3. Add _Imports.razor to folder and add line @layout StopWatchLayout (e.g. YourtCustomLayoutName)*@
    4. <br />
    5. <br />
    6. <button class="btn btn-info" @onclick="AddSw" style="width:100%">Add just ONE MORE STOPWATCH AND ONE MORE ...</button>
    7. <br />
    8. <br />
    9. <br />
    10. @for (int i = 0; i < counter; i++)
    11. {
    12. <br />
    13. <StopWatchComponent></StopWatchComponent>
    14. <br />
    15. }
    16. @code {
    17. public int counter = 0;
    18. public void AddSw()
    19. {
    20. counter += 1;
    21. }
    22. }


    BaseViewModel : INotifyPRopertyChanged
    Spoiler anzeigen

    C#-Quellcode

    1. public abstract class BaseViewModel : INotifyPropertyChanged
    2. {
    3. //FROM:
    4. //https://www.syncfusion.com/blogs/post/mvvm-pattern-in-blazor-for-state-management.aspx
    5. private bool isBusy = false;
    6. public bool IsBusy
    7. {
    8. get => isBusy;
    9. set
    10. {
    11. SetValue(ref isBusy, value);
    12. }
    13. }
    14. public event PropertyChangedEventHandler PropertyChanged;
    15. protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    16. {
    17. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    18. }
    19. protected void SetValue<T>(ref T backingFiled, T value, [CallerMemberName] string propertyName = null)
    20. {
    21. if (EqualityComparer<T>.Default.Equals(backingFiled, value)) return;
    22. backingFiled = value;
    23. OnPropertyChanged(propertyName);
    24. }
    25. }


    Hier noch das Interface nachgereicht, nach dem "ErfinderDesRades in seinem Post fragt:

    Spoiler anzeigen

    C#-Quellcode

    1. public interface IStopWatchViewModel
    2. {
    3. string FinalTimeString { get; set; }
    4. string Name { get; set; }
    5. bool ShowComp { get; set; }
    6. bool SwRunning { get; set; }
    7. string TimerElapsedString { get; set; }
    8. public event PropertyChangedEventHandler PropertyChanged;
    9. void AddSw();
    10. void Start();
    11. void Stop();
    12. }


    Model und ViewModel erben das BaseViewModel und senden via INotify Änderungen vom Model -> ViewModel -> View


    Ich erwarte nicht, dass Ihr meinen Code kommentiert (oder anseht) :P .
    Ich wollte nur kurz melden wie es läuft. Zum einfacheren Verstehen noch kurz ein .gif von der App.

    Also noch mal tausend Dank für die Starthilfe

    @ErfinderDesRades
    @Joshi
    @Nofear23m
    @shad

    Bilder
    • BZSer-StopWatch-MVVM.gif

      1,14 MB, 502×920, 75 mal angesehen
    codewars.com Rank: 4 kyu

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „nogood“ ()

    Ich vermisse das Listing von IStopwatchViewModel.
    Und ich frag mich, wozu das nötig ist - braucht Blazor das?


    Des weiteren wunder ich mich, dass StopWatchModel von BaseViewmodel erbt.
    IMO ists dadurch per Definitionem kein Model mehr, sondern ein Viewmodel (es erbt ja von BaseViewmodel).
    Vielleicht wäre einfach BaseViewmodel umzubenennen in das was es tut: NotifyPropertyChanged (ohne I). Dann würde davon zu erben nicht mehr Viewmodellität mit-definieren.


    Und wieder meine ketzerische Frage, ob die Anwendung nicht ebensogut laufen würde ohne das StopWatchModel. Dann hätte man statt des einen StopWatchModel-Feldes eben zwei Felder im StopWatchViewModel: die Stopwatch und den Timer.
    Wäre vermutlich einfacher zu warten, weil nur in einer Klasse zu modifizieren ist.
    ZB wenn man nun hinzufügen möchte Funktionalität zum Modifizieren des Intervals:
    So oder so müsste dafür StopWatchViewModel geändert werden, und bei Vorhandensein von StopWatchModel wäre diese zusätzlich zu modifizieren.
    Ich meine zusätzlich, weil in StopWatchViewModel wäre ja keinerlei Aufwand einsparbar durch die StopWatchModel-Funktionalität.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

    @ErfinderDesRades

    1. IStopwatchViewModel nötig?
    Das ViewModel wird per DependencyInjection in der Startup.cs verlinkt. Also müsste man später, falls man ein anderes ViewModel benutzen möchte nur an dieser einen Stelle den Code ändern. Aus:
    services.AddTransient<IStopWatchViewModel, StopWatchViewModel>(); kann dann einfach
    services.AddTransient<IStopWatchViewModel, StopWatchViewModel2.0>(); werden und man muss nicht in den einzelnen Views etwas ändern. Ich hatte das leider noch nicht in meinem Bsp. 100%ig umgesetzt. Ich trage das in den CodeSpoilern oben nach.

    2."Warum erbt Model von BaseViewModel"? Gut Benamung ist ja immer schwer (jedenfalls für mich). BaseViewModel ist wohl unglücklich gewählt.
    Das Model erbt auch davon. Nicht, damit es zum ViewModel werden soll, sondern da Daten bei Änderung im Model auch ins ViewModel kommen sollen. Ich benutze den gleichen Trick wie zwischen ViewModel und View. Nur eben vom Model zum ViewModel. Ich hab das so rausgelesen aus den "Quellen". Wie sonst soll auf Änderungen, die ihren Ursprung im Model haben reagiert werden (wie sollen Änderungen sonst zum ViewModel kommen). Gut kann sein, dass es sehr selten ist, das Änderungen ihren Ursprung im Model haben. Und der Weg eher so ist, dass das ViewModel per Datenbankabfrage die Model-Daten erhält/anfordert. Aber ich wollte auch mal den Fall durch spielen, dass es eben nötig ist, Daten vom Model Richtung ViewModel zu leiten, ohne das das ViewModel diese "anfordert".

    3. Ist das Model überhaupt nötig? Kann das nicht alles ins ViewModel? Klar ich denke schon. Und wahrscheinlich wäre es so auch einfacher etc.. Ich war nur auf der Suche etwas zu finden, wo man den theoretischen Fall hat, dass das Model Daten selber erzeugt.
    Ziel war nicht ein gutes Stoppuhr Programm zuschreiben, sondern die Therorie des MVVM in der Praxis auszuführen.

    Falls Du mir sagen möchtest, dass man nicht zu steif an den Pattern festhalten soll, und auch immer im Blick haben soll, dass es Fälle gibt , wo Pattern eher stören ... ist das bei mir Angekommen :) .
    codewars.com Rank: 4 kyu

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „nogood“ ()

    nogood schrieb:

    Falls Du mir sagen möchtest, dass man nicht zu steif an den Pattern festhalten soll
    Ja, das ist tatsächlich der tiefere Sinn meiner Einlassung.

    Aber ich meins ernst damit - das unterscheidet mich vielleicht von anderen, die das YAGNI-Prinzip ins Spiel bringen:
    Also führe das Interface erst dann ein, wenn es wirklich dahin kommt, dass du ein anderes, aber struktur-identisches Viewmodel benutzen möchtest.
    Nach meiner Erfahrung kommt sowas nämlich annähernd nie vor - also ich habs tatsächlich noch nie erlebt.
    Immer immer, und immer wieder waren es Struktur-Änderungen, die gefordert waren.
    Und da hilft dir ein Interface nicht die Bohne, im Gegenteil: das ist dann nämlich auch noch anzupassen, und zwar bei jedem Versuch, und Vorversuch und vorläufigem Versuch auch.
    Bei Änderungen steht ja meist nicht von vornherein fest, in welcher Struktur die Änderung optimal implementiert ist.



    Zu MVVM: In meiner Welt ist das Model dumm: erzeugt keine Daten und feuert keine Events. So gesehen ist die Stopwatch bereits ein Model. Obs der reinen Lehre entspricht, einen Timer ins Model zu nehmen mögen andere postulieren - ich hab meine Zweifel.
    Wenn aber die Stopwatch bereits ein Model ist, ists nicht ergiebig, da noch einen Wrapper drum zu basteln, und den dann nochmal Model zu nennen - blos damit man ein selbstgeschriebenes Model hat.

    Aber gut - du wolltest für jeden Layer was schreiben - hastenun, ok.
    Und ich hab gesagt, dassich finde, dass man einen Grund haben muss, wenn man für einen Layer was schreibt - andernfalls soll mans lassen - habichnun, auch ok.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()