MVVM Umsetzung mit WinForms

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

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

    Moin moin

    Denn dass das MainViewModel ein Form erzeugt, gehört gar nicht zu seinen Aufgaben


    Das "FrmSettings" erstellen gehört in eine "Model-Class" :?:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.Runtime.CompilerServices
    3. '/ file: MainViewModel.vb
    4. Public Class MainViewModel : Implements INotifyPropertyChanged
    5. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    6. Private DialogFormsInstance As DialogCreateForms = DialogCreateForms.Instance
    7. Protected Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = "")
    8. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    9. End Sub
    10. Property SettingViewModel As SettingViewModel
    11. Property CallSettingsRelayCommand As New RelayCommand(AddressOf CallSettings)
    12. Private Sub CallSettings()
    13. DialogFormsInstance.ShowSettingsDialog(SettingViewModel)
    14. End Sub
    15. End Class


    VB.NET-Quellcode

    1. '/ file: CreateForms.vb
    2. Public Class DialogCreateForms
    3. Private Shared ReadOnly _instance As New Lazy(Of DialogCreateForms)(Function() New DialogCreateForms())
    4. ' Öffentliche Methode, um die Singleton-Instanz zu erhalten
    5. Public Shared ReadOnly Property Instance As DialogCreateForms
    6. Get
    7. Return _instance.Value
    8. End Get
    9. End Property
    10. ' Erstelle Form FrmSettings
    11. Public Sub ShowSettingsDialog(settingViewModel As SettingViewModel)
    12. Using dialog As New FrmSettings
    13. dialog.bsSettings.DataSource = settingViewModel
    14. dialog.ShowDialog()
    15. End Using
    16. End Sub
    17. ' Erstelle Form XXX...
    18. End Class

    Asperger Autistin. Brauche immer etwas um gewisse Sachen zu verstehen. :huh:
    Nee, die Erzeugung der Forms kommt ganz woanders hin. Model-Klassen sind Deine Daten und deren Verarbeitung. Models machen selber nix mit View oder ViewModel. Models werden von anderen genutzt.
    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.
    @GerhardW: Riecht aber verdächtig nach "MVVM-Framework zu inkludieren". Wir wollen hier ja sehen, wie es gemacht wird, oder @Amelie?

    Das View-Erzeugen wird von Extraklassen gemacht, die View-spezifisch sind. Will heißen: man bastelt sich eigene, sogenannte WindowService-Klassen (im weiteren WS), die die Views erzeugen. Ein WS für WinForms und in WPF-Projekten einen für WPF-Windows.
    Vom Prinzip her geht das so, dass man eben eine neue WS-Klasse erzeugt, die für WinForms ein Form erzeugt und dieses mit ShowDialog aufruft.
    Was ist jetzt anders, als wenn man es direkt im View macht? Es wird komplizierter ;(
    Naja, aber nicht ohne Vorteile.
    Man erzeugt ein Interface (im Weiteren iWS) (ist Dir absolut klar, was ein Interface ist?), welches von der WS-Klasse implementiert wird. Die Views bekommen dieses Objekt (aber eben nicht das Objekt vom Typ WS, sondern vom Typ iWS) über ihren Konstruktor eingeschoben und können bei Bedarf das WS-Objekt über das iWS-Interface aufrufen und es dazu bringen, ein Form zu erzeugen. Man macht das über das Interface, damit man dem ViewModel auch eine WPF-WS-Klasse einflößen kann. Das Interface bleibt eben dabei gleich, das ViewModel auch. Nur WS-Klasse und GUI ändern sich. Aber der Rest funktioniert weiter. Das ist ja auch Sinn der MVVM-Sache. View von ViewModel und von Model trennen, um das View austauschbar zu machen.

    Informationsoverkill :D
    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.
    @GerhardW @VaporiZed
    Ich habe mir mal das YT-Video angesehen. Schaut alles toll aus und ist bestimmt für produktive Coder recht interessant .... :thumbup:

    ABER

    ​Wir wollen hier ja sehen, wie es gemacht wird,


    Genau darum geht es mir. :thumbsup:

    Interface, ja habe ich mal mir meinen DataStets etc benutz.
    Das "iWS" und "WS" usw... püüü muss das mal sacken lassen und bissel "rumspielen" :)

    Wenn ich etwas brauchbares habe, poste ich es :)
    Asperger Autistin. Brauche immer etwas um gewisse Sachen zu verstehen. :huh:

    Amelie schrieb:

    Interface, ja habe ich mal mir meinen DataStets etc benutz.
    Öhh, ok. Fällt mir auf Anhieb gar nicht ein, dass Du da mal was gemacht hast.

    Wenn Du willst, hier ein simples, aber auf Anhieb scheinbar langes Beispiel:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Friend Interface IWindowService 'das Interface; kann selber quasi nix, sagt aber anderen, was die können (müssen)
    2. Sub CreateView(ViewToCreate As ViewToCreate)
    3. Sub CloseCurrentView()
    4. End Interface
    5. Friend Enum ViewToCreate 'ein Enum, um dem Service zu sagen, worum es bei CreateView() geht
    6. MainView
    7. SettingsView
    8. End Enum
    9. Module Start
    10. Sub Main()
    11. Dim MainViewModel = New MainViewModel(New WinFormsService) 'hier wird konkret durch das New festgelegt, welcher WindowService verwendet werden soll
    12. MainViewModel.CreateView()
    13. End Sub
    14. End Module
    15. Friend Class WinFormsService : Implements IWindowService 'eine Klasse, die auf Befehl Forms erzeugt
    16. Private CurrentForm As Form 'ganz allgemein das aktuelle Form merken
    17. Sub CreateView(ViewToCreate As ViewToCreate) Implements IWindowService.CreateView
    18. Select Case ViewToCreate
    19. Case ViewToCreate.MainView : CreateMainForm()
    20. Case ViewToCreate.SettingsView : CreateSettingsForm()
    21. End Select
    22. End Sub
    23. Private Sub CreateMainForm()
    24. Using Dialog As New FrmMain
    25. CurrentForm = Dialog
    26. Dialog.ShowDialog()
    27. End Using
    28. End Sub
    29. Private Sub CreateSettingsForm()
    30. Using Dialog As New FrmSettings
    31. CurrentForm = Dialog
    32. Dialog.ShowDialog()
    33. End Using
    34. End Sub
    35. Sub CloseCurrentView() Implements IWindowService.CloseCurrentView
    36. If CurrentForm Is Nothing Then Return
    37. CurrentForm.Close()
    38. End Sub
    39. End Class
    40. Friend Class MainViewModel
    41. Private ReadOnly WindowService As IWindowService
    42. Sub New(WindowService As IWindowService)
    43. Me.WindowService = WindowService
    44. End Sub
    45. Sub CreateView()
    46. WindowService.CreateView(ViewToCreate.MainView)
    47. End Sub
    48. Sub CreateSettingsView()
    49. Dim SettingsViewModel As New SettingsViewModel(WindowService)
    50. SettingsViewModel.CreateView()
    51. End Sub
    52. Sub CloseCurrentView()
    53. WindowService.CloseCurrentView()
    54. End Sub
    55. End Class
    56. Friend Class SettingsViewModel
    57. Private ReadOnly WindowService As IWindowService
    58. Sub New(WindowService As IWindowService)
    59. Me.WindowService = WindowService
    60. End Sub
    61. Sub CreateView()
    62. WindowService.CreateView(ViewToCreate.SettingsView)
    63. End Sub
    64. Sub CloseCurrentView()
    65. WindowService.CloseCurrentView()
    66. End Sub
    67. End Class


    Das Erstellen und Schließen des Views ist jetzt ausgelagert und die ViewModels wissen nur, dass sie ein Objekt kennen, was Views erstellen und schließen kann. ABER: Ob das Teil, was den ViewModels untergeschoben wird, nun Forms erstellt, oder WPF-Windows, oder was ganz anderes, ist den ViewModels wurscht. Was sie wissen ist, dass dieses untergeschobene Objekt dank des implementierten Interfaces irgendwas in der Richtung kann. Aber was dabei rauskommt, ist den ViewModels wurscht und liegt ganz allein in der Hand der benutzten und übergebenden WindowService-Klasse.
    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.
    Moin moin

    Nachdem mal wieder die Bindings weg waren, ein Update der einzelnen Dateien mit dem Interface zur Form-Erstellung.
    Das mit den Bindings ist schon extrem nervig, auch wenn ich inzwischen weiß, wie ich die schnell wieder hinbekomme.

    Das mit dem Interface zur Form-Erstellung ist irgendwie ja schon gut, aber bringt auch wieder erheblich mehr an Code mit und man muss im Vorfeld schon genau wissen was in welcher Reihenfolge zu erstellen ist.
    Sonst sucht man ggf nach Fehlern / Warnungen im Studio die zu vermeiden wären.
    Beispiel:
    Erst im Designer das entsprechende Form erstellen und dann im Code verwenden ;)

    Spoiler anzeigen

    VB.NET-Quellcode

    1. '/ file: InterfaceiWin.vb
    2. Public Interface IWindowService
    3. ' für jedes Form ein Sub
    4. Sub ShowSettings(settingsViewModel As SettingViewModel)
    5. 'Sub ShowInfo()
    6. End Interface
    7. Public Class WinFormsWindowService
    8. Implements IWindowService
    9. Public Sub ShowSettings(settingsViewModel As SettingViewModel) Implements IWindowService.ShowSettings
    10. Dim form As New FrmSettings()
    11. form.bsSettings.DataSource = settingsViewModel
    12. form.ShowDialog()
    13. End Sub
    14. 'Public Sub ShowInfo() Implements IWindowService.ShowInfo
    15. ' Dim form As New FrmInfo()
    16. ' form.ShowDialog()
    17. 'End Sub
    18. End Class


    VB.NET-Quellcode

    1. '/ file: StartMain.vb
    2. Module StartMain
    3. Sub Main()
    4. Application.EnableVisualStyles()
    5. ' Erstellen und Initialisieren der ViewModels und des WindowService
    6. Dim windowService As New WinFormsWindowService()
    7. Dim mainViewModel As New MainViewModel(windowService)
    8. Dim settingViewModel As New SettingViewModel()
    9. mainViewModel.SettingViewModel = settingViewModel
    10. Debug.WriteLine($"Gestartet in der Sub Main")
    11. ' FrmStart öffnen und die ViewModels zuweisen
    12. Using dialog As New FrmStart()
    13. dialog.bsDataViewModel.DataSource = mainViewModel
    14. dialog.bsSettings.DataSource = settingViewModel
    15. dialog.ShowDialog()
    16. End Using
    17. End Sub
    18. End Module


    VB.NET-Quellcode

    1. '/ file: MainViewModel.vb
    2. Public Class MainViewModel : Implements INotifyPropertyChanged
    3. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    4. Private ReadOnly _windowService As IWindowService
    5. Public Property SettingViewModel As SettingViewModel
    6. Protected Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = "")
    7. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    8. End Sub
    9. Public Sub New(windowService As IWindowService)
    10. _windowService = windowService
    11. End Sub
    12. ' An den MenueEintrag 'Einstellungen' gebunden
    13. Property OpenSettingsRelayCommand As New RelayCommand(AddressOf OpenSettings)
    14. Public Sub OpenSettings()
    15. _windowService.ShowSettings(SettingViewModel)
    16. End Sub
    17. 'Public Sub OpenInfo()
    18. ' _windowService.ShowInfo()
    19. 'End Sub
    20. End Class


    Asperger Autistin. Brauche immer etwas um gewisse Sachen zu verstehen. :huh:
    @ErfinderDesRades

    Bitte nicht alle meine Worte auf die berüchtigte Goldwaage legen. ;) Das irgendwie gut bedeutet schon etwas :)

    Ich habe so eine Zentrale Stelle, wo ich meine Views (Forms) mit diversen Parametern erstellen kann. Diese bleiben aber von der Logik, die keine Kenntnis davon haben muss "getrennt".
    Zudem ist es einfach das Projekt um weitere Views zu erweitern.
    So ein bisschen vergleichbar mit der ComboBox die Du in meinem HomeClima eingebaut hattest um die DGVs anzuzeigen UND um ggf einen weitere "Mess-Station" hinzu zufügen. ;)
    Asperger Autistin. Brauche immer etwas um gewisse Sachen zu verstehen. :huh:
    Ok, zentrale Stelle klingt erstmal gut, kann aber auch ein Klotz am Bein sein. Möchtest Du zeigen, was Du da hast und wie die Abläufe zum View-Erzeugen sind?
    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.
    @VaporiZed
    Möchtest Du zeigen, was Du da hast


    In Post #108 ist das mit dem Interface etc.

    Das ich das FrmMain auch gleich mit dem Interface erstellen könnte habe ich mittlerweile auch gelesen. ;)
    Asperger Autistin. Brauche immer etwas um gewisse Sachen zu verstehen. :huh:

    Neu

    Habe auch mal einen Versuch gestartet. Aber so richtig warm bin ich damit nicht geworden. Musste ab und zu in die Trickkiste greifen. Die eine oder andere Angelegenheit, kann sicher besser gelöst werden.

    Es funkst auf jeden Fall, auch wenn es nicht unbedingt Core-Konform ist.

    Freundliche Grüsse

    exc-jdbi


    EDIT - WinformsSpecMvvm_V2.zip:
    Nochmals kurz überarbeitet, aber dem Konzept treu geblieben.
    • Es gibt keine Kontruktoren mehr für die Viewmodels (alles über Bindings), und es gibt eine Vb-Net-Version, die leider für das Laden des DbContext nicht richtig Updatet.
    • View und ViewModel habe ich in die gleiche Dll geschoben, damit Typensicherheit gewährleitet ist.
    • Jedes View (Container) besitzt ihr eigene BindingSource, was den Vorteil hat, dass die BindingSource 'Private' gehalten werden können.
    • Die Bindings könnten weiter gepflegt werden, so das nur noch mit den Bindings z.B. extern gearbeitet werden kann. Das überlass ich jedoch dem, der dazu llust hat, sich weitere Gedanken darüber zu machen ;)
    Dateien

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „exc-jdbi“ ()

    Neu

    exc-jdbi schrieb:

    Core-Konform
    Was soll das bedeuten?

    exc-jdbi schrieb:

    Aber so richtig warm bin ich damit nicht geworden.
    Es ist eben ne architektonische Umstellung, nicht mehr die Forms im Zentrum des Geschehens zu haben.

    Was mir auf Anhieb auffällt: MVVM bringt mit sich, dass das ViewModel das View nicht kennt. Das View kennt das ViewModel und bindet daran. Die Kommunikation läuft dann nur über das DataBinding. Bei Dir wird dem MainViewModel aber das MainForm über den Konstruktor infundiert. Das ist nicht MVVM-konform. Denn dann könnte das View nicht z.B. gegen ein WPF-Window ausgetauscht werden. Du weißt z.B. dann, dass das Projekt dann MVVM-konform, wenn Du die Models in eine DLL packst, die ViewModels in eine andere und im WinForms-Projekt beide DLLs einbindest. Die VM-DLL muss noch die Model-DLL einbinden. Aber sonst müsste es laufen. Da dann die VM-DLL zwar die Model-DLL kennt, aber nicht das View, kann das View problemlos ausgetauscht werden. Das wär dann MVVM-konform.
    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.

    Neu

    Hallo VaporiZed

    Ich weiss das die einzelnen Schichten im MVVM nichts voneinander wissen. Das wurde in diesem Thread ja auch genug erwähnt. Ich wollte auf einen Controller (MVC-Modell) verzichten, und versuchen die UserControls, die ja untereinander in der FrmMain ausgetauscht werden, über das MVVM zu bewerkstelligen. Ist mir total suspect rübergekommen.

    So wie es aufgebaut ist, also jede Schicht eine Dll, so kennen sich die einzelnen Schichten auch nicht.
    Ich empfand es dann am einfachsten, die wichtigsten Komponenten über den Konstruktor in die ViewModel einfliessen zu lassen, und das ja auf diese Art und Weise zu versuchen.

    Alles in nur eine Dll zu machen, dass ist kein Problem, das habe ich schon gemacht, und der Vorteil ist natürlich, dass die genauen Typen bekannt sind. Das ist in dem jetzigen Projekt nicht so.

    Aber ich werde das mit den Bindings irgendwann nochmals anschauen, ob es da wirklich eine gute Lösung gibt.

    Freundliche Grüsse

    exc-jdbi

    Neu

    Dann dampfe mal bitte Deine Problematikbeschreibung auf ein, zwei Punkte ein. Geht es darum, MVVM-konform ein leeres Form zu haben und dann VM-abhängig das passende Daten-beinhaltende UC anzuzeigen?
    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.

    Neu

    Ich werde es nächste Woche sicher nochmals anschauen.

    Das eigentliche Problem ist, mit diesem Aufbau den ich gerade habe, also jeder Schicht eine eigene Dll, mehrere UserControls in eine FrmMain einzubauen.

    Dazu möchte ich jedoch die UserControls nicht im Vorfeld in einem UserControlHolder oder dergleichen schon instanziert halten, sondern wenn möglich soll das jeweilige UserControl, dass verwendet werden soll grundlegend neu instanziert werden, und dann in die FrmMain unter Controls geaddet werden.

    Ich sehe hierbei das Problem der Typensicherheit, und da sich Schichten und Komponenten untereinander nicht kennen, wie kann das nur über die Binding gelöst werden?

    hier MVVM-Konform zu bleiben wäre natürlich super.

    Das wäre schon eine kleine Hilfe :)

    Freundliche Grüsse

    exc-jdbi

    Neu

    Ich kann aufgrund der Beschreibung erstmal nur halbfertige Spekulatius backen.
    Falls es Dir hilft, würde ich kein Form im Voraus haben, sondern ein neues bei Viewerstellung von einer eigenen Klasse namens WindowService kreieren lassen. Da kommt als einziges ein UserControl rein, was gefüllt gedockt den kompletten Platz einnimmt. Die UCs erstellst Du im Designer und gibst denen eine BindingSource mit DataSource = Typ des UC-spezifischen ViewModels. Zu Programmbeginn erstellst Du das Main-/StartViewModel und dieses gibt den Befehl an entweder den WindowService oder eine andere Klasse, dass diese ein VM-spezifisches UC erstellen soll und dieses an den WindowService weitergibt, damit er eben ein Form mit dem VM-spezifischen UC erstellt. Wenn man es tricky/sophisticated/kompliziert (nach Belieben) machen will, kann man festlegen, dass das UC ein Interface implementiert, welches eine BindingSource enthält. So könnte man von außen sauber darauf zugreifen, welche DataSource die UC-eigene BS hat und käme so an den Typ des benötigten VMs. Dann könnte man also vom VM aus dem WindowService sagen: "Erstelle ein neues Form mit einem UC, welches mich benötigt."
    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.

    Neu

    Jedes UserControl ist doch schon ein eigener View, jedes davon muss das Viewmodel nutzen können. Ich finde es deswegen erstmal komisch, dass bei dir jedes UC ein eigenes Viewmodel hat. Das Viewmodel soll doch nicht einem View dienen, sondern einem Model. Pro Model ein Viewmodel, nicht pro View ein Viewmodel

    Neu

    Haudruferzappeltnoch schrieb:

    Jedes UserControl ist doch schon ein eigener View
    Ja, aber ein UC kann ohne Form nicht existieren. Irgendwo muss es als Control ja dargestellt werden.

    Haudruferzappeltnoch schrieb:

    dass bei dir jedes UC ein eigenes Viewmodel hat
    Es gibt bestimmt Situationen, in denen UCs ein VM teilen können. Aber jedes View hat bei mir andere Daten darzustellen und ist für eigene Aufgaben zuständig. Wenn ich dafür ein einziges VM verwende, ballere ich mir die VM-Klasse wieder zu und habe einen VM-Monolithen. Sehe ich jetzt nicht als so prickelnd an. Aber hey! Probieren wir es aus und sehen, was sich bewährt. Ich steh da auch noch am Anfang meiner persönlichen MVVM-Entwicklung.
    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.