WinForms+MVVM: Wie mache ich das MainForm unabhängig vom MainViewModel?

  • VB.NET
  • .NET 7–8

Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von VaporiZed.

    WinForms+MVVM: Wie mache ich das MainForm unabhängig vom MainViewModel?

    Hallo zusammen.

    Zu Beginn meines Programms macht ein Service folgendes:
    • sich die von der Startroutine erzeugte MainForm-Instanz merken (UcMain, gespeichert in einer Variable vom allgemeinen Typ Control; es ist ein UserControl, welches in ein generisches Form gepackt wird.)
    • sich die von der Startroutine erzeugte MainViewModel-Instanz merken (VmMain, gespeichert in einer Object-Variable)
    • VmMain als DataContext des UcMain setzen
    • UcMain als Dialog anzeigen
    Eine untypisierte Property des VmMain ist eine Ansammlung von DatenViewModels als ObservableCollection(Of Object), damit mein DataListPanel (DLP) (ein DGV-ähnliches, eigenes Control in einer DLL) auf die Items zugreifen kann, ohne wissen zu müssen, was es konkret für Items hostet.

    Nun habe ich zwei Stellen, bei denen das UcMain das VmMain kennt, was ja nicht bei MVVM sein soll:
    1. Eine BindingSource bekommt im Designer als DataSource GetType(VmMain), damit ich RelayCommands des VmMain an Buttons oder ToolStripButtons des UcMain binden kann. Diese Abhängigkeit kann ich - naja, umgehen, indem ich im UcMain-Konstruktor einen Typen übergebe, den ich im Startmodul mit angebe, also:

      VB.NET-Quellcode

      1. Friend Class UcMain
      2. Property ViewModelType As Type
      3. Sub New(Type As Type)
      4. ViewModelType = Type
      5. InitializeComponent()
      6. End Sub
      Dann kann ich in der UcMain.Designer.vb diesen Typ bei BsViewModel.DataSource angeben und GetType(VmMain) weglassen. Nachteil: Ich habe keine DesignTime-Unterstützung mehr für die RelayCommands.
    2. Das DLP braucht die untypisierte VmMain-Property ItemViewModels mit den zu hostenden Items/Data-ViewModels. Derzeit leider nur umsetzbar mit DataListPanel.DataContext = DirectCast(DataContext, VmMain).ItemViewModels. Da habe ich noch keinen Weg gefunden, das zu ersetzen, da der Service ja auch nicht so viel vom ViewModel kennen soll, damit er auch für andere Forms einsetzbar bleibt.
    Hat mir jemand Vorschläge zur Beseitigung dieser Dilemmata auf hohem Niveau?

    ##########

    Ich hatte zwar den Microsoft-Artikel von @loeffel gesehen, wie man WinForms und MVVM zusammenbringt, aber mein Problem 1 führt darin (soweit ich gesehen habe) zur gleichen Umsetzung wie gehabt: BindingSource mit DataSource = GetType(ViewModel), was ja wieder das Form an die Bekanntheit des ViewModels bindet.

    ##########

    Mir ist eingefallen, dass ich im Startmodul neben UcMain- und VmMain-Instanz-Erstellung auch die ItemsViewModels untypisiert dem UcMain schon mitgeben kann. Aber ob das sauber ist?

    ##########

    Vielleicht bin ich aber auch gedanklich grad zu radikal. Schließlich muss man ja auch in der WPF beim DesignTime-DataContext den ViewModel-DataType angeben. Damit wäre ggf. Problem 1 hinfällig und Problem 2 umgangen. Aber ich lass es mal noch ein paar Tage unerledigt, falls sich noch jemand mit Anmerkungen äußert.
    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.

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

    VaporiZed schrieb:

    Vielleicht bin ich aber auch gedanklich grad zu radikal. Schließlich muss man ja auch in der WPF beim DesignTime-DataContext den ViewModel-DataType angeben. Damit wäre ggf. Problem 1 hinfällig und Problem 2 umgangen.


    Hi, in WinForms bin ich null drin und ohne konkretem Code kann ich auch nur best guesses machen, aber ich denke, dein letzter Satz geht in die richtige Richtung. Die Trennung in MVVM ist typischerweise:
    • Model: Idealerweise absolut unabhängig von allem anderen. Oft sind das einfach "normale" Klassen aus der BCL oder 3rd Party Libs.
    • View (in deinem Fall die Forms): Kennt VMs.
    • ViewModel: Brücke zwischen Models und View. Kennt Models / Services (was ja oft notwendig ist, um die VM Funktionen zu implementieren), aber NICHT die konkrete View Implementierung. Letzteres nicht, damit die VMs leichter testbar sind / eine andere View Engine genutzt werden kann / ...

    In WPF z.B. kennt das View typischerweise auch immer das ganze VM. Wenn du Beispielsweise ne TextBox an eine Property Message in einem VM bindest, schreibst du im XAML auch Text="{Binding Message}". -> Das View (das XAML) kennt die genaue Struktur des VMs. Das macht auch Sinn - um zwei verschiedene Layer zu verbinden, muss einer zwangsläufig den anderen kennen - wie könnenten sie sonst miteinander interagieren?

    Vielleicht noch abschließend: Der Sinn von VMs ist u.a. oft, dass man seine App leichter (Unit) Testen kannst. Ohne VMs ist das oft gar nicht so einfach. Wie testet man z.B. in einer Form mit Code-Behind, dass sich ein Label updated, wenn ein Button geklickt wird? Das geht zwar, ist aber oft schwierig (ich glaube, Keywords sind hier UI Automation in der Desktop Welt (?)). Leichter wäre die Sache, wenn man die Kernlogik des UIs wegabstrahierst, womit man zum VM kommt. Ein VM repräsentiert ein View. Es enthält die Kernlogik des UIs und typischerweise Properties für alle "Dinge", die im UI angezeigt werden. Durch die Entfernung von Framework-spezifischem UI Code kannst du die VMs leicht testen. Wie das UI letztendlich angezeigt wird, ist dem VM egal. Das ist die Aufgabe des Views. Ein View, egal welches Framework dahintersteckt, hat die Aufgabe, ein VM zu nehmen und die dahinterliegenden Daten / Aktionen darzustellen.
    Ja, ich vergaß. View kennt ViewModel. Danke für die Erinnerung.
    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.
    Es ging mir ja auch um die Mitgabe der ItemViewModels im Startmodul und nicht um das untypisiert, denn typisiert würde das MVVM-Binding nicht klappen, da im Programm und in der DLL die Auflistungen vom selben Typ sein müssten. Ich kann in der DLL nicht ein IEnumerable(Of Object) verwenden und im Hauptprogramm IEnumerable(Of ItemViewModel). Wenn die Typen nicht passen, reagiert das Binding nicht mehr. Und generisch typisiert bringt mir das auch nix, weil ich in der DLL keine Verwendung für den konkreten Datentyp (ItemViewModel) habe.
    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.

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

    Ja, die DLL ist von mir. Sie enthält das DataListPanel (DLP), welches ich als eine datenbindungsfähige DGV-Alternative in .NET konstruiere (Stichwort CommandBinding, geht ja (noch) nicht mit DGVCommandColumns) und die in vielen meiner Programmen auftauchen wird.
    Ohne die DLL müsste ich das DLP in jedem Programm haben, was natürlich nicht Sinn der Sache ist.
    Ich werde demnächst ggf. das DLP mal hochladen, viele Augen sehen mehr (Fehler) als wenige.
    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.