The motivation for this post is to explore WPF view navigation, since my most recent work had a nice (custom) framework in place where I never had to do any of the navigation I thought it was time to revisit this subject as I found myself asking a basic question, “how do I switch from this View to that View?”.
Starting at the start, I used:
– Microsoft Windows 7 Enterprise
– Microsoft Visual Studio 2010 Ultimate (Service Pack 1)
– Microsoft Expression Blend 4
– The MVVM Light Toolkit
I created an MvvmLight (WPF4) project using the Visual Studio template (installed by the toolkit), I compiled and then ran it. Great, a simple MVVM app up and running, but no app of any complexity has a single View, so I next wanted to implement another view (erm, window?) then some navigation between them.
I added a new MvvmView (WPF) and a MvvmViewModel (WPF).
Here I noticed the views are in the root level, so I did some housekeeping, giving me the following structure:
Nav (my project name)
Nav\Design\DesignDataService.cs
Nav\Models\DataItem.cs
Nav\Models\DataService.cs
Nav\Models\IDataService.cs
Nav\Skins\MainSkin.xaml
Nav\ViewModels\AnotherViewModel.cs (mine)
Nav\ViewModels\MainViewModel.cs
Nav\ViewModels\ViewModelLocator.cs
Nav\Views\AnotherWindow.xaml + .cs (mine)
Nav\Views\MainWindow.xaml + .cs
Nav\App.xaml
The AnotherViewModel was not yet supported by the Locator, so I added the following to ViewModelLocator.cs using the mvvmlocatorproperty snippet:
/// <summary>
/// Gets the Another property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public AnotherViewModel Another
{
get
{
return ServiceLocator.Current.GetInstance<AnotherViewModel>();
}
}
And this bit into the static constructor:
SimpleIoc.Default.Register<AnotherViewModel>();
Next I wired up the Binding in the AnotherWindow.xaml to use AnotherViewModel:
DataContext="{Binding Another, Source={StaticResource Locator}}"
So the view I created is now using the correct ViewModel, which is always good. And I wanted the same skin as MainWindow, so I also added the following:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
The last few steps will be required each time a new View and ViewModel is added.
Next (more to illustrate how this thing holds together than anything else) I re-wired the App.xaml to start AnotherWindow at first:
StartupUri="Views/AnotherWindow.xaml"
This is my way of showing MainWindow is nothing special. Or is it? Well it does have the extra code in MainWindow.xaml.cs:
Closing += (s, e) => ViewModelLocator.Cleanup();
I don’t need this, nor do I care about cleanup in this case as the ViewModelLocator will exist till the bitter end, so I commented it out.
I then ran it up, a nice blank window this time, as planned.
I decided I wanted my App to handle the window switching, so I wrote a little method to do this for me (in App.xaml.cs):
private int selectedWindow = 0;
internal void SwitchWindow()
{
Window cw = Windows[0];
Window nw;
if (selectedWindow == 0)
{
nw = new MainWindow();
selectedWindow = 1;
}
else
{
nw = new AnotherWindow();
selectedWindow = 0;
}
nw.Show();
cw.Close();
}
You can bind a ViewModel command to a button easily enough, so I added a RelayCommand to AnotherViewModel that called the SwitchWindow:
/// <summary>
/// Initializes a new instance of the AnotherViewModel class.
/// </summary>
public AnotherViewModel()
{
Switch = new RelayCommand(() => { ((App)App.Current).SwitchWindow(); });
}
public RelayCommand Switch { get; set; }
Sure I’m casting App.Current to App here, I could have created SwitchWindow as a static member of App, this would remove the need for that, but I didn’t.
I rebuilt the solution then (in blend) I added a button to AnotherWindow.xaml and drag and dropped the Switch RelayCommand onto the button, this added the following xaml:
<Button Content="Button" HorizontalAlignment="Left" Margin="272,0,0,190.04" VerticalAlignment="Bottom" Width="75" Command="{Binding Switch}"/>
I rebuilt, ran and tested I could switch one way, then I needed to add the same code to MainViewModel and button on MainWindow.xaml. On this occasion copy and paste was fine to test it and it worked going both ways as expected, the Switch code could of course be moved into a new ViewModel base class in future.
So what have I achieved here, not a lot, but in sticking the switch code into the App class I have removed any inter ViewModel dependency I would otherwise introduce, I guess I am looking at App as being my navigation controller at the moment, but this logic could of course be moved elsewhere.
Next up is to revisit view switching (within the same window), from my ASP.NET background this to me is swapping user controls. I hope to cover this in a follow up blog post.