Wie ein untergeordnetes Fenster anzeigen? Oder ist das eine WinForms-Denkweisen-Sackgasse?

  • WPF
  • .NET 5–6

Es gibt 33 Antworten in diesem Thema. Der letzte Beitrag () ist von Akanel.

    Wie ein untergeordnetes Fenster anzeigen? Oder ist das eine WinForms-Denkweisen-Sackgasse?

    Hallo zusammen.

    Mühsam erhängt sich das WPF-Projekt-Eichhörnchen bei mir. Ich wurschtel mich irgendwie durch, glaube aber, dass ich einfach zu sehr noch von WinForms festgehalten werde.
    In der WPF würde ich gern soviel wie möglich per XAML lösen, aber da in den Tutorials von @Nofear23m m.E. eine Sache noch nicht vorkam, stecke ich fest. Wie erzeuge ich sinnvoll in der WPF Unterfenster?
    Klar, mit VB-Code. Man nehme 2 Windows, rufe das eine mit dem anderen auf, also z.B. (weg von Autos, hin zum Fahrrad :) ) :

    VB.NET-Quellcode

    1. Dim NewBikeViewModel = New ViewModels.BikeViewModel(New Models.Bike)
    2. Dim Dialog As New BikeConfigurationWindow(NewBikeViewModel)
    3. Dim DialogResult = Dialog.ShowDialog()
    4. If DialogResult Is Nothing OrElse Not DialogResult Then Return
    5. BikeViewModels.Add(NewBikeViewModel)
    6. AddHandler NewBikeViewModel.DeleteBike, AddressOf DeleteBike

    Aber irgendwie ist mir das zu WinForms-mäßig. Da hab ich doch bestimmt irgendwas verpasst. Ich hab schon irgendwas von Frames gelesen, bei denen man quasi im MainWindow die FrameSource auf ein MainView-UserControl setzt (klappt auch) und dann ggf. die FrameSource zu einem anderen UC wechselt (klappt noch nicht, weil ich auch gar nicht weiß, wie ich dem dann ein ViewModel mit an die Hand gebe).

    Also, Ihr WPF-Spezis: Wie geht's in schön? Ist das mit XAML überhaupt machbar oder muss ich einfach nur die richtige Balance zwischen XAML und VB selber finden? Auch im Hinblick auf die Große Unbekannte MVVM.
    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.
    Hallo

    Als erstes müssten wir mal klären ob es eine MVVM Lösung sein soll oder nicht.
    Hast du eine MVVM Projektstruktur? Siehe aktuell letztes Kapitel in der Tutorialreihe.

    Ich denke nicht denn sonst hättest du sowas wie Dialog oder ShiwDialog ja garnicht zur Verfügung.

    Soll es also eine MVVM Lösung sein??
    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. ##

    Tja, keine Ahnung, gerne soll es eine MVVM-Lösung werden. Zur Vollständigkeit würd ich trotzdem noch wissen, welche Möglichkeiten ist außer der genannten Dialog-Variante habe - unabhängig von MVVM.
    Was kann ich derzeit über die Projektstruktur sagen, bevor ich das Testprojekt selber mal zur Begutachtung hochladen (ist ja eh noch klein)? Es besteht aus 3 Projekten: Models, ViewModels und Views. Aber bei letzterem befürchte ich, dass ich aus Unwissenheit viel zu viel Fremdzeug reinmantsche. Ich lad das heute Nachmittag mal hoch.
    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.
    Hallo

    Ja, kannst du gerne mal hochladen.
    Zum Thema Projektstruktur und Verweise in Punkto MVVM ist wie gesagt das aktuell letzte Kapitel genau das was du suchst. Hier habe ich genau drinnen wie das aussehen soll.

    Lade mal hoch und ich editiere es mal, denn für solche Dinge unter mvvm muss ich etwas ausholen bei der Antwort die dann kommt :)

    Grüße
    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. ##

    Ok, anbei die umgemodelte bisher-Wahrheit. Es ist ein Passwortmanager (noch) ohne tiefere Funktionalität. Es ist also ein einfaches Übungsprojekt.
    Kurze Erklärung. Man soll über den Neu-Button im MainView-UC einen neuen sog. AccessPoint (AP) erzeugen können, indem ein Konfigurationsfenster geöffnet wird. Ein AP besteht aus Zielseite, Loginname, Passworteinstellungen.
    Hat man einen oder mehrere APs erstellt, werden diese im MainView-UC in einer ListBox angezeigt. Abhängig von den Einstellungen werden mehrere Optionen (de)aktiviert, was durch Converter und Commands erreicht wird.

    Habe nun entsprechend des Tuts die Projektmappe erweitert und sehe zumindest nun, warum Dialog nicht mehr geht. Einfach weil der MainWindow-Inhalt (also das MainView) ein UserControl ist und kein Window mehr. Genauso die anderen. Nun hab ich gar keine Ahnung mehr, wie ich die wechseln soll, um auf das Konfigurationsview zuzugreifen oder an dieses ein ViewModel weiterzugeben, damit ich dessen Model ansehen und verändern kann. Das CodeBehind im 2. View ist absolut un-MVVM, da da ins ViewModel direkt reingegrabscht wird, ich weiß. Aber ich glaub ich hab nicht nur da ein Problem. Auch der DesignTimeSupport ist im MainView-UC (?, also UclMainView) nicht möglich, da es u.a. Probleme beim Auffinden der ResourceDictionary.xaml gibt. Zur Laufzeit ging es mal, aber zur Designzeit gar nicht mehr. Auch das mit der Kompilierreihenfolge ist erstmal noch ein Problem, weil ich zig mal rumprobieren muss, bis Visual Studio die fertigen Assemblys findet, obwohl sie schon existieren. Daher wird immer rumgemeckert, dass Verweise nicht gefunden/gesetzt werden können. :cursing:
    Dateien
    • PWM.zip

      (1,42 MB, 82 mal heruntergeladen, zuletzt: )
    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.
    Hallo

    Ich werde mir mal mühe geben.

    Ich habe mir das Projekt mal angesehen. Also von der Grundstruktur her bist du mal Save.
    Das ist schon mal MVVM Konform, was schon mal eine sehr gute Grundlage ist.

    Ich weis, manche Dinge gestalten sich dadurch etwas umständlicher, du wirst aber (später, also wenn das Projekt größer wird) sehen welche Vorteile du daraus ziehen kannst.
    Kopf hoch, du packst das.

    Als erstes habe ich mal ein MainViewModel erstellt, das gab es noch nicht. Stattdessen hattest du Code in der Codebehind des MainWindow. Das ist nicht notwendig, wir binden IMMER auf ViewModels.
    Im UclMainView hattest du einen DesignTimeDataContext auf AccessPointViewModel. Aber dieses Control soll ja eine Auflistung von AccessPoints zeigen und kein einzelnes.
    Ist also falsch. Hier kann als DesignTimeDataContext das MainViewModel rein.

    Das mit dem ResourceDictionarys ist so ne Sache. Ich gebe Sie immer mittels MergeDictionary an.

    XML-Quellcode

    1. <ResourceDictionary>
    2. <ResourceDictionary.MergedDictionaries>
    3. <ResourceDictionary Source="ResourceDictionary.xaml"/>
    4. </ResourceDictionary.MergedDictionaries>
    5. </ResourceDictionary>


    Jetzt packen wir für die DesignTime mal ein paar AccessPoints rein - wie du es in der CodeBehind hattest - damit wir im Designer auch etwas sehen - macht vieles einfacher zum Designen.
    DesignTimeSupport funktioniert nämlich nur richtig wenn man auf eine Klasse bindet welche nicht von WindowBase erbt.
    Dafür müssen wir aber mal wissen ob wir zur Designtime unterwegs sind. Das implementiere ich in die Basisklasse ViewModelBase.

    VB.NET-Quellcode

    1. Private Shared ReadOnly HostProcesses As New List(Of String)({"XDesProc", "devenv", "WDExpress", "WpfSurface"})
    2. Public ReadOnly Property IsInDesignMode As Boolean
    3. Get
    4. Return HostProcesses.Contains(Process.GetCurrentProcess().ProcessName)
    5. End Get
    6. End Property


    VB.NET-Quellcode

    1. Public Class MainViewModel
    2. Inherits ViewModelBase
    3. Public Sub New()
    4. AccessPoints = New ObservableCollection(Of AccessPointViewModel)
    5. If IsInDesignMode Then
    6. With AccessPoints
    7. .Add(New AccessPointViewModel(New Models.AccessPoint With {.Name = "Foo", .Login = "", .Password = "asd", .LinkToPasswordChangeSite = "sdklf", .LastPasswordChangeDate = #01/01/2000#}))
    8. .Add(New AccessPointViewModel(New Models.AccessPoint With {.Name = "Bar", .Login = "2", .Password = "csd", .LinkToPasswordChangeSite = "sdklf", .LastPasswordChangeDate = #11/01/2021#}))
    9. .Add(New AccessPointViewModel(New Models.AccessPoint With {.Name = "Baz", .Login = "3", .Password = "vsd", .Website = "sdflk"}))
    10. .Add(New AccessPointViewModel(New Models.AccessPoint With {.Name = "Buzz", .Login = "4", .Password = "bnsd", .OldPassword = "vjg"}))
    11. End With
    12. End If
    13. End Sub
    14. ...
    15. ...




    Gut, dann haben wir das mal.

    Jetzt können wir mal mit der Sache mit dem Window anfangen.
    Erstmal ist es ja so das das ViewModel (wie du gesehen hast) keine View kennt. Also müssen wir einen kleinen Umweg gehen. Stichwort Abstraktion. Dieser führt über Interfaces.
    Also machen wir uns erstmal ein Interface und bestimmen was wir können wollen. Das pack ich immer gerne in den Namespace "Services".

    VB.NET-Quellcode

    1. Namespace Services
    2. Public Interface IDialogWindowService
    3. Function ShowModalDialog(windowname As String, datacontext As Object, owner As Object, Optional topMost As Boolean = False, Optional showInTaskbar As Boolean = True) As Boolean
    4. Sub CloseDialog()
    5. End Interface
    6. End Namespace


    Jetzt wissen wir mal was wir wollen. Ein Fenster öffnen und wieder schließen. Beim öffnen möchten wir natürlich vom VM aus auch ein paar Dinge Einstellen können, dafür die Parameter.
    Du siehst im Interface nichts von Window, Control, DialogResult oder sonstwas. Ja, wir hätten diese Klassen ja auch nicht zur Verfügung im ViewModel.

    Jetzt brauchen wir mal ein Fenster welches später geöffnet werden soll. Dies wird/ist ein allgemein gehaltenes Fenster, denn dieses wird JEDES MAL verwendet wenn wir einen Dialog öffnen, egal was darin angezeigt werden soll.
    Ich erstelle also mal eines mit dem Namen DialogWindow im View-Projekt.

    Der XAML hier ist ganz einfach, wir definieren nur das später unser Inhalt gerendert wird.

    XML-Quellcode

    1. <Window x:Class="DialogWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:PWM.View"
    7. mc:Ignorable="d"
    8. Title="DialogWindow" Height="450" Width="800">
    9. <ContentPresenter Content="{Binding}"/>
    10. </Window>


    In der CodeBehind (Ja, CodeBehind ist erlaubt wenn dieser rein die View betrifft) ist ein wenig Code um einfach schließen per ESC zu erlauben und um über einen Klassentyp einfach das Owner-Fenster finden zu können. Nichts aufregendes.

    OK, wo hauchen wir dem Interface nun leben ein. Denn in dem Code brauchen wir nun ja dann Window, Control und diese Klassen. In der App selbst haben wir auf jeden Fall all diese Dinge zur Verfügung. Also machen wir uns genau dort eine Klasse welche IDialogWindowService implementiert und leben einhaucht. Am besten auch wieder im Namespace Services.

    VB.NET-Quellcode

    1. Namespace Services
    2. Public Class DialogWindowService
    3. Implements IDialogWindowService
    4. Private _currentSpsWindow As DialogWindow
    5. Public Function ShowModalDialog(windowname As String, datacontext As Object, owner As Object, Optional topMost As Boolean = False, Optional showInTaskbar As Boolean = True) As Boolean Implements IDialogWindowService.ShowModalDialog
    6. Dim spswin As New DialogWindow(windowname, datacontext, SizeToContent.WidthAndHeight, WindowStartupLocation.CenterOwner)
    7. _currentSpsWindow = spswin
    8. spswin.Topmost = topMost
    9. spswin.ShowInTaskbar = showInTaskbar
    10. spswin.Owner = spswin.FindOwnerWindow(owner)
    11. Return CType(Application.Current.Dispatcher.Invoke(Function() spswin.ShowDialog), Boolean)
    12. End Function
    13. Public Sub CloseDialog() Implements IDialogWindowService.CloseDialog
    14. If _currentSpsWindow IsNot Nothing Then
    15. _currentSpsWindow.Close()
    16. End If
    17. End Sub
    18. End Class
    19. End Namespace


    Im Grunde für sich selbst auch nichts wunderbares. Bis hier kein Hexenwerk und auch keine Magie.
    Interessant wird's wenn wir nun darüber nachdenken wie wir nun diese Klasse, welche ja das Fenster öffnet vom ViewModel aus ansprechen, wir kennen diese Klasse nicht. Aber das Interface.
    Wir brauchen also eine Klasse welche von der App aus Instanziiert wird und wir in das ViewModel reichen, aber eben nicht die Klasse selbst sondern nur das Interface, über das Interface kennen wir im ViewModel ja die Methoden welche in dieser implementiert sind, somit können wir behaupten "Führe ShowModalDialog aus" ohne direkt die Klasse zu sehen.

    Hierfür brauchen wir irgendeinen Container der uns überall zur Verfügung steht. Also machen wir diesen am besten auch im ViewModel.
    Diesen halten wir aber gleich recht allgemein, damit wir da später alles mögliche reinpacken können. Wir brauchen später ja noch so einige Dinge.
    Messagebox, Cursor, Errormeldung, Inputbox, OpenfileDialog, SaveFileDialog usw.

    Hier meine implementierung eines solchen Containers.

    VB.NET-Quellcode

    1. Public Class ServiceContainer
    2. Public Shared ReadOnly Instance As New ServiceContainer()
    3. Shared Sub New()
    4. ServiceMap = New Dictionary(Of Type, Object)()
    5. ServiceMapLock = New Object()
    6. End Sub
    7. Public Sub AddService(Of TServiceContract As Class)(implementation As TServiceContract)
    8. SyncLock ServiceMapLock
    9. ServiceMap(GetType(TServiceContract)) = implementation
    10. End SyncLock
    11. End Sub
    12. Public Shared Function GetService(Of TServiceContract As Class)() As TServiceContract
    13. Dim service As Object = Nothing
    14. SyncLock ServiceMapLock
    15. ServiceMap.TryGetValue(GetType(TServiceContract), service)
    16. End SyncLock
    17. Return TryCast(service, TServiceContract)
    18. End Function
    19. Shared ReadOnly ServiceMap As Dictionary(Of Type, Object)
    20. Shared ReadOnly ServiceMapLock As Object
    21. End Class


    Dieser ist als Singleton implementiert damit wir recht simplen Zugriff von überall haben, und zwar immer auf die selbe Instanz (in welcher sich unsere Elemente befinden).

    Super, das war im Grunde schon fast alles. Jetzt können wir also mal unsere DialogWindowService Klasse in diesem Container packen.
    Ich mache das immer im App_Startup der Application.vb.

    VB.NET-Quellcode

    1. Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
    2. ServiceContainer.Instance.AddService(Of IDialogWindowService)(New DialogWindowService)
    3. End Sub


    Das wars. Nun öffnen wir einfach mal nen Dialog wenn wir auf den Button klicken. Da du dies bis Dato mit einem Click-Event gelöst hast muss das mal weg. Wir verwenden Binding und somit Commands.
    Command im MainViewModel erstellen

    VB.NET-Quellcode

    1. Private _createNewAccessPointCommand As ICommand
    2. Public ReadOnly Property CreateNewAccessPointCommand As ICommand
    3. Get
    4. If _createNewAccessPointCommand Is Nothing Then _createNewAccessPointCommand = New RelayCommand(AddressOf CreateNewAccessPointCommand_Execute)
    5. Return _createNewAccessPointCommand
    6. End Get
    7. End Property
    8. Private Sub CreateNewAccessPointCommand_Execute(obj As Object)
    9. End Sub


    XAML abändern

    XML-Quellcode

    1. <Button Content="{StaticResource NewIcon}" HorizontalContentAlignment="Stretch" ToolTip="neuen AccessPoint erstellen"
    2. Command="{Binding CreateNewAccessPointCommand}"/>


    Im Execute des Commands öffnen wir nun das Fenster und übergeben eine neue Instanz eines AccessPoints da wir ja einen neuen erstellen wollen.

    Wir holen uns über den ServiceContainer das richtige Objekt. Nämlich das vom Typ IDialogWindowService.
    Hier führen wir die Methode ShowDialog() auf und übergeben die Parameter mit den Werten die wir wollen.

    VB.NET-Quellcode

    1. Private Sub CreateNewAccessPointCommand_Execute(obj As Object)
    2. Dim vm = New AccessPointViewModel(New Models.AccessPoint())
    3. Dim dialogService = ServiceContainer.GetService(Of IDialogWindowService)
    4. dialogService.ShowModalDialog("newAp", vm, Me, True, True)
    5. AccessPoints.Add(vm)
    6. End Sub


    Das wars.

    Tja, wir sehen aber in dem Dialogfenster wenn es sich öffnet nicht viel. Nämlich nur folgendes



    Ja, mehr haben wir noch nicht definiert.
    Also jetzt noch ein DataTemplate für den Typ den wir übergeben. Also AccessPointViewModel.
    Das mache ich immer in der Application.xaml.

    XML-Quellcode

    1. <Application.Resources>
    2. <DataTemplate DataType="{x:Type ViewModels:AccessPointViewModel}">
    3. <View:UclAccessPointItem/>
    4. </DataTemplate>
    5. </Application.Resources>


    Jetzt sieht das Fenster so in etwa aus.



    Und nachdem man das Fenster schließt dann so.



    Und nach dem selben Prinzip funktioniert das mit allem möglichen. Nur das du nun die ganze Infrastruktur bereits hast. Also einfach ein Interface machen, eine Klasse welche dieses implementiert und mit Code versieht. Fertig.

    Jetzt kannst du deine Fantasie spielen lassen wir du z.b. den Dialog Rückgabewert bekommst. Das habe ich dir absichtlich nicht reingepackt, so bist du quasi gezwungen dich damit zu Beschäftigen und zu sehen wie das ganze funktioniert ;)

    Ich hoffe ich habs gut rübergebracht und es hilft. Lass dich nicht davon abschrecken, sieht Anfangs komplizierter aus als es tatsächlich ist.

    PS: Falls du dich fragst wozu das ganze.... Berechtigt. Ich habe in dein "Testprojekt" auch ein paar UnitTests reingepackt um den Code Testen zu können. Da siehst du nun den Vorteil.
    Ein Test schlägt fehl. Nun liegt es an dir den Code so zu ändern das dieser Test erfolgreich abgeschlossen wird. ;)
    Mit dem Tastenkürzel STRG+R,A kann man schnell Tests ausführen und man weis innerhalb von Millisekunden ob alles klappt. Top bei größeren Projekten und man weis immer ob man was kaputt gemacht hat (solange man einen Test geschrieben hat).



    Grüße
    Sascha
    Dateien
    • PWM.zip

      (1,14 MB, 82 mal heruntergeladen, zuletzt: )
    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. ##

    Ok, bis ich das auch nur annähernd erfasst, geschweige denn verarbeitet habe, wird einige Zeit vergehen. Wenigstens habe ich schon häufiger mit UnitTests (anfänglich) gearbeitet und komm damit an sich klar. Das mit dem (korrekten) DataContext ist für mich immer noch ein wenig Rätselraten, aber irgendwann raff ich das schon auch noch.
    Die ganzen Zusatzkonstrukte muss ich mir in Ruhe antun und sehen, wie Du da was verarbeitet hast und welche Auswirkungen das hat. Dass die "artgerechte" Handhabung der WPF anfangs komplizierter ist, war mir bewusst, weshalb ich buchstäblich jahrelang damit gehadert habe. Ich hatte geahnt, dass das nicht einfach so schwuppsdiwupps ein Wechsel von WinForms auf WPF wird.

    Ne kleine Anmerkung für Dich. Probier mal statt der HostProcesses-Auflistung das hier:

    VB.NET-Quellcode

    1. Public ReadOnly Property IsInDesignMode As Boolean
    2. Get
    3. Return System.ComponentModel.LicenseManager.UsageMode = System.ComponentModel.LicenseUsageMode.Designtime
    4. End Get
    5. End Property
    Scheint auch in der WPF zu klappen. Zumindest bei diesem meinigen Projekt. Vielleicht ist es eine verwendbare Vereinfachung.

    Dass der NewIcon-Klick nicht zum Konfigurationsfenster führt, ist sicherlich Absicht und als kleine Übung für mich gedacht. Die Entsorgung des CodeBehind-Mists in jenem Fenster sehe ich als meine Aufgabe. ;)
    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.
    Hallo

    VaporiZed schrieb:

    Das mit dem (korrekten) DataContext ist für mich immer noch ein wenig Rätselraten

    Ja, Anfangs manchesmal verwirrend. Aber im Grunde ja einfach solange zu Ordnung haltest. ICH habe für jede View ein ViewModel.
    Also wie in deinem Fall nun ein MainViewModel und ein AccessPointViewmodel. Das uclMain ist an das MainViewModel gebunden. Denn im View ist eine Auflistung von AccessPoints als Listbox. Im Viewmodel eine Collection(Of AccessPoints) gehören also zusammen.
    In der Listbox haste als DataTemplate ein uclAccessPoint oder so. dieses soll immer einen AccessPoint halten. Als muss dieses als DataContext ein AccessPointViewModel erhalten.

    VaporiZed schrieb:

    Ne kleine Anmerkung für Dich

    OK, hatte ich noch nie probiert. Das mit den Hostprozessen funzt bei mir super und ist für mich kontrollierbar, bei dem Code weis ich nicht wirklich was im Hintergrund abgefragt wird.

    VaporiZed schrieb:

    Dass der NewIcon-Klick nicht zum Konfigurationsfenster führt

    Sorry, hatte ich garnicht am Schirm.
    Wäre aber nun eine super Aufgabe für dich ein WindowService zu erstellen. Also nicht als Dialog sondern als normales Fenster.

    Ja, der Rest (CodeBehind -> ViewModel Klasse) ist deine Aufgabe. ;)

    Bei Fragen kannst du dich ja gerne melden.

    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. ##

    Nofear23m schrieb:

    ICH habe für jede View ein ViewModel.
    Ok, da habe ich wohl schon den ersten Grundsatzdenkfehler. Ja, ich habe ein AccessPointViewModel. Allerdings nicht, weil ich damit das AccessPointModel an das Konfigurationsview anbinden will, sondern an alle. Ich bin davon (wohl falsch) ausgegangen, dass ich ein Model habe (AccessPoint, mit all den Grundsatzdaten eines AccessPoints), dann ein ViewModel, welches alle Eigenschaften des Models repliziert und Logik reinbringt und dann eben auch dafür da ist, das Model in allen Views zugänglich zu machen. Also one for all! Dass jede View ihr eigenes ViewModel hat, also in meinem Fall effektiv:
    • eine Modelklasse AccessPoint
    • eine eigene ViewModel-Klasse pro View
    • ein View pro … naja, Window eben (wobei das ja falsch ausgedrückt ist, weil die Windows/UCs ja die Views sind
    nein, das muss ich zugeben, hatte ich überhaupt nicht auf dem Schirm und verwirrt mich jetzt erstmal, weil ich dachte, dass alle AccessPoints eben in einem einzigen ViewModel sind, von dort aus geändert werden und fertig. Wenn jetzt jede(s?) View ihr/sein eigenes ViewModel hat, werden die Model-Instanzen ja immer wieder neu gespeichert/umgelagert/wasauchimmer. Das Verständnisproblem ist jetzt aber: Was passiert beim Umschalten zwischen Views und somit ViewModels? Da die ViewModels ja die Modelklassen immer wieder instanziieren, starte ich beim MainViewModel (was ich auch falsch verstanden habe; ich hatte es gedanklich nicht mit der MainView assoziiert, sondern als oberstes ViewModel und das AccessPointViewModel als … naja … untergeordnetes ViewModel). Das MainViewModel erzeugt dann aufgrund von DB-Einträgen z.B. 5 Model-Instanzen. Wenn jetzt aber die View und somit das ViewModel gewechselt wird, müssen doch die Model-Instanzen entweder ins nächste ViewModel übertragen werden oder erstmal in die DB zurück, um dann wieder im nächsten ViewModel neu geladen zu werden? Oder verwurschtel ich mich jetzt im totalen Gedankenspaghettichaos ?(
    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.
    Sorry.

    Ich habe mich wohl falsch ausgedrückt. du hast eh richtig gedacht.
    OK, ich schreibe es anders.
    Ich habe für jedes ViewModel mind. ein View. UND jedes View ist an ein ViewModel gebunden, an welches ist erstmal egal.

    Sicher kannst du mehrere verschiedene Views an ein und das selbe ViewModel binden. Um gottes willen.

    Edit: Und nicht ein View erstellt eine Instanz eines ViewModels. Welche Instanz welcher Klasse gebunden ist entscheidest du vom ViewModel aus.
    Ausser dem "MainView" erstellt in der regel ein View keine Instanzen, es wird nur auf eine Instanz gebunden!

    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. ##

    Oh, dann hab ich mich irgendwo falsch ausgedrückt, finde aber auf Anhieb nicht wo. Was ich sagen wollte: Die ViewModels erstellen ja die Model-Instanzen. Obwohl, auch nicht richtig. Einer ViewModel-Instanz wird eine Model-Instanz an die Hand gegeben.
    Dass Views keine ViewModel- oder gar Model-Instanzen erstellen, ist mir bewusst.

    Aber jetzt komm ich tatsächlich gar nicht mehr hinterher. ;(
    Vielleicht kommen wir so weiter: Ich habe 2 Views
    • Main, in dem alle AccessPoints in einer ListBox visualisiert werden
    • Konfigurationsfenster, in dem ein AccessPoint durch den User manipuliert werden kann
    desweiteren habe ich zu Beginn keine AccessPoints (also keine Modelinstanzen). Diese können nacheinander erstellt werden. Und hoffentlich später gespeichert und wieder geladen werden.

    Wieviele ViewModels sind jetzt sinnvoll? Was machen diese ViewModels? Ich dachte: Ein einziges ViewModel ist Vermittler zwischen allen Views und der einen bei mir vorhandenen Modelklasse und packt noch Logik dazu, da ja die Modelklasse eine POCO-Klasse ist. Und ich dachte, dass diese eine ViewModel-Klasse dafür verantwortlich ist, eine Model-Instanz zu erstellen, wenn ein entsprechender Befehl vom View kommt. Aber wenn mehrere ViewModels unterwegs sind oder sein können, dann blick ich eben nicht mehr, welche ViewModel-Klasse da jetzt für die Model-Instanzerstellung zuständig ist.
    Mein Verständnis bisher: Zu jeder ViewModel-Instanz gibt es eine Model-Instanz. Aber wenn ich 2 ViewModel-Klassen habe, müsste ich ja … doppelt so viele Model-Instanzen haben ?(

    ##########

    Oder nochmal anders:
    ich habe 2 Views: Main, Konfiguration
    ich starte mit 0 Modelinstanzen

    ich erstelle, ausgelöst durch GUI-Klicks und Dateneingabe einen AccessPoint, also eine Modelinstanz.

    Brauche ich ein ViewModel insgesamt oder ein ViewModel pro View?
    Wo wird der eine AccessPoint dann hinterlegt? In jedem ViewModel, weil jedes ViewModel eine ObervableCollection(Of AccessPoint) hat? Oder nur in einem ViewModel? Oder bin ich ganz falsch?
    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“ ()

    OK.

    Als erstes vergiss mal die Model oder gar Model-Instanzen.
    Ist für das Verständnis komplett irrelevant.

    Der einzige Grund warum man/du Models benötigst ist lediglich weil du (später) persistieren willst. also lassen wir diese mal aussen vor, denn du reichst ja beim instanziieren einer ViewModel-Instanz ja eine Model-Instanz rein.
    Ist aber völlig egal, die View kennt kein Model, nur ein ViewModel.

    Wenn du in deinem MainView eine Listbox mit einer Liste von AccessPoints hast und einen Button um Einstellungen (ich nenne es jetzt mal Einstellungen) zu öffnen, hast du folgendes.

    Die MainView ist an ein MainViewModel gebunden. dieses beinhaltet ein Observablecollection(Of AccessPointViewModel) und einen Command welche dann die Einstellungen öffnet.
    Die ListBox-ItemsSource wird auf die Observablecollection gebunden. Für jedes Element in dieser Auflistung wird ein View vom dem Typ der in der Listbox als DataTemplate angegeben wird erstellt und automatisch über Vererbung auf dieses gebunden.

    Aber das siehst du eh alles in meinem Beispiel. Gehe das mal durch, dann wird sicher einiges klarer.
    Im Grunde Bildest du "die echte Welt" als ViewModels ab.

    Probiers mal so umzubauen wie du das aktuelle Beispiel benötigst und bei Fragen frag einfach und wir gehen es durch.

    Grüße
    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. ##

    Oookey … ein paar Trippelschritte bin ich weiter. Habe etwas die DialogWindowService.vb verändert, um da besser durchzublicken. Letztenendes wird ja in der Klasse u.a. das also doch verwendete ShowDialog quasi verpackt und garniert. Ok.
    Und dass beim Erzeugen des SubWindows dann wohl aus der Application.xaml bzw aus den Application.Resources das VM mit dem entsprechenden UC verknüpft wird, hab ich erst jetzt erfahren. Jetzt wird mir zumindest mal das gewünschte Window angezeigt. Wie ich dann aber ggf. verschiedene SubWindows mit demselben VM erzeugen kann, weiß ich damit noch nicht.
    Jetzt steh ich vor dem Problem, dass ich nicht weiß, wie ich dieses wieder schließen soll. Ok, das mit Escape-Drücken oder einem Button per XAML-Code IsCancel zuzuweisen, ist klar. Aber Einem OK-Button auf dem UC klarzumachen, dass das Window nun mit dem passenden DialogResult geschlossen werden soll, das bekomm ich nicht hin. Klar, ich kann in der Window_KeyDown-Methode der DialogWindow-Klasse, irgendeinem Key sinnfreierweise eine Zusatzfunktion verpassen, also bei F1 ein DialogResult setzen. Aber das ist ja Hunz. Enter ist noch schlimmer, weil ich dann natürlich e.Handled auf True setzen muss, sonst wird das SubWindow gleich wieder aufgerufen. Aber wie verpass ich dem OK-Button die passende Funktionalität? Per Event? Schließlich muss vom Button über das UC das DialogWindow ja informiert werden, dass das Window mit DialogResult = True geschlossen werden soll.
    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 schrieb:

    Wie ich dann aber ggf. verschiedene SubWindows mit demselben VM erzeugen kann, weiß ich damit noch nicht.

    Zwar ist dies nun in diesem Fall (wir reden ja von Dialogen) sicher nur eine reine verständnisfrage da dies in diesem Scenario ja nicht wirklich sinnvoll wäre aber....

    Das erzeugen ist weniger das Problem, immer wenn du die Methode aus dem Service aufrufst öffnet sich ein Fenster. Wenn alle geöffneten Fenster das selbe ViewModel als Context haben ist es schwer gezielt das richtige wieder zu schließen. Also nicht das öffnen ist das Problem sondern das schließen. Hierfür müsste man etwas umbauen, aber das führt hier zu weit. Konzentrieren wir uns mal auf die Basics, sobald man das versteht, ist solch ein umbau auch leicht.

    VaporiZed schrieb:

    Aber Einem OK-Button auf dem UC klarzumachen, dass das Window nun mit dem passenden DialogResult geschlossen werden soll

    Du musst weggehen vom Control-Denken. Nicht das UserControl muss es dem Window klarmachen.

    Es gibt nun zwei Möglichkeiten:

    Die einfache: Dein UserControl hat ja ein ViewModel als DataContext, in diesem UserControl befindet sich ein OK Button. Dieser OK Button ist gebunden an einen Command in deinem ViewModel.
    Dieser Command prüft die Eingaben, Speicher, und was weis ich was sonst noch, und schließt anschließend das Fenster indem er die Close Methode des Services aufruft.
    Es passiert also alles über das Service.

    Die kompliziertere: Eine weitere Methode ist es, das Interface (mein Beispiel war ja ein Grundbaustein oder Beispiel eines Interfaces für ein DialogWindowService) danach aufzubauen.
    Man könnte also das Interface so aufbauen das man nicht einen DataContext also "Object" übergibt sondern z.b. ein "IDialogViewModel" nenne ich es mal.
    Dieses impliziert wieder zwei Commands. Nennen wir Sie mal AbortDialogCommand und OKDialogCommand.
    Nun kannst du innerhalb der Fensters den DatenKontext in der CodeBehind Casten und alles damit machen. Was aber nicht mal nötig wäre, du könntest nun in dem Fenster unten zwei Buttons haben welche auf die Commands gebunden sind (wir wissen ja aufgrund des Interface das es die zwei Commands immer gibt wenn eine ViewModel Klasse dieses implementiert) welche diese Aufgaben übernehmen.

    Also wie immer muss man sich einfach im Vorfeld überlegen was man genau braucht und dies auf Interfaces abbilden. Das ist der Bauplan mit welchem alle Beteiligten (Fenster, Buttons) wissen was sie zu tun haben.

    Hoffe das war verständlich.

    PS: Das Kapitel wo es um diese Services geht kommt übrigens schon bald, ich bin jetzt wieder so weit und hab nun auch etwas Hilfe. Es dauert also nicht mehr lange und ihr bekommt eine detaillierte Anleitung. ;)

    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. ##

    Ok, habe nun das IDialogWindowService-Interface so geändert, dass der CloseDialog-Methode noch ein Boolean-Wert übergeben wird, mit dem mitgeteilt wird, welches DialogResult beim MeinViewModel ankommt, um dort festzulegen, ob der neue AccessPoint übernommen werden soll oder ob auf [abbrechen] geklickt wurde und somit der neue verworfen wird.

    Die Commands sind nun sehr minimalistisch. Effektiv geben sie erstmal im Execute-Teil nur einen Befehl ans AccessPointViewModel (APVM) und im CanExecute-Teil den Rückgabewert einer entsprechenden APVM-Funktion. Also RelayCommand als ganz allgemeiner Unterbau, konkrete Commands als Vermittler zwischen UserControl und APVM. Und im APVM werden dann tatsächliche Befehle ausgeführt und Überprüfungen anhand der AP(VM)-Daten durchgeführt. Ich hoffe, dass das passt.

    Bei meinem Config-Dialog habe ich nun folgende Denkschwäche: Ich habe 2 TextBoxen. Die eine ist ans APVM mittels Binding an eine Property gebunden, die die akzeptablen Zeichen für ein Passwort beherbergt/anzeigt. Die 2. TextBox soll hingegen die verbotenen Zeichen anzeigen, damit man bei einer Website, die sagt: »folgende Zeichen sind verboten« nicht lang bei den erlaubten Zeichen suchen muss, um die verbotenen Zeichen rauszulöschen, sondern man trägt die verbotenen Zeichen in die 2. TextBox ein und die 1. TextBox und somit das APVM "folgen" (erlaubte Zeichen werden also automatisch geändert). Nun bin ich mir nicht im Klaren, wie ich das korrekterweise mache. Mach ich es im CodeBehind des UCs, muss das UC wissen, welche Zeichen es gibt. :( Mach ich es im APVM, muss ich eine zusätzliche Property namens ForbiddenCharacters erstellen, die allerdings nur für das Config-Window gebraucht wird. :( Gibt es noch einen besseren Weg?

    Ist für jede GUI-APVM-Interaktion mittels Button eigentlich ein Command sinnvoll? Für mein Popelprogramm hab ich jetzt schon 13 Commands. Als WPF-Anfänger ist das erstmal viel, weil ich ja nicht weiß, ob ich mich überhaupt in die richtige Richtung bewege. Ist es am Ende durchaus normal, dass man irgendwann Dutzende davon hat? Auch eine Frage deshalb, weil ich für jeden Command mittels SubFile und Partial eine eigene Datei anlege.
    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“ ()

    Hallo

    VaporiZed schrieb:

    abe nun das IDialogWindowService-Interface so geändert, dass der CloseDialog-Methode noch ein Boolean-Wert übergeben wird

    Das kommt mir Komisch vor. Der Dialog sollte sich von selbst schliessen. Das sollst du ja garnicht vom VM aus machen und auch nicht machen können. Denn bei einem Dialog wird der Code angehalten.

    VaporiZed schrieb:

    Mach ich es im CodeBehind des UCs

    Bitte nicht. In die CodeBehin dürfen nur dinge die die UI betreffen. Du willst hier aber "Daten" manipulieren. Dafür ist das VM ja da.

    VaporiZed schrieb:

    Ist für jede GUI-APVM-Interaktion mittels Button eigentlich ein Command sinnvoll?

    Im Grunde ja, es gibt aber Aufnahmen wo man mit Parametern besser bedient ist.

    VaporiZed schrieb:

    Für mein Popelprogramm hab ich jetzt schon 13 Commands.

    UI, da würde ich sagen stimmt etwas mit dem Design nicht.

    VaporiZed schrieb:

    Auch eine Frage deshalb, weil ich für jeden Command mittels SubFile und Partial eine eigene Datei anlege.

    Wie? Nutzt du keine RelayCommand-Klasse?

    Schiebe hier mal das Projekt rein, ich schau gerne drüber, dann kann ich mir mehr vorstellen. Bekommen wir schon hin.

    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. ##

    Ok, dauert immer, bis ich meine eigene Extensions-DLL aus dem Projekt entfernt habe und durch den Standardkram ersetzt habe.
    Die RelayCommand-Klasse nutze ich schon. Aber die anderen Commands sind quasi die echten Commands. Aber ich sehe ich schwebe von einem Falschverständis zum nächsten.
    Das mit dem UC-CodeBehind habe ich gelassen und mit der anderen APVM-Property umgesetzt. Es klappt. Aber mir fehlt immer noch das Verständnis, was akzeptabel ist und was no-go.
    Dateien
    • PWM.zip

      (44,22 kB, 69 mal heruntergeladen, zuletzt: )
    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.
    OK, jetzt weis ich was du meinst.

    Ja, das sind schon einige Buttons die du da auf einem AccessPoint-View drauf hast.
    Im Ein paar kannst du sicher zusammenfassen indem du mit Parametern arbeitest.

    Beispiel:
    ToggleDigitCommand, ToggleSpecialCharacterCommand und ToggleLettersCommand kann man Zusammenfassen zu einem "TogglePasswordCommand"
    In XAML gibst du einfach dann einen Parameter mit ala CommandParameter=Digit

    Das fragst du dann in einem Select Case im Code ab und gut. Denn im Grunde machen diese Command immer das selbe, nur auf eine etwas andere Art und weise.

    VaporiZed schrieb:

    Aber mir fehlt immer noch das Verständnis, was akzeptabel ist und was no-go.

    Imho gibt es kein Richtig oder Falsch. Du musst es für dich und das Projekt entschieden. Je größer ein Projekt ist/wird desto sauberer versuche ich zu Arbeiten denn mit MVVM kann auch ein wirklich großes Projekt gut wartbar bleiben, bei kleineren Projekten macht MVVM ja so gesehen auch nicht viel Sinn. Mit steigender Projektgröße singt die Komplexität im Verhältnis.

    Aber das Verständnis dazu hast du eh. Das Projekt sieht doch gut Strukturiert aus. Alles gut. Bist auf nem super weg.
    Und bis jetzt funzt ja alles odeR?

    Hoffe du weist was ich meine.

    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. ##

    Ja, es funktioniert soweit. Klar, noch die effektive Datenarbeit einbauen und so. Aber von der Grundfunktionalität isses erstmal getan. Nur hab ich noch nicht raus, wie die Alternative zu meinem CloseDialog(True/False) aussehen könnte. Da werd ich mir am Wochenende nch einige Deiner Posts reinziehen, vielleicht begreif ich es dann endlich.

    Nofear23m schrieb:

    Dein UserControl hat ja ein ViewModel als DataContext, in diesem UserControl befindet sich ein OK Button. Dieser OK Button ist gebunden an einen Command in deinem ViewModel.
    Dieser Command prüft die Eingaben, Speicher, und was weis ich was sonst noch, und schließt anschließend das Fenster indem er die Close Methode des Services aufruft.
    So wie ich das verstehe ruft der Command oder eben das APVM den Service herbei und weist ihn an, das DialogWindow zu schließen. Es läuft also zwar über den Service. Aber der Aufrufer/Verursacher ist das APVM. Also Button auf UC -> Command -> APVM -> Service -> CloseDialog(True/False). Ist das so ok, oder ist das von mir falsch interpretiert und somit unzulässige Schichtenvermischung? Wenn B, wo bin ich falsch abgebogen?
    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 schrieb:

    Nur hab ich noch nicht raus, wie die Alternative zu meinem CloseDialog(True/False) aussehen könnte.

    In wie weit alternative? In dem Fall eines Dialogs ginge es "Einfacher" da man bei den Button zuweisen kann welchen DialogResult diese zurückgeben.

    Soll ich dir das am Abend mal in dein Beispiel adaptieren?

    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. ##