Tbooty
Tbooty

Reputation: 289

Navigation with Frame and Combobox MVVM

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

Answers (2)

Liero
Liero

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

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

Related Questions