Reputation: 289
I'm trying to get my head around MVVM, i'm currently stuck on how to handle navigation.
Currently I have a page and within that page is a frame, that frame is responsible for framing in various other pages. Navigation was previously handled with a drop down box and on selection changed it would navigate that way.
I'm not sure how I could do this without touching the frame from the model view which would end up breaking mvvm.
In the end what I am trying to accomplish is, clicking on the combobox, selecting an item and then having then frame below navigate to the correct view.
I'm not using Prism or any other framework with MVVM, just trying to do it all manually.
Upvotes: 1
Views: 985
Reputation: 27360
@EdPlunkett: As an alternative to DataTemplate for each view, you can bind your frame to selected page viewmodel using ViewModelToViewConverter like I did here: https://stackoverflow.com/a/31721236/475727
Implicit DataTemplates and DataTemplateSelectors are unique to WPF and XAML, so people think it's recommended solution, but I think it's not suitable for navigation. It feels hackish and it smells with violation of DRY principle.
Upvotes: 0
Reputation: 37066
The ComboBox
would display an ObservableCollection
of frame items exposed by your main viewmodel, and the viewmodel will have another property for the selected item.
Your main viewmodel and the frame item viewmodels all inherit from a ViewModelBase
class which implements INotifyPropertyChanged
, and maybe some other stuff.
So, C#:
public ObservableCollection<ViewModelBase> FrameItems { get; protected set; }
private ViewModelBase _selectedFrameItem;
public ViewModelBase SelectedFrameItem {
get { return _selectedFrameItem; }
set {
value = _selectedFrameItem;
// Defined in ViewModelBase
OnPropertyChanged();
}
}
Your main viewmodel will populate FrameItems
in its constructor:
public MainViewModel()
{
FrameItems = new ObservableCollection<ViewModelbase> {
new IceCreamMenu(),
new SmurfOptions(),
new MagicSparklePonyFourierTransformConfiguration()
};
}
Every frame item is a subclass of ViewModelBase
. It exposes properties with notifications, including ObservableCollections
of any set of child things it may have. And we'll display it by writing a datatemplate for it in just a bit.
Let's assume that you've given your ViewModelBase
class a String Title { get; set; }
property. Or maybe you'll want to write a subclass of ViewModelBase
that introduces Title
; your call. For now let's put it in ViewModelBase
for simplicity.
XAML -- this leaves out all the layout, but you don't need that here.
<ComboBox
ItemsSource="{Binding FrameItems}"
SelectedItem="{Binding SelectedFrameItem}"
DisplayMemberPath="Title"
/>
<Frame Content={Binding SelectedFrameItem}" />
OK, but how on earth does it know what to do with SelectedFrameItem
?!
Easy! Write a resource dictionary called, say, ViewModelDataTemplates.xaml
, and merge it into App.xaml
so its contents are "visible" in any XAML in your application.
App.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Source is a relative path from project root directory -->
<ResourceDictionary Source="ViewModelDataTemplates.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
...plus whatever theme stuff or whatever.
In ViewModelDataTemplates.xaml
, define data templates for your frame item classes.
Say you've got an IceCreamMenu
viewmodel, with a collection of Flavors
public ObservableCollection<IceCreamFlavor> Flavors { get; protected set; }
...and a SelectedFlavor
. You'd define the namespace vm
appropriately with an xmlns:vm
attribute on the resource dictionary.
ViewModelDataTemplates.xaml
<DataTemplate DataType="{x:Type vm:IceCreamMenu}">
<Grid>
<ListBox
ItemsSource="{Binding Flavors}"
SelectedItem="{Binding SelectedFlavor}"
/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:IceCreamFlavor}">
<StackPanel Orientation="Horizontal">
<Border
Height="20"
Width="20"
Margin="4"
Background={Binding Color, Converter={StaticResource ColorToBrushConverter}}"
/>
<Label Content="Name" />
</StackPanel>
</DataTemplate>
If you've got existing UserControls
that you want to use via datatemplates, that's easy: Say you've got a NotesTabView
UserControl
that's a view for your NotesTabViewModel
, you could define a DataTemplate like this:
<DataTemplate DataType="{x:Type vm:NotesTabViewModel}">
<vw:NotesTabView />
</DataTemplate>
Upvotes: 2