Contents
There are many articles discussing MVVM. One of the MVVM approaches was suggested/introduced by Josh Smith and Karl Shifflett. In my approach, the navigation from a view to another view can be performed by means of DataTemplates, which “tell” how to display a specified type. The Cinch framework, written by Sacha Barber, utilizes this approach. Another approach, in my opinion, could be based on the native (.NET) NavigationService
. I started thinking about navigation for MVVM after reading the "WPF: FlipTile 3D" article which talks about the Onyx framework. In the "Future Onyx Work" section, Sacha Barber writes: "I mentioned to Bill that I thought there was something missing which was the ability to open a new actual WPF window from a ViewModel and the whole issue of navigation…". In this article, I want to present an approach of navigation in MVVM applications, and perhaps make a small contribution to Onyx development.
XAML is the .NET language invented to define object graphs. XAML is like C# or Visual Basic, perhaps not so flexible, but doing a good job of using XML to describe an object graph. And, BAML is like IL. BAML is the markup compiled XAML in the WPF scenario.
A XAML file (typically) works in conjunction with a related primary code file, specified via the x:Class
attribute. A XAML file is processed to yield a *.g.cs file, and a BAML file. The primary code file and the *.g.cs (g - generated) file merge into a single unified class. The BAML is embedded as an assembly resource. You can find these files in the obj/Debug folder after the first compilation. You can also see these files if you reflect your assembly in Reflector.
Let’s consider two scenarios in our application:
- The
StartupUri
property equals to Page.xaml or UserControl.xaml; i.e., the root element is the System.Windows.Controls.Page
or System.Windows.Controls.UserControl
type.
- The
StartupUri
property equals to Window1.xaml; i.e., the root element is the System.Windows.Window
type.
What is the difference of the WPF application starting process in these two cases? In the first case, the NavigationService
instance is created. Consider the method System.Windows.Application::GetAppWindow()
. For standalone cases, this method creates and returns a NavigationWindow
. It should be noted that during NavigationWindow
initialization, a NavigationService
will be created.
public NavigationWindow()
{
this.Initialize();
}
private void Initialize()
{
Debug.Assert(_navigationService == null && _JNS == null);
_navigationService = new NavigationService(this);
_navigationService.BPReady += new BPReadyEventHandler(OnBPReady);
_JNS = new JournalNavigationScope(this);
_fFramelet = false;
}
This feature of NavigationWindow
is especially useful since the constructor of NavigationService
is internal
and we can’t create it directly.
In the second case, NavigationService
does not get created, and we do not have the opportunity to navigate.
Let’s see how the content of a BAML resource is displayed. For this purpose, the System.Windows.Application::LoadComponent(Uri uri)
method is utilized, which returns the object graph equivalent of the XAML content.
internal void DoStartup()
{
…
object root = LoadComponent(StartupUri, false);
ConfigAppWindowAndRootElement(root, StartupUri);
…
}
In order to access BAML resources, we have to use Unified Resource Identifiers (URIs). A URI can be used to load files from a variety of locations, including the following:
- The current assembly
- A referenced assembly
- A location relative to an assembly
- The application’s site of origin
Table 1
File |
Absolute pack URI |
Resource file - local assembly |
Uri uri = new Uri("pack://application:,,,/ResourceFile.xaml", UriKind.Absolute); |
Resource file in subfolder - local assembly |
Uri uri = new Uri("pack://application:,,,/Subfolder/ResourceFile.xaml", UriKind.Absolute); |
Resource file - referenced assembly |
Uri uri = new Uri("pack://application:,,,/ReferencedAssembly; component/ResourceFile.xaml", UriKind.Absolute); |
Resource file in subfolder of referenced assembly |
Uri uri = new Uri("pack://application:,,,/ReferencedAssembly; component/Subfolder/ResourceFile.xaml", UriKind.Absolute); |
Resource file in versioned referenced assembly |
Uri uri = new Uri("pack://application:,,,/ReferencedAssembly; v1.0.0.0; component/ResourceFile.xaml", UriKind.Absolute); |
Content file |
Uri uri = new Uri("pack://application:,,,/ContentFile.xaml", UriKind.Absolute); |
Content file in subfolder |
Uri uri = new Uri("pack://application:,,,/Subfolder/ContentFile.xaml", UriKind.Absolute); |
Site of origin file |
Uri uri = new Uri("pack://siteoforigin:,,,/ SOOFile.xaml", UriKind.Absolute); |
Site of origin file in subfolder |
Uri uri = new Uri("pack://siteoforigin:,,,/Subfolder/ SOOFile.xaml", UriKind.Absolute); |
Table 2
File |
Relative pack URI |
Resource file - local assembly |
Uri uri = new Uri("/ResourceFile.xaml", UriKind.Relative); |
Resource file in subfolder - local assembly |
Uri uri = new Uri("/Subfolder/ResourceFile.xaml", UriKind.Relative); |
Resource file - referenced assembly |
Uri uri = new Uri("/ReferencedAssembly;component/ResourceFile.xaml", UriKind.Relative); |
Resource file in subfolder - referenced assembly |
Uri uri = new Uri("/ReferencedAssembly;component/Subfolder/ResourceFile.xaml", UriKind.Relative); |
Content file |
Uri uri = new Uri("/ContentFile.xaml", UriKind.Relative); |
Content file in subfolder |
Uri uri = new Uri("/Subfolder/ContentFile.xaml", UriKind.Relative); |
In the demo application, I utilize absolute URI paths since I need to navigate from both local and remote assemblies.
private void NavCommandExecute(object sender, ExecutedRoutedEventArgs args)
{
NavigationService service =
NavigationService.GetNavigationService(this.View.ViewElement);
try
{
Uri uri = new Uri("pack://application:,,,/NavigationDemo;component/" +
args.Parameter.ToString(), UriKind.Absolute);
service.Navigate(uri);
}
catch (Exception ex)
{
}
}
Let’s consider how to perform the test of navigation for a WPF application. Once we create an instance of NavigationWindow
, we obtain the functionality of NavigationService
. Therefore, we are able to navigate to the resources which could be located both in the local and remote assemblies.
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Assembly.LoadFile(path + @"\NavigationDemo.exe");
Application.Run(new TestForm());
}
public TestForm()
{
InitializeComponent();
nw = new NavigationWindow();
try
{
uri = new Uri("pack://application:,,,/NavigationDemo;component/Page1.xaml",
UriKind.Absolute);
nw.Navigate(uri);
nw.Show();
}
catch (Exception ex) {}
}
In this article, an approach for navigation in MVVM oriented applications is presented. Combined with navigation through DataTemplates, navigation through NavigationService
provides a richer toolkit for WPF developers. I think it would be useful to utilize NavigationService
, since the development of WPF applications then becomes similar to web development.
- 8/09/2009: Initial release.
- 12/11/2009:
NavigationWindow
control extended by Custom Navigator.