WinForms mit Viewmodel

    • VB.NET
    • .NET (FX) 4.5–4.8

    Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von Yanbel.

      WinForms mit Viewmodel

      Hallo Leute,ich bin jetzt soweit, dass ich die Funktionsweise meines Frameworks erläutern und ein funktionstüchtiges Beispiel liefern kann.

      Folgende grundlegende Kenntnisse sind erforderlich:
      - Unterscheidung von Klassen, Modules, Structures, etc.
      - Vererbung und abstrakte Basisklassen
      - SQL-Syntax
      - System.Reflections
      - MSSQL-Server (wird für das angehängte Projekt vorausgesetzt)

      Außerdem werden folgende Punkte behandelt:
      - Cryptography
      - SQL-Adapter
      - DataModelling
      - Borderless Formdesign & Control-Design
      - JSON / XML-Serialisierung
      Auf vielfachen Wunsch eines Einzelnen selbstverständlich mit Option Strict On (kann sein, dass ich das manchmal vergessen habe, aber an den relevanten Stellen müsste ich das überall berücksichtigt haben). Ich werde in diesem Thread allerdings nicht auf den Code im Einzelnen eingehen, sondern nur das Programmverhalten erläutern. Alles weitere dann in der darauf folgenden Diskussion.

      Anders als zuvor angekündigt, lade ich nicht mein eigenes Framework hoch, da das einfach den Rahmen sprengen würde. Ich habe ein komplett neues sehr schlichtes Framework geschrieben und allen für die Erklärung unnötigen Code weggelassen (beispielsweise Custom-Buttons im FunctionPanel, Interfaces, komplexe Vererbungsstrukturen, aufwendige Designs, Serverkomponente, etc.).

      Okay, let's go.

      Kommen wir zunächst einmal zur Klassenstruktur (Abbildung 1). In der Grafik seht ihr die Zugriffshierarchie der einzelnen Klassentypen untereinander. Jedem Klassentyp liegt eine Basisklasse zugrunde, die das generelle einheitliche Programmverhalten enthält, sodass die geerbte Klasse ausschließlich die Kernaufgaben übernimmt für die sie zuständig ist. Allgemein formuliert: Ich programmiere ungern eine Funktion zweimal wenn ich sie auch in die Basisklasse schieben und an zwei Masken vererben kann. Ein Beispiel zur Aufteilung der Arbeit findet ihr in Abbildung 2.

      Form
      Die Aufgabe der Form beschränkt sich auf das direkte Control-Handling. Die Maske selbst enthält keine Business-Logik. Die Idee ist letztendlich Anzeige, Daten und Data-Handling strikt zu trennen. Generelle Funktionen und Controls werden in der Basisform definiert. Das FunctionPanel kann natürlich um weitere Funktionen erweitert werden, wie beispielsweise eine Suchfunktion oder verschiedene Aufrufe anderer Masken (Im Projekt der Einfachheit halber weggelassen).

      Viewmodel
      Das Viewmodel enthält die freie, maskenbezogene Business-Logik. Datengebundene Businesslogik wird wiederrum in eine BusinessIntelligence-Klasse (im Projekt nicht implementiert, da nicht benötigt) ausgelagert, damit sie in mehreren Masken unabhängig verfügbar ist. Generelles Programmverhalten wird aus der Basisklasse des Viewmodels vererbt. Hierzu zählen Navigation und generelle Maskenfunktionen wie Speichern, Löschen und Neuerstellen, sowie die Rechte um die Buttons im Viewmodel nötigenfalls zu sperren.

      ViewElement
      Das ViewElement enthält eine 1:1 Abbildung der Maske in Form von einer Property die den Wert des Controls enthält. Für das Databinding ist es erforderlich das Control und Property denselben Namen haben. Das Binding wird in der Basisklasse des Viewmodels gesetzt und beim Wechsel des ActiveElements entfernt und neu gesetzt.

      Access-Klassen
      Access-Klassen enthalten die Logik für das Datahandling einer SQL-Tabelle mittels bestimmter Filter, die als Parameter an die Methoden übergeben werden.

      DataModel
      Ein Datamodel ist eine 1:1 Abbildung einer SQL-Tabelle und stellt die SQL-Tabelle als Objekt in der Application zur Verfügung. Dieses kann beliebig modifiziert werden, ändern die Produktivdaten auf dem SQL aber erst dann, wenn der User auf Speicher klickt.

      Sonstiges

      Cryptography

      Wie bereits mehrfach hier im Forum gelesen, wird häufig die Frage gestellt, wohin mit dem Private Key oder dem Kennwort, wenn ich Daten chiffriert habe. Ich habe hier eine Methode drin, die einen PrivateKey generiert und zur Aufbewahrung in einen RSA-Container verschiebt. Dieser kann dann durch die Applikation ausgelesen, gelöscht und neu generiert werden. Für das Abspeichern der SQL-Verbindungsdaten nutze ich die Generierung eines Private Keys den ich dann aus Kennwort für eine TripleDES-Verschlüsselung verwende.

      SQL-Adapter
      Die Projektmappe enthält auch eine SQL-Adapterklasse, die mir einen schnellen Zugriff auf einen MSSQL-Server ermöglicht. Beispiele für die Nutzung des Servers findet ihr beispielsweise in den Accessklassen.

      Borderless Form-Design
      Ich habe ein Beispiel beigefügt wie man eine Form mit FormBorderStyle = None funktionsbereit macht. Sie ist sehr schlicht gehalten und kann an einigen Stellen (Resizing) durchaus verbessert werden. Seht es nur als Beispiel, das ich auf die Schnelle nachgezogen habe, denn die Forms die ich normalerweise designe, sind geringfügig aufwendiger. Die kann ich hier allerdings nicht implementieren, da die Designklassen zu umfangreich sind und das zentrale Thema dadurch verfehlt wird. Das wäre etwas für ein weiteres Tutorial.

      Control-Design
      Mit dem Control-Design verhält es sich ähnlich. Er gibt eine Basisklasse, von der geerbt wird und die die von Control zu Control wiederkehrende Logik enthält. Das eigentliche Control enthält dann nur noch eine eigenes Design und Controlspezifisches Verhalten.

      XML-Serialisierung
      Hierzu habe ich bereits ein Tutorial verfasst, bedarf also keiner weiteren Erklärung. Ein Beispiel für die serialisierbare Klasse findet ihr in der RSAKeyValue-Klasse.

      Ich freue mich auf eine muntere Diskussion und eine rege Beteiligung eurerseits, natürlich den Forenregeln konform hier nur in direktem Bezug auf das Tutorial. Bei Bedarf schreibt mich an dann eröffne ich ggf. einen eigenen Thread dafür wo wir sonstige Ideen diskutiert werden können.

      Gruß Yanbel

      EDIT: @ErfinderDesRades hat mich noch auf eine wichtige Sache aufmerksam gemacht: Ihr müsst Datenbankseitig nichts erstellen, konfigurieren oder einpflegen. Das macht das Programm alles von alleine. Danke für den Hinweis.

      die Farbe Rot ist der Moderation vorbehalten und wurde ersetzt ~VaporiZed
      Bilder
      • Abbildung 1.png

        19,17 kB, 800×400, 248 mal angesehen
      • Abbildung 2.png

        18,45 kB, 800×500, 235 mal angesehen
      Dateien


      Ein Computer wird das tun, was du programmierst - nicht das, was du willst.

      Dieser Beitrag wurde bereits 19 mal editiert, zuletzt von „Yanbel“ ()

      Hmm - da sehe ich erhebliche Mängel in der Berücksichtigung von den Grundlagen der Sprache.
      Dringend erforderlich: Visual Studio - Empfohlene Einstellungen
      Ich für mein Teil mag mich damit nicht auseinandersetzen in dem Zustand wie das ist.
      Auch weils in gewisser Weise wohl als "Grundlage" gedacht ist, auf die evtl. weiteres aufgebaut werden könnte.

      Allein Strict On zu stellen fördert 33 Fehler zutage.

      Microsoft.Visualbasic - GeneralImport entfernen zeigt 27 Verwendungen obsoleter vb6-Methoden.
      Hey @ErfinderDesRades,

      vielen Dank für dein Feedback. Da sind mir wohl einige Klassen durchgerutscht.

      Zu Option Strict On:
      Ich habe leider einzelne Klassen vergessen auf Option Strict On zu stellen, bitte entschuldige. Ich kann meine IDE leider nicht generell umstellen, da die Hauptprojekte die ich auf der Arbeit programmiere historisch bedingt Option Strict Off programmiert sind und den GeneralImport von Microsoft.VisualBasic erfordert. Aber ich denke, dass ich jetzt alles gefixt habe.

      Zu Namespace Microsoft.Visualbasic:
      Das hat mich doch sehr überrascht, was alles noch zu vb6 zählt. vbcrlf zum Beispiel verwende ich regelmäßig und wüsste auch nicht was ich da als Alternative nehmen sollte (im Programmcode habe ich es auf Environment.NewLine geändert). Selbiges gilt für isNumeric (Im Projekt auf Integer.TryCast() geändert). Verwendest du diese Methoden grundsätzlich nicht und gibt es Alternativen? Oder geht es grundsätzlich nur darum, die Microsoft.VisualBasic nur OnDemand und nicht als GeneralImport zu laden? Allgemein sind die Methoden ja nicht Deprecated / Obsolete und werden von Microsoft ja weiterhin empfohlen (daher vermutlich auch der GeneralImport als Standardeinstellung).

      Edit (19.06.2020 13:21 Uhr): Hab vergessen die Projektmappe zu bereinigen. Hab sie jetzt nochmal bereinigt hochgeladen


      Ein Computer wird das tun, was du programmierst - nicht das, was du willst.

      Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von „Yanbel“ ()

      Yanbel schrieb:

      Ich kann meine IDE leider nicht generell umstellen, da die Hauptprojekte die ich auf der Arbeit programmiere historisch bedingt Option Strict Off programmiert sind und den GeneralImport von Microsoft.VisualBasic erfordert. Aber ich denke, dass ich jetzt alles gefixt habe.
      Jo - da hab ich Glück mit meim Kollegium - die konnte ich vom Sinn von Strict On und vom Unsinn von MVB überzeugen.
      Wir haben auch tonnenweise Strict-Off-Code, aber wir haben die Voreinstellung umgedreht:
      Nicht wo Strict On programmiert wird steht Stricht On drüber, sondern Strict On ist Voreinstellung, und wo Off programmiert ist, haben wir Option Strict Off drüber geschrieben.
      So ist der bestehende Code unverändert, aber neue Dateien sind Strict On (zumindest zunächstmal).
      Das pädagogische Prinzip dahinter: Beachtung von Datentypen setzt sich allmählich durch, weil ist naheliegender als Schmuddel-Coding.

      Für MVB habich iwann mal das Go bekommen, den GeneralImport von Microsoft.VisualBasic rauszuschmeissen und zu ersetzen durch VB6=Microsoft.VisualBasic.
      Dann findet der compiler alle Stellen, wo die Grütze verwendet wird, und man kann da ganz schematisch ersetzen, ala

      VB.NET-Quellcode

      1. dim bla = Instr("uffUff", "uf")
      2. 'durch
      3. dim bla = VB6.Instr("uffUff", "uf")
      Das ist in wenigen Stunden auch für riesige Code-Bases erledigt.
      Der olle Code ist auf diese Weise gar nicht geändert, aber alles, was neu angefangen wird, wird sauber angefangen, einfach weil Schmuddel ist nicht mehr so bequem.

      Zu vbcrlf: ich habe einen GeneralImport auf Microsoft.VisualBasic.Controlchars. Das ist eine Enumeration von derlei Steuerzeichen (Tab, Cr, CrLf, ...)
      Kann man genauso einfach und allgemein verwenden wie früher vbCrlf, nur dass es nicht mit dem Mega-GeneralImport erkauft ist.
      Statt IsNumeric bietet sich oft Integer/Double.TryParse an, aber das tickt nicht ganz genauso wie VB6.IsNumeric.
      Also guck dir TryParse an, und wenns deine Bedürfnisse nicht bedient, dann nimm doch VB6.IsNumeric - also meinen benannten GeneralImport. Es ist natürlich am besten, den Krempel zu meiden, aber wo das (noch) nicht geschehen, sollte er unbedingt kenntlich gemacht sein, damit der Programmierer weiss, was er tut.

      Ich verteufel den MVB ja nicht aus Prinzip, sondern nur das Schlechte darinnen (und das ist numal 99%).
      Manche Sachen sind gut, die befürworte ich. ControlChars sind schon genannt, der TextFieldParser ist prima, und die InputBox ist manchmal auch ganz praktisch. (kann sein, dass' noch mehr gibt, aber fällt mir grad nicht ein).



      Yanbel schrieb:

      Allgemein sind die Methoden ja nicht Deprecated / Obsolete
      Najaa - "deprecated" ist ja einfach englisch und heisst: "veraltet, missbilligt".
      Und zB Vb6.FileOpen(), Vb6.Instr(), Vb6.Mid$(), ... täte ich schon sehr missbilligen, und täte schon behaupten, ein kompetenter DotNet-Programmierer würde solch nie nie nie benützen, weil ihm die viel besseren Technologien aus dem System - Namespace bekannt sind.
      Klar - es gibt auch ein <Deprecated>-Attribut, und Microsoft hats auf den Vb6-Schrott (leider) nicht angewendet - aber dieses Versäumnis ändert ja nix dran, dass der Schrott Schrott ist, den man kaum anders bezeichnen kann als: "deprecated" - "veraltet, missbilligt".

      Yanbel schrieb:

      werden von Microsoft ja weiterhin empfohlen
      Ist mir bisher entgangen, dass das empfohlen würde - und glaube ich lieber nicht. Solltest du recht haben, wäre das schlimm.

      Yanbel schrieb:

      ...(daher vermutlich auch der GeneralImport als Standardeinstellung).
      Über MS-Standard-Einstellungen kann man viel vermuten. Mir fallen noch paar weitere Beispiele ein, wo die Voreinstellung maximal dämlich eingestellt ist - ich weiss nicht,warum das bei dene so ist.

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

      Hey @ErfinderDesRades,

      hattest du denn jetzt abschließend Zeit dir meinen Code mal anzuschauen, nachdem ich ihn bereinigt habe. Über ein Feedback würde ich mich sehr freuen, einfach mal generell zur Struktur und der Umsetzung. Ich denke du bist da schon am ehesten einer der richtigen Ansprechpartner, da du genug Vorkenntnisse besitzt um das auch wirklich bis ins Detail zu verstehen und dementsprechend auch eine qualifizierte Meinung dazu abgeben kannst. Vielleicht können sich auch die anderen nochmal melden die an der Diskussion teilgenommen hatten (z.B. @petaod, @VaporiZed, etc.). Gerade auch in Hinsicht auf die Entwicklung von Codeentwicklung wie @VaporiZed sie in im Parallelthread beschrieben hat. Würde mich freuen.


      Ein Computer wird das tun, was du programmierst - nicht das, was du willst.
      Ich fühl mich ein Stück weit von dem Informationsumfang der Projektcodes erschlagen und ich glaube auch nicht, dass ich die Eleganz dieses Projekts erfassen kann. Ist natürlich die Frage, wieviel man vom Innenleben eines Frameworks wirklich wissen will 8|
      Das Ganze erinnert mich ein wenig an @Gathers MetroSuite und den Bildern von @dive26: sehr stylisch und ja, man kann einiges aus WinForms rausholen. Aber ich bin da old style. Wenn ich schickes Design haben will, wär ich Designer geworden :P .
      Was sehe ich (wenn man die grafischen Besonderheiten mal weglässt): Ein Form, welches datenbankgebunden ist und die entsprechenden DB-Funktionen immer bereithält. Nicht jedes Form sollte sowas unbedingt leisten können. Ein Berichtdruckerform oder ein Mailsendeprogramm kann mit den BaseForm-Funktionalitäten wohl nicht sonderlich viel anfangen.
      Machen wir es doch mal so rum: Wie einfach könntest Du Deine DataBase gegen eine andere oder gar gegen ein anderes Persistenzsystem tauschen? Wie groß wäre der Aufwand? Ich glaube, das könnte ich als Standardfrage für sowohl meine als auch andere Projekte verwenden. Ist die DB tief verflochten oder ist sie quasi ein austauschbares Plugin?
      Sorry, produktiver bin ich gerade nicht. Wie gesagt: Ich bin derzeit nicht fähig, die Vollumfänglichkeit des Projektes zu erfassen.

      Mir fallen auf Anhieb nur wenige Punkte auf:
      • streckenweise sind noch deutsche Begrifflichkeiten drin (z.B. RechterAussenrand, Dfifferenz)
      • bei Property-Settern reicht standardmäßig einfach Set, da Value aus dem Propertydatentyp geschlussfolgert wird
      • das CallerMemberNameAttribute erspart Dir die Übergabe des Propertynamens an Deine PropertyChanged-Sub bzw. das Event
      • ein paar mal habe ich And gesehen, obwohl AndAlso möglich wäre (Der Unterschied zwischen And und AndAlso/Or und OrElse)
      • Deine BaseForm-LockControl-Sub könntest Du sicherlich mit Reflection deutlich kürzen
      • manche sind kein Fan davon, aber Du könntest mehr mit If-Umkehrung arbeiten, um die Übersicht zu verbessern bzw. den Code etwas zu vereinfachen
      • Du arbeitest in BaseForm bei Minimize/Maximize nicht mit Anchor, sondern berechnest deren Position. Absicht?
      Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

      Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
      Hey @VaporiZed,

      erstmal vielen Dank für dein Feedback.

      Zu deinen Punkten:
      1.) Jop... gepennt, braucht man nicht schön reden. :D
      2.) Wird glaube ich autogeneriert, kann man aber einsparen, geb ich dir recht.
      3.) Das kenne ich noch nicht, danke für den Hinweis, schaue ich mir auf jeden Fall an.
      4.) Jo... hab ich gefunden, betraf 2 Klassen und habe ich ersetzt.
      5.) Geht bestimmt. Aber die Typen müsste ich auch bei Reflection abfragen, für die Unterscheidung von Enabled und Readonly. Würde ich glaube ich nicht wirklich kürzer gestalten können.
      6.) Tue ich schon an vielen Stellen, aber wenn dir da noch was aufgefallen ist, dann siehe 1.) :D
      7.) Ja und Nein. Ich arbeite schon mit Anchor, aber ich setze die Positionen nochmal manuell damit die Buttons auch im Designer an der richtigen Stelle sitzen. Ansonsten kriegt der Designer das aus irgendeinem Grund nicht immer hin.

      Das Design ist tatsächlich für meine Verhältnisse sehr schlicht gehalten. Die Designs die ich sonst schreibe sind sehr viel aufwendiger. Im Anhang findest du zwei Beispiele, die beide auf dem gleichen FrameWork basieren. Ist also durchaus modular anpassbar.

      Das FunctionPanel ist nicht zwangsläufig an eine Datenbank gebunden. Wie du in der Login-Form sehen kannst, kann man über die Save-Funktion auch ganz andere Speichervorgänge umsetzen. Das FunctionPanel ist selbstverständlich jederzeit erweiterbar und da du Mail und Drucken angesprochen hast. Mein reguläres FunctionPanel verfügt über Mail, Print und Export-Buttons. Ich habe mich bei dem Projekt nur auf das beschränkt, was für das Projekt relevant ist, aber weitere Function-Buttons sind durchaus denkbar und einfach zu realisieren.

      Zum Punkt Austauschbarkeit / Skalierbarkeit: Du kannst parallel normale losgelöste WinForms verwenden oder eine weitere Form parallel betreiben. Sofern es über die gleichen Eigenschaften wie auch die BaseForm verfügt kann du sogar alle Viewmodels daran binden. Die Viewmodel greifen niemals auf die geerbte Form zu. Nur auf die BaseForm. Du brauchst kein Function-Panel? Blende es aus.

      Zur Datenbank: Es ist dem Programm völlig egal welchen MS-SQL-Server du dahinter klemmst. Wenn ihm Tabellen fehlen, werden sie angelegt. Du kannst da eine völlig blanke Datenbank dranklemmen. Sobald die zu dem Programm verbunden wird. Werden benötigte Tabellen angelegt. Ich habe auch mal irgendwann einen Adapter geschrieben, der je nach Bedarf MSSQL-Syntax in MySQL übersetzt, um nachträglich eine MySQL damit zu betreiben. Die Translationklasse hab ich bestimmt auch noch irgendwo.

      Ich hoffe das beantwortet deine Fragen weitestgehend.
      Bilder
      • Kratos Systems.PNG

        36,25 kB, 2.561×1.399, 201 mal angesehen
      • Odysseus.PNG

        13,78 kB, 843×478, 199 mal angesehen


      Ein Computer wird das tun, was du programmierst - nicht das, was du willst.