ViewModel im View nicht verfügbar!?

  • WPF

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

    Ansonsten meldest du dich eben wieder mit dem Code den du hast und schreibst einfach was nicht geht. Das finden wir es schon raus. 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. ##

    @Nofear23m
    Hallo Sascha,
    ich habe deine Vorschläge beherzigt und versucht umzusetzen.
    Aktuell habe erstmal die View Designed damit ich weiß welche ViewModels ich alles brauche.
    Ich habe mich für einen Header, eine StatusBar und halt die Hauptanzeige entschieden.
    Da die StatusBar nur den Benutzer und eine Nachricht anzeigen soll bin ich damit angefangen. Kann ja nicht so wild sein, dachte ich.
    Pustekuchen, da habe ich schon meine ersten Probleme.
    Ich bekomme die StatusBar zur Laufzeit nicht angezeigt, bzw ist sie leer.
    Da ich jetzt nicht den ganzen relevanten Code posten möchte habe ich das kleine Projekt mal angehangen.
    Könntest du es dir mal anschauen und mir evtl sagen was ich falsch gemacht habe?

    In WinForms ging so ein Adressbuch mit DataBinding danke @ErfinderDesRades seiner Hilfe ratz fatz.
    Ich fürchte fast das mir Grundlagen von arbeiten mit Klassen fehlen.
    Aber ich warte mal ab wo der fehler ist.

    Danke vorab.
    Dateien
    • Adressbuch00.zip

      (41,68 kB, 7 mal heruntergeladen, zuletzt: )
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    Hallo @Akanel

    Ich habe gleich ein paar Dinge ausgebessert aber nur Kleinigkeiten, du bist auf den völlig richtigen Weg. Das hat alles so gepasst.
    Das einzige warum du NIE was angezeigt bekommen kannst... du hast keinen Datenkontext angegeben.

    Zwar hast du einen Designtime-Datenkontext angegeben und somit im UserControl alles brav gesehen, aber zur Laufzeit hattest du keinen.
    Also ein:

    XML-Quellcode

    1. <UserControl.DataContext>
    2. <viewmodel:MAinViewVM/>
    3. </UserControl.DataContext>

    rein und schon klappt es.

    Ich habe dir alle Änderungen sowohl im Code als auch im XAML mit 'TODO bzw. mit einem <!-- TODO --> gekennzeichnet.

    PS: Lass dich nicht entmutigen, ich weis das es am Anfang schwer ist. Aber es macht dann klick und schon spielt man sich damit als hätte man immer mit Binding gearbeitet und lernt es sehr schnell zu schätzen.
    Außerdem gibt es dieses Forum und sowohl ich als auch viele andere werden immer versuchen zu helfen.

    Edit: Sorry, im Zip Archiv waren nicht alle Ordner. Anhang ausgetauscht.

    Grüße
    Sascha
    Dateien
    • Adressbuch1.zip

      (157,01 kB, 6 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. ##

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

    Ich glaube ich habe es verstanden. Die Auflistung der Personen habe ich hinbekommen.

    Nun habe ich ja das PersonVm und das PersonListeVm, beide erben von ViewModelBase.
    Im ViewModelBase ist unter anderem der von Dir stammende Code für die IsInDesignMode Property.
    Nun ist es so, das mir die Property auch im DataGrid angezeigt wird, wenn ich ganz normal Binde.

    XML-Quellcode

    1. <DataGrid Margin="5" ItemsSource="{Binding Persons}">
    2. </DataGrid>

    Ist das hierbei normal das die Property mit angezeigt wird weil es von ViewModelBase erbt?

    ID,ErstelltAm und ErstelltVon ( aus ModelBase) werden ja auch nicht automatisch in PersonVm übernommen wenn ich dazu keine extra Propertys im PersonVm anlege.

    Das würde ja quasi heißen ich MUSS mir ein DataTemplate bauen um das zu umgehen. Habe ich zu Lernzwecken auch vor, aber mir geht es hier darum um es so sein MUSS.

    Hffentlich versteht man mich und ich habe nicht zu wirr geschrieben.

    Anbei noch das Projekt.

    Gruß Sven

    Edit:
    Habe Herausgefunden dass das DataGrid Automatisch für jede Öffentliche Eigenschaft eine Spalte generiert und auch den DatenTyp erkennt.
    Liegt daran das AutoGenerateColumns Standartmässig beim DataGrid auf True steht.
    Dateien
    • Adressbuch01.zip

      (44,91 kB, 2 mal heruntergeladen, zuletzt: )
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.

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

    Akanel schrieb:

    Ist das hierbei normal das die Property mit angezeigt wird weil es von ViewModelBase erbt?

    Ja, das ist soweit normal. Erbt eine Klasse von einer anderen erbt diese deren Eigenschaften. Eine Textbox hat das Tag Property weil sie von Control erbt und Control eben dieses Property besitzt.
    Aber du kannst es vor der WPF verbergen indem du das IsInDesignMode Property als Friend deklarierst. Das kann das View dieses nicht mehr sehen da die Projekte ja voneinander getrennt sind. Wieder ein kleiner Vorteil der Trennung.

    Akanel schrieb:

    ID,ErstelltAm und ErstelltVon ( aus ModelBase) werden ja auch nicht automatisch in PersonVm übernommen wenn ich dazu keine extra Propertys im PersonVm anlege.

    Achtung. nicht wieder Äpfel mit Birnen zusammenwürfeln. Warum soll die View Eigenschaften vom Model anzeigen. Wir haben dieses ja getrennt. Die View weis nichts vom Model weshalb es auch von diesen Eigenschaften nichts weis. Deshalb müssen wir uns ja ViewModel-Klassen erstellen.
    Schau dir mal folgendes Diagram an, hier habe ich mal dargestellt wie das Model vom ViewModel verdeckt wird. Die View kann davon nichts sehen solange kein Verweis hinzugefügt wird.



    Akanel schrieb:

    Das würde ja quasi heißen ich MUSS mir ein DataTemplate bauen um das zu umgehen.

    Warum? Bei einem DataGrid muss man hierfür keine Templates bauen, du musst Ihm nur sagen welches spalten du haben willst.

    XML-Quellcode

    1. <DataGrid Margin="5" ItemsSource="{Binding Persons}" AutoGenerateColumns="False">
    2. <DataGrid.Columns>
    3. <DataGridTextColumn Header="Vorname" Binding="{Binding Vorname}"/>
    4. <DataGridTextColumn Header="Nachname" Binding="{Binding Nachname}"/>
    5. <DataGridTextColumn Header="Wohnort" Binding="{Binding Ort}"/>
    6. </DataGrid.Columns>
    7. </DataGrid>


    Ich hoffe meine Erklärung war verständlich.
    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. ##

    @Amro
    Gut aufgepasst.
    Du hast recht, das muss nicht sein. Der Verweis auf das Model kann raus.

    Was drinnen bleiben muss ist der Verweis auf das ViewModel und die View. Die View wird benötigt da im MainWindow das UserControls verwendet wird und das ViewModel wegen dem DesignTime-Datenkontext im MainWindow.
    Nehme ich den DesignTime-Datenkontext auch noch raus könnte ich auch den Verweis auf das ViewModel rausnhemen.

    Achtung: Dann muss ich aber (z.b. über den PostBuild-Event) die dlls in das Ausgabeverzeichniss kopieren lassen, sonst gibt es sowohl die Model.dll als auch die ViewModel.dll im Ausgabeverzeichnis der exe nicht.
    Solange der Verweis auf das ViewModel vorhanden ist, wird die Model.dll mitkopiert da die ViewModel.dll diese benötigt. Da weis der Compiler das, nehme ich das ViewModel weg weis er auch nicht mehr das er die Model.dll benötigt.

    Hierfür dann im Postbuild sowohl beim Model als auch beim ViewModel folgendes eintragen:

    SQL-Abfrage

    1. if not exist "$(SolutionDir)$(SolutionName)\$(OutDir)" mkdir "$(SolutionDir)$(SolutionName)\$(OutDir)"
    2. copy "$(TargetPath)" "$(SolutionDir)$(SolutionName)\$(OutDir)$(TargetFileName)"


    Schöne 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. ##

    Moin @Nofear23m
    Nun Bin ich soweit das ich die Buttons einbinden möchte. Dazu habe ich einige fragen.

    Brauche ich ein HeaderVm oder können die Commands auch in das MainViewVm?
    Wie kann ich zb eine Person neu anlegen?

    Das ganze möchte ich über einen Dialog machen, den ich allerdings im View habe.
    Wie stelle ich es denn an im Command(welches im Viewmodel ist) auf den Dialog PersonNewEdit (welcher im View ist) zuzugreifen?
    Dateien
    • Adressbuch02.zip

      (46,85 kB, 3 mal heruntergeladen, zuletzt: )
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.

    Akanel schrieb:

    Brauche ich ein HeaderVm oder können die Commands auch in das MainViewVm?

    Ganz nach belieben. Ich persönlich teile es gerne auf, das bringt Ordnung. Dann habe ich alles dort wo es hingehört.
    Aber... Du hast ja eh schon ein HeaderVm und im MainViewVm hast du das Property HeaderVm also hast du es ja eh schon.

    Akanel schrieb:

    Wie kann ich zb eine Person neu anlegen?

    Einfach der Auflistung hinzufügen.

    Akanel schrieb:

    Das ganze möchte ich über einen Dialog machen, den ich allerdings im View habe.
    Wie stelle ich es denn an im Command(welches im Viewmodel ist) auf den Dialog PersonNewEdit (welcher im View ist) zuzugreifen?

    Naja, da kommten wir zum nächsten "Kapitel" von MVVM. Du brauchst ein "Service" welches dir das Regelt. Das ViewModel kennt die View nicht. Somit auch keine Controls, Dialoge oder anderes. Soll ja auch so sein.

    Gerne mache ich dir für alle drei Fragen ein Beispiel, dann siehst du das Anhand von "deinem" Fallbeispiel.
    Wie das mit dem Service funktioniert erkläre ich dann noch extra hier im Thread. Hier sollte man sich mit Interfaces beschäftigen. Ich mach das mal und melde mich wieder.

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

    Hallo

    Da du nun mit Dialogen und der gleichen anfängst muss erstmal ein Servicecontainer in form einer Signleton Klasse geschaffen werden.
    Wir erstellen uns also im ViewModel-Projekt eine Klasse ServiceContainer

    VB.NET-Quellcode

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


    Sieht jetzt kompilzierter aus als es ist. Dem ServiceContainer (Instance) können Services hinzugefügt werden welche dieser hält, dazu kommen wir aber erst später.

    Erstmal müssen wir uns überlegen was wir machen möchten. Im Projekt habe ich zwei Beispiele implementiert. Einen DialogWindowService und ein MessageBoxService
    hier besprchen wir mal das kompliziertere DialogWindowService.

    Erstmal benötigen wir einen Bauplan (Interface) was dieses Service können soll.
    Es soll einen Dialog öffnen und wieder schliessen können.
    Also erstellen wir ein Interfyce im ViewModel-Projekt:

    VB.NET-Quellcode

    1. Namespace Services
    2. Public Interface IDialogWindowService
    3. Sub ShowModalDialog(windowname As String, windowTitle As String, datacontext As Object, owner As Object)
    4. Sub CloseDialog()
    5. Sub CloseDialog(vm As Object)
    6. End Interface
    7. End Namespace


    Der Bauplan steht und wir wissen was dieses Service später "kann".

    Im App-Porjekt (also dem Projekt welches später ausgeführt wird), in deinem Fall "Adressbuch" erstellen wir nun ein Window welches später für ALLE Dialog dienen soll und packen folgendes in en XAML.

    XML-Quellcode

    1. <Window x:Class="ModalDialogWindow"
    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:Adressbuch"
    7. mc:Ignorable="d"
    8. Title="ModeldialogWindow" Height="450" Width="800">
    9. <ContentPresenter Content="{Binding}"/>
    10. </Window>


    Das wars, mehr benötigt das Window nicht im XAML nicht. Wir gehen in die CodeBehind und machen uns noch ein paar Hilfsmethoden für mehr "komfort". Wir möchten z.b. das man jeden Dialog mit ESC schliessen kann.

    VB.NET-Quellcode

    1. Public Class ModalDialogWindow
    2. Private Sub ModelDialogWindow_PreviewKeyDown(sender As Object, e As KeyEventArgs) Handles Me.PreviewKeyDown
    3. If e.Key = Key.Escape Then
    4. DialogResult = False
    5. Close()
    6. End If
    7. End Sub
    8. Public Function FindOwnerWindow(viewModel As Object) As Window
    9. ' if the ViewModel of the caller is set, we try to find the corresponding Window
    10. If viewModel IsNot Nothing Then
    11. If viewModel.GetType Is GetType(ModalDialogWindow) Then Return CType(viewModel, Window)
    12. If Application.Current.Windows.Cast(Of Window)().SingleOrDefault(Function(x) x.DataContext IsNot Nothing AndAlso x.DataContext.GetType Is viewModel.GetType) Is Nothing Then
    13. For Each window As Window In (From win In Application.Current.Windows()).ToList
    14. If window.DataContext IsNot Nothing AndAlso window.DataContext.GetType Is viewModel.GetType Then
    15. Return window
    16. End If
    17. Next
    18. Else
    19. Return Application.Current.Windows.Cast(Of Window)().SingleOrDefault(Function(x) x.DataContext IsNot Nothing AndAlso x.DataContext.GetType Is viewModel.GetType)
    20. End If
    21. End If
    22. ' by default we use the active Window in the List as owner
    23. Return Application.Current.Windows.Cast(Of Window)().SingleOrDefault(Function(x) x.IsActive)
    24. End Function
    25. End Class


    So, das wars dann auch wieder. Zu FindOwnerWindow komme ich dann noch.

    Nun legen wir uns die Klasse ModalDialogWindow an, diese wird unseren Bauplan nun quasi "bauen" indem es IDialogwindowService implementiert:

    VB.NET-Quellcode

    1. Imports ViewModel.Services
    2. Public Class ModalDialogService
    3. Implements IDialogWindowService
    4. Private _currentWindow As ModalDialogWindow
    5. Public Sub CloseDialog() Implements IDialogWindowService.CloseDialog
    6. If _currentWindow IsNot Nothing Then
    7. _currentWindow.Close()
    8. End If
    9. End Sub
    10. Public Sub CloseDialog(vm As Object) Implements IDialogWindowService.CloseDialog
    11. Dim owner As Window = Application.Current.Windows.Cast(Of Window)().SingleOrDefault(Function(x) x.DataContext IsNot Nothing AndAlso x.DataContext.GetType Is vm.GetType)
    12. If owner Is Nothing Then
    13. For Each window As Window In (From win In Application.Current.Windows()).ToList
    14. If window.DataContext.GetType Is vm.GetType Then window.Close()
    15. Next
    16. Else
    17. owner.Close()
    18. End If
    19. End Sub
    20. Public Sub ShowModalDialog(windowname As String, windowTitle As String, datacontext As Object, owner As Object) Implements IDialogWindowService.ShowModalDialog
    21. _currentWindow = New ModalDialogWindow() With {.Name = windowname, .DataContext = datacontext, .SizeToContent = SizeToContent.WidthAndHeight, .WindowStartupLocation = WindowStartupLocation.CenterScreen}
    22. _currentWindow.Title = windowTitle
    23. _currentWindow.Owner = _currentWindow.FindOwnerWindow(owner)
    24. _currentWindow.ShowDialog()
    25. End Sub
    26. End Class


    Wie du sehen kannst wir hier nun die Logik für den Bauplan implementiert. Also was passieren soll. Da wir hier in der App sind und diese ja die View kennt können wir dies tun.
    Hier kommt auch nun die FindOwnerWindow(owner) ins Spiel, diese Findet einfach das richtige Fenster anhand des Datencontexts (ViewModel).
    Jetzt müssen wir nur noch das soeben erstelte Service ModelDialogService in den am Anfang erstellten Container packen. Das machen wir am besten beim start der Applikation als aller erstes indem wir in der Application.vb den Code in Application_Startup packen:

    VB.NET-Quellcode

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


    Wir haben dem ServiceContainer also somit mitgeteilt das wir ein neues Service vom Typ IDialogWindowService mit der Logik von ModelDialogService hinzufügen möchten und obwohl das ViewModel die Klasse nicht kent klappt dies weil der Bauplan vorgibt was die Klasse kann.

    Willst du nun im ViewModel einen Dialog aufrufen schreibst du:

    VB.NET-Quellcode

    1. Dim dialogService = ServiceContainer.GetService(Of IDialogWindowService)
    2. dialogService.ShowModalDialog("editPerson", "Person bearbeiten...", currentPerson, Me)


    Möchtest du einen Dialog in welchem ein spezielles VM als Datengrundlage vorhanden ist schliessen gibtst du in CloseDialog das VM mit und schreibst:

    VB.NET-Quellcode

    1. Dim dialogService = Services.ServiceContainer.GetService(Of Services.IDialogWindowService)
    2. If dialogService IsNot Nothing Then dialogService.CloseDialog(Me)


    Ansonsten kannst du einfach immer den aktuellen Dialog schliessen mit: dialogService.CloseDialog()

    Schaut jetzt komplizierte aus als es ist, vorallem.... das machst du nur 1x. Ab dann musst du wirklich immer nur die zwei Zeilen schreiben.
    Jetzt kommt die "Magie". Woher weis die WPF welches/welche Controls in den Diaog müssen. Wie haben da ja nur den ContentPresenter drinnen und übergeben dem Window lediglich einen DatenKontext in form eines ViewModels mit. Hierfür gibt es DataTemplates. Ich habe einfach in der Applikation.xaml ein DataTemplate für den Typ ViewModel.Person.PersonVm definiert und gesagt das dieser Typ mit dem Control PersonNewEdit.xaml gerendert werden soll. Alles andere macht uns die WPF klar.

    Ansonsten habe ich dir ein paar Bindings korrigiert und ein bischen logik implementiert sowie ein SelectedPerson in PersonListVm damit bearbeiten von Personen möglich wird.

    Wenn Fragen auftauchen - ich denke das wird der Fall sein - dann Frag einfach. Grüße
    Sascha
    Dateien
    • Adressbuch02.zip

      (840,66 kB, 3 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. ##

    Nofear23m schrieb:

    Im App-Porjekt (also dem Projekt welches später ausgeführt wird), in deinem Fall "Adressbuch" erstellen wir nun ein Window welches später für ALLE Dialog dienen soll und packen folgendes in en XAML.

    XML-Quellcode

    1. <Window x:Class="ModalDialogWindow" [...]>
    2. <ContentPresenter Content="{Binding}"/>
    3. </Window>
    Ist das nicht etwas mühsam, wenn man dann für den ContentPresenter die richtigen DataTemplates zu designen?
    Weil für ein DataTemplate als solches bekommt man im Xaml-Designer ja keine Vorschau.

    ErfinderDesRades schrieb:

    Ist das nicht etwas mühsam, wenn man dann für den ContentPresenter die richtigen DataTemplates zu designen?


    XML-Quellcode

    1. <DataTemplate DataType="{x:Type vm_person:PersonVm}">
    2. <view:PersonNewEdit DataContext="{Binding}"/>
    3. </DataTemplate>

    Die angabe des UserControls ist mühsam? Musst nix "Designen"! Für jedes View gibts in der Regel ein Usercontrol mit Designtime-Support.
    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. ##

    Danke Dir @Nofear23m
    Ich werde mir das alles jetzt erstmal in Ruhe zu Gemüte führen und versuche erstmal alles zu verstehen.
    Bei fragen ( die ziemlich sicher kommen) werde ich mich natürlich wie gewohnt melden.
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.