Click here to Skip to main content
15,881,709 members
Articles / Desktop Programming / WPF

Navigation in MVVM applications (Onyx)

Rate me:
Please Sign up or sign in to vote.
4.33/5 (7 votes)
13 Nov 2009CPOL4 min read 69.8K   2.2K   28   2
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 developme

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.

C#
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

C#
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
FileAbsolute pack URI
Resource file - local assemblyUri uri = new Uri("pack://application:,,,/ResourceFile.xaml", UriKind.Absolute);
Resource file in subfolder - local assemblyUri uri = new Uri("pack://application:,,,/Subfolder/ResourceFile.xaml", UriKind.Absolute);
Resource file - referenced assemblyUri uri = new Uri("pack://application:,,,/ReferencedAssembly; component/ResourceFile.xaml", UriKind.Absolute);
Resource file in subfolder of referenced assemblyUri uri = new Uri("pack://application:,,,/ReferencedAssembly; component/Subfolder/ResourceFile.xaml", UriKind.Absolute);
Resource file in versioned referenced assemblyUri uri = new Uri("pack://application:,,,/ReferencedAssembly; v1.0.0.0; component/ResourceFile.xaml", UriKind.Absolute);
Content fileUri uri = new Uri("pack://application:,,,/ContentFile.xaml", UriKind.Absolute);
Content file in subfolderUri uri = new Uri("pack://application:,,,/Subfolder/ContentFile.xaml", UriKind.Absolute);
Site of origin fileUri uri = new Uri("pack://siteoforigin:,,,/ SOOFile.xaml", UriKind.Absolute);
Site of origin file in subfolderUri uri = new Uri("pack://siteoforigin:,,,/Subfolder/ SOOFile.xaml", UriKind.Absolute);
Table 2
FileRelative pack URI
Resource file - local assemblyUri uri = new Uri("/ResourceFile.xaml", UriKind.Relative);
Resource file in subfolder - local assemblyUri uri = new Uri("/Subfolder/ResourceFile.xaml", UriKind.Relative);
Resource file - referenced assemblyUri uri = new Uri("/ReferencedAssembly;component/ResourceFile.xaml", UriKind.Relative);
Resource file in subfolder - referenced assemblyUri uri = new Uri("/ReferencedAssembly;component/Subfolder/ResourceFile.xaml", UriKind.Relative);
Content fileUri uri = new Uri("/ContentFile.xaml", UriKind.Relative);
Content file in subfolderUri 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.

C#
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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Russian Federation Russian Federation
I have Master degree in Particle Physics. During my last several years I work as software developer.

Primary Interests
- c#, c++, php, java.
- scientific programming

Comments and Discussions

 
GeneralMissing Onyx referenced dll Pin
shgil30-Mar-11 3:41
shgil30-Mar-11 3:41 
GeneralRe: Missing Onyx referenced dll Pin
Sam Safonov30-Mar-11 4:39
Sam Safonov30-Mar-11 4:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.