Anischten mit Ribbon Control umschalten

  • WPF

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

    Anischten mit Ribbon Control umschalten

    Hallo zusammen,

    ich könnte mal wieder Hilfe bei der (sinnvollen) Implementierung von MVVM brauchen. Grundsätzlich bin ich mit der Thematik halbwegs vertraut, aber ab und zu hänge ich dann doch mal. Jetzt gerade möchte ich zum Beispiel eine Desktopanwendung entwickeln, die ein RibbonControl mit zunächst zwei Tabs enthält. Ich möchte, dass wenn ich die Tabs wechsle auch die View unterhalb des RibbonControls wechselt. Dazu bin ich bislang so vorgegangen:

    Zunächst Views und ViewModels erstellt: Es gibt drei Views mit jeweils einem ViewModel. MainView und MainViewModel, TimeTableView und TimeTableViewModel, AnalysisView und AnalysisViewModel. Die MainView beschreibt das Hauptfenster und enthält das RibbonControl und einen ContentPresenter zum Anzeigen der anderen beiden Views:

    XML-Quellcode

    1. <Window x:Class="MainWindow"
    2. xmlns...>
    3. <Window.Resources>
    4. <DataTemplate DataType="{x:Type local:TimeTableViewModel}">
    5. <local:TimeTableView/>
    6. </DataTemplate>
    7. <DataTemplate DataType="{x:Type local:AnalysisViewModel}">
    8. <local:AnalysisView/>
    9. </DataTemplate>
    10. </Window.Resources>
    11. <Window.DataContext>
    12. <local:MainViewModel x:Name="vmMainWindow" />
    13. </Window.DataContext>
    14. <Grid>
    15. <Grid.RowDefinitions >
    16. ...
    17. </Grid.RowDefinitions>
    18. <Ribbon Grid.Row="0">
    19. <Ribbon.ApplicationMenu>
    20. <RibbonApplicationMenu LargeImageSource=...>
    21. <RibbonApplicationMenuItem Header="Options" ImageSource=.../>
    22. <RibbonApplicationMenuItem Header="Exit" ImageSource=.../>
    23. </RibbonApplicationMenu>
    24. </Ribbon.ApplicationMenu>
    25. <RibbonTab Header="Time Table" IsSelected="{Binding TimeTableTabActive}">
    26. <RibbonGroup Header="Live">
    27. <RibbonMenuButton Label="Start" LargeImageSource=.../>
    28. <RibbonButton Label="Source" SmallImageSource=.../>
    29. <RibbonButton Label="Refresh" SmallImageSource=... />
    30. </RibbonGroup>
    31. <RibbonGroup Header="Post session" Foreground="Black">
    32. <RibbonButton Label="CSV" LargeImageSource=.../>
    33. <RibbonButton Label="Clear" SmallImageSource=.../>
    34. </RibbonGroup>
    35. <RibbonGroup Header="Database" Foreground="Black">
    36. <RibbonButton Label="Connect" LargeImageSource=.../>
    37. <RibbonButton Label="Disconnect" SmallImageSource=.../>
    38. </RibbonGroup>
    39. </RibbonTab>
    40. <RibbonTab Header="Analysis" IsSelected="{Binding AnalysisTabActive}">
    41. <RibbonGroup Header="Plot">
    42. ...
    43. </RibbonGroup>
    44. </RibbonTab>
    45. </Ribbon>
    46. <ContentControl Grid.Row="1" Content="{Binding ActiveViewModel, Mode=OneWay}">
    47. </ContentControl>
    48. </Grid>
    49. </Window>


    Das dazugehörige ViewModel "MainViewModel":

    VB.NET-Quellcode

    1. Public Class MainViewModel : Inherits BindableBase
    2. Dim _TimeTableTabActive As Boolean
    3. Dim _AnalysisTabActive As Boolean
    4. Dim _TimeTable AsTimeTableViewModel
    5. Dim _Analysis As AnalysisViewModel
    6. Dim _activeViewModel As Object
    7. Public Property ActiveViewModel As Object
    8. Get
    9. Return _activeViewModel
    10. End Get
    11. Set(value As Object)
    12. _activeViewModel = value
    13. NotifyPropertyChanged()
    14. End Set
    15. End Property
    16. Public Property TimeTableTabActive As Boolean
    17. Get
    18. Return _TimeTableTabActive
    19. End Get
    20. Set(value As Boolean)
    21. _TimeTableTabActive = value
    22. If _TimeTableTabActive Then
    23. Me.ActiveViewModel = TimeTable
    24. End If
    25. NotifyPropertyChanged()
    26. End Set
    27. End Property
    28. Public Property AnalysisTabActive As Boolean
    29. Get
    30. Return _AnalysisTabActive
    31. End Get
    32. Set(value As Boolean)
    33. _AnalysisTabActive = value
    34. If AnalysisTabActive Then
    35. Me.ActiveViewModel = Analysis
    36. End If
    37. NotifyPropertyChanged()
    38. End Set
    39. End Property
    40. Public Property TimeTable As TimeTableViewModel
    41. Get
    42. Return _TimeTable
    43. End Get
    44. Set(value As TimeTableViewModel)
    45. _TimeTable = value
    46. NotifyPropertyChanged()
    47. End Set
    48. End Property
    49. Public Property Analysis As AnalysisViewModel
    50. Get
    51. Return _Analysis
    52. End Get
    53. Set(value As AnalysisViewModel)
    54. _Strategy = value
    55. NotifyPropertyChanged()
    56. End Set
    57. End Property
    58. Public Sub New()
    59. ...
    60. TimeTable = New TimeTableViewModel()
    61. Analysis = New AnalysisViewModel()
    62. TimeTabaleTabActive = False
    63. AnalysisTabActive = True
    64. Me.ActiveViewModel = Analysis
    65. End Sub
    66. End Class


    Die einzelnen Views "TimeTableView.xaml" und "AnalysisView.xaml" sind jeweils UserControls. In beiden Fällen setze ich deren DataContext im Xaml auf das jeweilige ViewModel, z.B für die AnalysisView:

    XML-Quellcode

    1. <UserControl.DataContext>
    2. <local:AnalysisViewModel x:Name="vmAnalysis" />
    3. </UserControl.DataContext>


    Die Idee dahinter ist dass ich die IsSelected-Eigenschaft der einzelnen Tabs an ein eigenes Property im MainViewModel binde. Je nachdem wird dann die im Konstruktor erzeugte ViewModel-Instanz des passenden ViewModels an ActiveViewModel übergeben, die wiederum an den Content des ContentPresenter gebunden ist. Durch die DataTemplates in den Ressources des MainWindow weiß dann der ContentPresenter anhand des ViewModel Types wie ActiveViewModel dargestellt werden soll. (So funktioniert es in meinem Kopf)

    Das Wechseln der Views beim Wechseln des Tabs funktioniert auch soweit. Allerdings stört mich, dass bei jedem Wechsel wieder eine neue Instanz des jeweiligen ViewModels erzeugt wird (Sub New() des ViewModels wird aufgerufen). Daher dachte ich ich instanziiere ich jeweils eine Instanz im Konstruktor der MeinViewModel-Klasse und übergebe immer diese Instanzen an AvtiveViewModel. Das löst das Problem allerdings nicht, vermutlich weil im DataContext der einzelnen Views nochmal auf das ViewModel verwiesen wird. Gibt es dafür eine Lösung oder einen besseren Weg das Ganze umzusetzen?

    Viele Grüße
    Andy
    Hallo

    Soweit so gut, du hast wenn ich das richtig überflogen habe eigendlich ganz gut Strukturiert.
    Wenn ich das richtig verstehe funzt soweit alles so wie du es willst nur das immer beim wechseln eine neue Instanz der Klassen TimeTableViewModel und AnalysisViewModel erstellt werden.

    Habe ich das richtig rausgelesen?
    Vermutlich hast du in den Views TimeTableview und AnalysisView den DataContext wie im Window gesetzt. (Annahme von meiner Seite)

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type local:AnalysisViewModel}">
    2. <local:AnalysisView />
    3. </DataTemplate>

    Da du hier kein Binding für den DataContext übergibst.
    Besser wäre hier ein Binding auf das Property in der MainViewModel-Klasse

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type local:AnalysisViewModel}">
    2. <local:AnalysisView DataContext={Binding TimeTable}/>
    3. </DataTemplate>

    Evtl. musst du mit z.b. FindAncestor deinen Context suchen.
    Du musst nur aus dem UserControl AnalysisView den DataContext rausnehmen, sonst macht er ja eine neue Instanz von dem Datacontext über den Default-Konstruktor.
    Das funzt bei dem MainWindow da dies ohnehin nur einmahl geöffnet wird, in Usercontrols ist sowas eher kontraproduktiv.

    Ich hoffe ich konnte das einigermaßen rüber bringen. Wenn nicht kannst du ja ein Beispielprojekt hochladen und ich schau mir das dann mal an.

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

    Hi Sascha,

    vielen Danke für deine Hilfe. Jetzt ist es tatsächsich so, dass keine neuen Instanzen der ViewModels mehr erstellt werden. Allerdings scheint jetzt die Datenbindung der einzelnen Controls nicht mehr zu funktionieren, sprich ich die Properties der ViewModels weisen Werte im Debugger auf aber diese Daten sind jetzt nicht mehr sichtbar in der GUI. Ich habe den DataContext aus den beiden xaml-files entfernt und per Datenbindung in der MainWindow.xaml and Objekte des MainViewModels gesetzt. Die werden auch im Konstruktor des MainViewModels initialisiert. Hier beispielhaft mal die TimeTable.xaml

    XML-Quellcode

    1. <UserControl x:Class="TimeTable"
    2. xmlns=...>
    3. <Grid>
    4. <Grid.RowDefinitions>
    5. <RowDefinition Height="Auto" />
    6. <RowDefinition Height="*" />
    7. </Grid.RowDefinitions>
    8. <StackPanel Orientation="Horizontal">
    9. <TextBlock Text="Select:" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0"/>
    10. <ComboBox Margin="10,5" Width="400" ItemsSource="{Binding TimeTableCollection}" SelectedItem="{Binding Selection}" VerticalAlignment="Center" x:Name="CB_Selection">
    11. <ComboBox.ItemTemplate>
    12. <DataTemplate>
    13. <StackPanel Orientation="Horizontal">
    14. <TextBlock Text="{Binding Number}" Margin="0,0,0,0"/>
    15. <TextBlock Text="|" Margin="10,0,0,0"/>
    16. <TextBlock Text="{Binding FirstName}" Margin="10,0,0,0"/>
    17. <TextBlock Text="{Binding LastName}" Margin="3,0,0,0"/>
    18. </StackPanel>
    19. </DataTemplate>
    20. </ComboBox.ItemTemplate>
    21. </ComboBox>
    22. </StackPanel>
    23. <DataGrid Grid.Row="1" ItemsSource="{Binding ElementName=CB_Selection, Path=SelectedItem.LstLaps, IsAsync=True}" AutoGenerateColumns="True" IsReadOnly="True"
    24. EnableColumnVirtualization="True" EnableRowVirtualization="True"/>
    25. </Grid>
    26. </UserControl>


    Die Bindings hier bestehen alle zu Properties der Klasse TimeTableViewModel bzw. dann des Objektes TimeTable aus der MainViewModel Instanz.

    Viele Grüße
    Andy
    ICh kann es hier nicht nachvollziehen, sehe auch z.b. nich den Code den Fensters und sohin das Binding an das UserControl selbst.

    Stelle doch mal das Projekt hier ein (ohne Bin und wenn vorhanden ohne Packages-Ordner)

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

    So, im Anhang sind jetzt die betroffenen Klassen. Nicht wundern, ich hatte in meinem Beispiel oben zunächst die Namen der Klassen verändert. Auf Nachfrage hin darf ich aber auch die originalen Namen verwenden und kann deshalb auch die "richtigen" Klassen hochladen. Leider kann ich nicht das gesamte Projekt hochladen, was bezüglich Testen mit Daten aber ohnehin nicht funktionieren würde, da diese aus einer Datenbank stammen. Hoffe das passt soweit.
    Dateien
    Sooo, ich werde mir das mal in ruhe ansehen, mal sehen was du genau meinst.
    Hoffe das ich mit den einzelnen Files was Anfange.

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

    Ich habe das Probelm gelöst. Allerdings verstehe ich nicht ganz wie... Die Lösung war einfach nirgendwo einen DataContext zu setzen, weder in der MainWindow.xaml unter den DataTemplates, noch in den UserControls. Scheinbar wird das passende ViewModel über das Property ActiveViewModel des MainViewModels gesteuert. Der ContentPresenter der MainView ist ja an dieses Property gebunden und über das DataTemplate scheint dann View mit diesem ViewModel verknüpft zu werden ohne die DataContext Eigenschaft explizit zu setzen.
    Auf jeden Fall nochmals Danke für deine Hilfe!

    Schöne Grüße
    Andy
    Hallo

    Ich lade dir trotzdem ein "sauberes" Beispiel hoch wie es MVVM konform ist. Ich arbeite nicht mit Prism sondern mit rein eigenem Code, ich in da eigen, ich will die Kontrolle nicht abgeben.
    Du wirst sehen, dein ganzes vorhaben (habe mir deinen code ja angesehen) geht nocht viel sauberer und einfacher.

    So ist es auch jederzeit Skalierbar und viel besser zu steuern. Ausserdem bist du mit meinem Beispiel Typ-Save da ich für ein RibbonTab ein Interface erstellt habe.
    Jedes VM welches ein Ribbon-Tab sein können soll muss dieses Interface implementieren, NUR so ist sichergestelt das du immer ein Objekt übergibst welches auch die benötigten Properties besitzt UND du somit nie Bindingfehler haben kannst.
    Sauber und knapp.

    Ich empfehle dir, es dir trotzdem 5 Minuten anzusehen.

    Grüße
    Sascha
    Dateien
    • RibbonTest.zip

      (63,08 kB, 207 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. ##

    Hi Sascha,
    danke nochmals für deine Bemühungen. Ich habe mir dein Beispiel angescheut und muss sagen, dass es endlich mal ein übsersichtliches und verständliches Beispiel ist. Werde mein Projekt nun dementsprechend anpassen, weil ich die Struktur sehr gut finde.

    Viele Grüße
    Andy
    Hallo

    Ja, MVVM ist ja sehr aufgeräumt, das gefällt mir auch daran.
    Man behällt leicht den Überblickt, auch wenn es mal etwas mehr wird, und findet auch nach Monaten/Jahren auf anhieb die richtige Codestelle.

    Ich hoffe dir ist nun klarer wie man sowas löst. Schau dir (falls du damit noch nicht so vertraut bist) Interfaces an und was dir diese bringen.

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