Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Navigation in MVVM applications (Onyx)

0.00/5 (No votes)
13 Nov 2009 1  
Navigation in MVVM applications. 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.

mvvm.png

Contents

Introduction

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 and BAML

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.

URI

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.

baml_to_objects.png

internal void DoStartup() 
{ 
   …
   object root = LoadComponent(StartupUri, false);
   // If the root element is not a window, we need to create a window. 
   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

uri1.png

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
    {
        // We use absolute Uri path, since it should be valid
        // in both cases when run from local or remote assemblies
        Uri uri = new Uri("pack://application:,,,/NavigationDemo;component/" + 
                          args.Parameter.ToString(), UriKind.Absolute);
        service.Navigate(uri);
    }
    catch (Exception ex)
    {
    }
}

Test application

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);

    //Load remote assembly
    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) {}
}

Conclusion

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.

History

  • 8/09/2009: Initial release.
  • 12/11/2009: NavigationWindow control extended by Custom Navigator.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here