Monday, August 06, 2012

Shell Based Navigation in Cocoon

I recently had a query in the Cocoon CodePlex forums regarding how to support an application shell when using the Cocoon framework’s navigation support. By default the Cocoon navigation framework will display pages full screen, with each navigation replacing the previous page with the next. There are some occasions however where it makes sense to have an application shell that takes up the full screen, with the page navigation occurring in a region within this.

A typical example would consist of a fixed region dedicated to navigation at the top of the screen, with the page content filling below. The end result looks like,

image

Creating an Application Shell in Cocoon

The key to creating an application shell in Cocoon is the INavigationTarget interface. This has only a single method named NavigateTo(…). When implemented by an application, any calls to the navigation framework will result in a call to this method with the page to display. The framework itself will handle the creation and wiring up of views and view-models, the navigation stack, persistence and other aspects of navigation.
In our example application we will use the MVVM pattern to define our application shell, hence we have a ShellViewModel,

   1: [Export(typeof(INavigationTarget))]
   2: [Shared]
   3: public class ShellViewModel : NotifyPropertyChangedBase, INavigationTarget
   4: {
   5:     // *** Fields ***
   6:  
   7:     private object content;
   8:     private ShellPage shellPage;
   9:  
  10:     // *** Properties ***
  11:  
  12:     public object Content
  13:     {
  14:         get
  15:         {
  16:             return content;
  17:         }
  18:         set
  19:         {
  20:             if (content != value)
  21:             {
  22:                 content = value;
  23:                 OnPropertyChanged();
  24:             }
  25:         }
  26:     }
  27:  
  28:     // *** INavigationTarget Methods ***
  29:  
  30:     public void NavigateTo(object page)
  31:     {
  32:         // If this is the first navigation then create the shell view and bind to this view model
  33:  
  34:         if (shellPage == null)
  35:         {
  36:             shellPage = new ShellPage();
  37:             shellPage.DataContext = this;
  38:         }
  39:  
  40:         // Set the content for the shell to the specified page
  41:  
  42:         this.Content = page;
  43:  
  44:         // Set the shell view as the window content
  45:  
  46:         Window.Current.Content = shellPage;
  47:     }
  48: }

The ShellViewModel exposes a single property named ‘Content’ that will contain the page to display and will be bound to in the view. In our NavigateTo(…) method we firstly determine if we have created the associated view and create this is necessary. We then set the ‘Content’ property to the supplied page and this ensure that the view is displayed in the window. Finally we mark the class as a shared export of INavigationTarget using the MEF attributes. Note that since we will never be navigating explicitly to the shell then we do not need to decorate this with a ViewModelExport attribute.

In the sample code the shell view model also exposes a ‘GoBackCommand’ that allows you to include a back navigation button within the shell region.

The ShellPage.xaml file contains the view for the application shell. This simply contains the required elements for the upper portion of the screen, with a ContentControl bound to the view models ‘Content’ property. It is within this ContentControl that the pages will be displayed. The key elements are shown below,


   1: <Grid Style="{StaticResource LayoutRootStyle}">
   2:     ...
   3:  
   4:     <!-- Back button and page title -->
   5:     <Grid>
   6:         <Grid.ColumnDefinitions>
   7:             <ColumnDefinition Width="Auto"/>
   8:             <ColumnDefinition Width="*"/>
   9:         </Grid.ColumnDefinitions>
  10:         <Button x:Name="backButton" .../>
  11:         <TextBlock x:Name="pageTitle" .../>
  12:     </Grid>
  13:     <ContentControl Content="{Binding Content}" .../>
  14: </Grid>

When the application is run the NavigationManager will automatically locate the INavigationTarget through the MEF export and direct all navigation through this.

Summary

I have shown above how you can implement an application shell using the Cocoon framework. The sample application with full source code is available from the Cocoon CodePlex downloads.

1 comment:

Anonymous said...

Thanks Andy,

Great framework, great work