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

Navigating the different modules through a TreeView in Prism.

0.00/5 (No votes)
31 May 2012 1  
Describes using TreeView navigation in the standard PRISM.

Motivation

When we want to use the patterns and practices contained in the concept of a composite PRISM we can exploit one of the assumptions shown in the examples. Unfortunately, there is no example of a hierarchical navigation system contained in the TreeView.

In this article I want to show how I solved the problem of navigation regardless of the number of modules added to the application and regardless what the hierarchy of the module is and what is contained in the modules. Zoom way in (Shell works with modules) and navigate the infrastructure by using TreeView.

Requirements

  • For a good understanding of the need to:
  • Know C #.
  • Know what it is PRISM and be able to implement it.
  • Visual C # 2010 Express (minimum) and SharpDevelop 4.1 (I used Visual Studio 2010).

How it Works

The base project I used:


Templates from http://dphill.members.winisp.net/Templates.html thanks David Hill.


Rebuild the file "ModuleCatalog.xml" from the root directory of the project „NavShelll”

//
<prism:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism"> 
    <prism:ModuleInfo Ref="NavModule_One.dll"
                      ModuleName="NavModule_One.ModuleInit"
                      ModuleType="NavModule_One.ModuleInit, NavModule_One, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                      InitializationMode="WhenAvailable"/>
 
    <prism:ModuleInfo Ref="NavModule_Two.dll"
                      ModuleName="NavModule_Two.ModuleInit"
                      ModuleType="NavModule_Two.ModuleInit, NavModule_Two, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                      InitializationMode="WhenAvailable">
        <prism:ModuleInfo.DependsOn>
            <sys:String>NavModule_One.ModuleInit</sys:String>
        </prism:ModuleInfo.DependsOn>
    </prism:ModuleInfo>
 </prism:ModuleCatalog>

The projects' Module_One "and" Module_Two "and" Independent_Module "double click" Property "Tab then" Build "in the Output section, and indicate the directory where the application resides NavShell ie the resulting file, eg… /bin/Debug/ or /bin/Release/


Rebuild Solution.

We'll add a Class Library project named "NavInfrastructure" to become independent modules but does not create many repetitive base classes and interface. We will add a reference to this library in your project NavShell.

The project NavInfrastructure add class NameRegions which will help us correct identification of the region and factoring in one place (of course you can stick with a dedicated code).

using System;
namespace NavInfrastructure 
public static class NameRegions
	{
		public static string NavigationTreeViewRegion = "NavigationTreeViewRegion"
	}

The project "NavShell" in the "Views" find a file called "ShellView.xaml" which is responsible for displaying the regions and their content. Of course, the regions themselves are created by ManagerRegion of helper classes using MappAdapter depending on what type of base is used to control the region.

Let's find the region in the file "ShellView.xaml" in the "NavShell" and is located in the "View" in which we use the TreeView that is the main theme of the article. Part of:

<local:RegionBorderControl Grid.Row="1" Grid.Column="0" RegionName="TopLeftRegion"
			Style="{StaticResource RegionBorderControlStyle}">
		<!--<span class="code-comment"> TopLeft Region : A simple content control -- >		<ContentControl prism:RegionManager.RegionName="TopLeftRegion"			VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" />
</local:RegionBorderControl >

Complement ShellView.xaml references in the file:

 xmlns:infra="clr-namespace:NavInfrastructure;assembly=NavInfrastructure"

Let's change the name of the region to:

        
<local:RegionBorderControl Grid.Row="1" Grid.Column="0" RegionName="Navigation"
                              Style="{StaticResource RegionBorderControlStyle}">
    <!--NavigationTreeViewRegion Region : A TreeView control --></span>
    <TreeView prism:RegionManager.RegionName="{x:Static infra:NameRegions.NavigationTreeViewRegion}"
                             VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"/>
</local:RegionBorderControl>

>Rebuild the entire solution - Rebuild solution -> F6.


The Modules Themselves Navigating

In this section, we create classes that are automatically loaded into the TreeView control and set up a root or main stems of navigation in the individual modules. About the navigation module to write in another part.

Shows two versions. The first in the XAML. The second is the procedural code, C#.

Code Xaml

We use the XAML code in the project "NavModule_One". In "Views" use the right mouse button and click Add> continue NewItem, and in the pattern selection window position UserControl.xaml NavigationModuleOne.xaml will call it. Open the newly created file. I will make factoring to the form:

<TreeViewItem x:Class="NavModule_One.Views.NavigationModuleOneView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:infra="clr-namespace:NavInfrastructure;assembly=NavInfrastructure"
              xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
              mc:Ignorable="d">
    <TreeViewItem.Resources>
        <!-- HierarchicalDataTemplates --></span>
        <HierarchicalDataTemplate x:Key="categoriesEntry"
                                  DataType="{x:Type infra:EntityBase}"
                                  ItemsSource="{Binding Path=SubEntity}">
            <StackPanel Orientation="Horizontal"
                        ToolTip="{Binding}">
                <ContentControl Margin="0 0 4 0"
                                Content="{Binding Icon, Converter={x:Static infra:StringToImage.Default}}" />
                <TextBlock FontWeight="Bold"
                           VerticalAlignment="Center"
                           Text="{Binding Path=Title}" />
            </StackPanel>
        </HierarchicalDataTemplate>
        <DataTemplate x:Key="headerDataTemplate"
                      DataType="{x:Type infra:Catalog}">
            <StackPanel Orientation="Horizontal"
                        ToolTip="{Binding}">
                <ContentPresenter Margin="0 0 4 0"
                                  Content="{Binding  Icon, Converter={x:Static infra:StringToImage.Default}}" />
                <TextBlock FontWeight="Bold"
                           Text="{Binding Title, Mode=TwoWay}"
                           VerticalAlignment="Center" />
            </StackPanel>
        </DataTemplate>
    </TreeViewItem.Resources>
    <TreeViewItem ItemsSource="{Binding Path=Categories}"
                  Header="{Binding Root}"
                  HeaderTemplate="{StaticResource headerDataTemplate}"
                  ItemTemplate="{StaticResource categoriesEntry}">
    </TreeViewItem>
    <i:Interaction.Triggers>
        <!--<i:EventTrigger EventName="Selected">--></span>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction Command="{Binding SelectedCommand}"
                                   CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource  AncestorType={x:Type TreeView}}}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TreeViewItem>

And the file code background to the form:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;
using NavModule_One.ViewModels;
 
namespace NavModule_One.Views
{
    /// <summary>
    /// Interaction logic for NavigationModuleOne.xaml
    /// </summary>
    [Export]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public partial class NavigationModuleOneView : TreeViewItem
    {
        public NavigationModuleOneView()
        {
            InitializeComponent();
        }
 
        static NavigationModuleOneView()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(NavigationModuleOneView), new FrameworkPropertyMetadata(typeof(ItemsControl)));
        }
 
        [Import(AllowRecomposition = false)]
        public NavigationModuleOneViewModel ViewModel
        {
            get { return this.DataContext as NavigationModuleOneViewModel; }
            set { this.DataContext = value; }
        }
    }
}

The most important change is the type of controls in a UserControl on a TreeViewItem control. HierarchicalDataTemplate allows the use of TreeViewItem which in our case is crucial. I used another sub TreeViewItem where the main root key replaces the default style for the ItemsControl which will handle the first level as a container, which appends the appropriate template HierarchicalDataTemplate and HeaderTemplate. Overriding style ItemsControl type of functionality will help you a list of items depriving it of part responsible for the header (Header_Part). These results also show that everything is in the proper "Theme" used in the project and the main application (in this case "NavShell"). I implemented these in a static constructor that is invoked only once when creating the object. Applying the standard model one level we use procedural code module "NavModule_Two". All the time we remember the class attributes decorating exports and imported constructors in classes that require the mobilization of a particular attribute constructor [ImportingConstructor]. We now report our control navigation in the region, "Navigation". We do this in class ModuleInit NavModule_One project that implements the interface IModule. We add a reference to the project "NavInfrastructure" which contains the names of the declared regions.

using NavInfrastructure;

And rebuild ModulInit.Initialize:

#region IModule Members
        public void Initialize()
        {
            // Use View Discovery to automatically display the MasterView when the TopLeft region is displayed.
            _regionManager.RegisterViewWithRegion(NameRegions.NavigationTreeViewRegion, () => _serviceLocator.GetInstance<NavigationModuleOneView>());
        }
        #endregion

Time to navigation logic implementations and the basis for presentation of nodes contained navigation.

We Will Need

The data model classes: EntityBase and Catalog which allows for the convenience and use them in other modules in the library NavInfrastructure.dll applications. The project NavInfrastructure EntityBase and add a class that inherits from the Catalog EntityBase class.

EntityBase.cs

namespace NavInfrastructure
{
    public class EntityBase
    {
        public int IDEntity { get; set; }
        public string Title { get; set; }
        public string Icon { get; set; }
        public string Description { get; set; }
        public EntityBase Parent { get; set; }
        public override string ToString()
        {
            if (string.IsNullOrEmpty(Description))
            {
                return Title;
            }
            else
            {
                return Description;
            }
        }
    }
}

Catalog.cs

using System.Collections.Generic;
 
namespace NavInfrastructure
{
    public class Catalog : EntityBase
    {
        public IEnumerable<EntityBase> SubEntity { get; set; }
    }
}

I would add the folder where you put the Images default node icons and I have now in the library


The QueryStringBuilder.cs class helps to build the address and downloaded from the project by Karl Shifflett http://karlshifflett.wordpress.com/ and StringToImage.cs class address conversion on the image icon.

Let's go back to the file NavigationModuleOneView.xaml. As I mentioned earlier the main level of the control I used as a container that provides templates HierarchicalDataTemplate and HeaderTemplate. We use the HierarchicalDataTemplate template as a class that provides data indicate EntityBase, Catalog class inherits from EntityBase to the extent required by the navigation in the Catalog data binding engine ignores binding to properties where there is no class EntityBase.


Classes Catalog and EntityBase are simple collections of the basic information needed to properly navigate through the compositions of the id and the type which requires the uniqueness of id.

Pattern MVVM Plus Cache

In this example we use the pattern (pattern) presentation of MVVM. Actually, it imposes itself. As can be seen in the file NavigationModuleOneView.xaml we combined the characteristics of the relevant class konteksu VM (ViewModel) that will be discussed in relation to the view (View).

The above diagram shows the core architecture NavModule_One module. For collaborating with the user corresponds to which NavigationModuleOneView instance through logging and mapping mechanism implemented in the Prizm is appended to the region Navigation in the main application NavShell. When an instance is built NavigationModuleOneView is built instance NavigationModuleOneViewModel mechanisms using MEF. Engine WPF or Silverlight bonds link the properties of both instances. The constructor itself NavigationModuleOneViewModel launch mechanism to create instances of the class that stores the data in the cache. Then initiates the data collection method through DocumentService service. NavigationModuleOneViewModel class is the main place where it is the whole logic of navigation.

Here is the entire class:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Prism.ViewModel;
using NavInfrastructure;
using NavModule_One.Models;
using NavModule_One.Properties;
 
namespace NavModule_One.ViewModels
{
    /// <summary>
    /// Main navigation class of the Module_One.
    /// </summary>
    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class NavigationModuleOneViewModel : NotificationObject
    {
        #region Declaration field class
        CacheManager _cacheManager;
        IRegionManager _regionManager;
        #endregion
        #region .ctor
        /// <summary>
        /// Default Mef .ctor. Called when creating an object constructor.
        /// </summary>
        /// <param name="cacheManager">The data cache manager.</param>
        /// <param name="regionManager">The Regions manager.</param>
        [ImportingConstructor] // Called when creating an object constructor
        public NavigationModuleOneViewModel(
            CacheManager cacheManager, // Initialization of the data cache manager
            IRegionManager regionManager // Initialization of the Regions manager
            )
        {
            if (cacheManager == null) throw new ArgumentNullException("cacheManager");
            if (regionManager == null) throw new ArgumentNullException("regionManager");
            this._cacheManager = cacheManager;
            this._regionManager = regionManager;
 
            _categories = new ObservableCollection<EntityBase>();
            // Get the data model from the Cache but wait how will be populated.
            ((NotificationObject)_cacheManager).PropertyChanged += new PropertyChangedEventHandler(NavigationDocumentsViewModel_PropertyChanged);
 
            // Initialize this ViewModel's commands. 
            // The command occurs upon request to an item
            SelectedCommand = new DelegateCommand<object>(SelectedExecute, CanExecuteSelected);
        }
        #endregion
        #region Property CurrentCategory and ...
        public EntityBase CurrentCategory { get; private set; }
        #endregion
        #region Property Root and helper methods
        private EntityBase _root;
        public EntityBase Root // Populate in NavigationDocumentsViewModel_PropertyChanged
        {
            get { return _root ?? new Catalog() { Title = Strings.LoadingModuleMessage }; }
            set
            {
                if (value != _root)
                {
                    _root = value;
                    this.RaisePropertyChanged("Root");
                }
            }
        }
        #endregion
        #region Property Categories and helper methods
        private ObservableCollection<EntityBase> _categories;
        public ObservableCollection<EntityBase> Categories { get { return _categories; } }
        #endregion
        #region Helper methods
        void NavigationDocumentsViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "CacheDocuments")
            {
                Populate();
                // Po każdym resecie Documents (Przypisanie nowej kolekcji usuwa również subskrybcje)
                // przypisujemy Subskrybcje na zdarzenia zmian w kolekcji.
                _cacheManager.CacheDocuments.CollectionChanged += new NotifyCollectionChangedEventHandler(CacheDocuments_CollectionChanged);
            }
        }
        void CacheDocuments_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null || e.OldItems != null)
                Populate();
        }
        void Populate()
        {
            PopulateRoot();
            this.Categories.Clear();
            foreach (var i in PopulateItemCategories(_root.IDEntity))
            {
                Categories.Add(i);
            }
        }
        void PopulateRoot()
        {
            this.Root = this._cacheManager.CacheDocuments.Single(i => i.Parent == null);
            if (string.IsNullOrEmpty(Root.Icon)) this.Root.Icon = Settings.Default.RootIcon;
        }
        IEnumerable<EntityBase> PopulateItemCategories(int idParent)
        {
            var tempItem = this._cacheManager.CacheDocuments.Where(i => i.Parent != null && i.Parent.IDEntity == idParent);
            foreach (var e in tempItem)
            {
                Catalog cat = e as Catalog;
                if (cat != null)
                {
                    cat.SubEntity = PopulateItemCategories(e.IDEntity);
                }
            }
            return tempItem;
        }
        #endregion
        #region SelectedCommand
        public DelegateCommand<object> SelectedCommand { get; private set; }
        private void SelectedExecute(object commandParameter)
        {
            if (commandParameter != null)
            {
                EntityBase obj = commandParameter as EntityBase;
                if (obj != null)
                {
                    Catalog cat = obj as Catalog;
                    if (cat != null)
                    {
                        string addressView = QueryStringBuilder.Construct(ViewsName.CatalogView, new[,] { { ViewsName.ParentId, obj.IDEntity.ToString() } });
                        _regionManager.RequestNavigate(NameRegions.MainRegion, addressView, Callback);
                    }
                    else
                    {
                        _regionManager.RequestNavigate(NameRegions.MainRegion, QueryStringBuilder.Construct(ViewsName.DocumentView, new[,] { { ViewsName.ParentId, obj.IDEntity.ToString() } }), Callback);
                    }
                }
                else
                {
                    // First level.
                    if (_root != null)
                    {
                        string addressView = QueryStringBuilder.Construct(ViewsName.CatalogView, new[,] { { ViewsName.ParentId, _root.IDEntity.ToString() } });
                        _regionManager.RequestNavigate(NameRegions.MainRegion, addressView, Callback);
                    }
                }
            }
        }
        private bool CanExecuteSelected(object commandParameter)
        {
            return true;
        }
        #endregion
        #region Navigation helper
        void Callback(NavigationResult result)
        {
            // Todo: To do log.
            Debug.WriteLine("NavigationResult: {0}", result.Result);
        }
        #endregion
    }
}

The class can be divided into three functional parts: first - working with data, the second command operation initiated by the user; The third is the proper presentation of data in the view. For his work with the data corresponding to a constructor that initializes the retrieval, the event handler

method void NavigationDocuments_PropertyChanged (object sender, <br />PropertyChangedEventArgs e)
and
void CacheDocuments_CollectionChanged (object sender, <br />NotifyCollectionChangedEventArgs e)
and the auxiliary method void Populate (), void PopulateRoot (), IEnumerable <EntityBase> PopulateItemCategories (int idParent). For handling the events initiated by the user corresponds to the functional group contained in the
public DelegateCommand <object> SelectedCommand {get; private set;} <br />private void SelectedExecute (object CommandParameter)
, private bool CanExecuteSelected (object CommandParameter), void Callback (NavigationResult result) and the presentation of data through the related properties CurrentCategory, Root, Categories. In order to well illustrate the process of loading data in the class applied MockDokumentsService data transmission delay to 5 seconds. As the loading method applied here Asynchronous loading of other components of the application takes place concurrently. While waiting for the completion of data loading effect is displayed in the box for the navigation of our module. To receive a user-driven needs of navigation used a method of initiating events by triggering occurs when double-clicking the mouse on the selected item. The project should include the appropriate library  C:\Program Files (x86)\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Libraries\System.Windows.Interactivity.dll which is placed in SDKBlend. NetFramework4.0 and add a reference to a file and view System.Windows.Interactivity

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity".

Presentation Navigated Views

We are left now to create views of class and you navigate to the region to create their presentation. When we go back to class NavigationModuleOneViewModel SelectedCommand section in the method private void SelectedExecute (object CommandParameter) we find a piece of code (if someone rewrote or pasted from the text is certainly underscores indicating errors)

_regionManager.RequestNavigate(
			NameRegions.WorkbenchRegion, 
			QueryStringBuilder.Construct(ViewsName.
				CatalogView, new[,] { { ViewsName.ParentId, obj.IDEntity.ToString() } }), 
			Callback);

This passage is required to navigate to a specific address in the module. However, we will zaczym this in detail we need to create a region in which display the appropriate view, class view, model view, and all related infrastructure.

Region MainRegion

The project "NavShell" open the file and find in ViewShell.xaml:

<!--Style to display an icon and the current item's name in the tab header--></span>
        <Style x:Key="TabHeaderStyle" TargetType="{x:Type TabItem}">
            <Setter Property="HeaderTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Height="20"
                                   Width="20"
                                   Margin="0,0,2,0"
                                   Source="/NavigateTreeView;component/Images/ItemIcon.png"/>
                            <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem} }, Path=Content.DataContext.CurrentItem.Name}"
                                       VerticalAlignment="Center"/>
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>

I rebuild the following:

<!--Style to display an icon and the current item's name in the tab header-->
        <Style x:Key="TabHeaderStyle"
               TargetType="{x:Type TabItem}">
            <Setter Property="HeaderTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <DataTemplate.Resources>
                            <Style x:Key="ToolButtonStyle"
                                   BasedOn="{x:Null}"
                                   TargetType="{x:Type Button}">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="{x:Type Button}">
                                            <Grid>
                                                <Rectangle Opacity="0"
                                                           Fill="#66FFFFFF"
                                                           Stroke="{x:Null}"
                                                           StrokeThickness="0.5"
                                                           HorizontalAlignment="Stretch"
                                                           Margin="2,0,2,0"
                                                           x:Name="rectangle"
                                                           VerticalAlignment="Stretch" />
                                                <Path Fill="{x:Null}"
                                                      x:Name="leftLine"
                                                      Stretch="Fill"
                                                      Stroke="LightGray"
                                                      StrokeThickness="1"
                                                      HorizontalAlignment="Left"
                                                      Margin="0.5"
                                                      Width="1"
                                                      Height="18"
                                                      Grid.RowSpan="1"
                                                      Data="M-87.28,4 L-87.28,17" />
                                                <Path Fill="{x:Null}"
                                                      x:Name="rightLine"
                                                      Stretch="Fill"
                                                      Stroke="LightGray"
                                                      StrokeThickness="1"
                                                      HorizontalAlignment="Right"
                                                      Margin="0.5"
                                                      Height="18"
                                                      Width="1"
                                                      Grid.RowSpan="1"
                                                      Data="M-87.28,4 L-87.28,17" />
                                                <ContentPresenter x:Name="presenter"
                                                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                                  Margin="10,0,10,0"
                                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                                                  TextElement.Foreground="LightGray"
                                                                  RecognizesAccessKey="True" />
                                            </Grid>
                                            <ControlTemplate.Triggers>
                                                <Trigger Property="IsMouseOver"
                                                         Value="True">
                                                    <Setter Property="Opacity"
                                                            TargetName="rectangle"
                                                            Value="0.5" />
                                                    <Setter Property="TextElement.Foreground"
                                                            TargetName="presenter"
                                                            Value="Red" />
                                                    <Setter Property="Path.Stroke"
                                                            TargetName="leftLine"
                                                            Value="Red" />
                                                    <Setter Property="Path.Stroke"
                                                            TargetName="rightLine"
                                                            Value="Red" />
                                                    <Setter Property="Rectangle.Fill"
                                                            TargetName="rectangle"
                                                            Value="LightGray" />
                                                </Trigger>
                                                <Trigger Property="IsPressed"
                                                         Value="True">
                                                    <Setter Property="Opacity"
                                                            TargetName="rectangle"
                                                            Value="1" />
                                                    <Setter Property="Fill"
                                                            TargetName="rectangle"
                                                            Value="DarkGray" />
                                                </Trigger>
                                                <Trigger Property="IsEnabled"
                                                         Value="False" />
                                            </ControlTemplate.Triggers>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </DataTemplate.Resources>
                        <StackPanel Orientation="Horizontal">
                            <Image Height="16"
                                   Width="16"
                                   Margin="0,0,2,0"
                                   Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentControl} }, Path=Content.DataContext.CurrentItem.Icon}" />
                            <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentControl} }, Path=Content.DataContext.CurrentItem.Title}"
                                       VerticalAlignment="Center"
                                       TextTrimming="CharacterEllipsis" />
                            <Button Content="x"
                                    Style="{StaticResource ToolButtonStyle}"
                                    Margin="5,0,0,0"
                                    Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}, Path=Content.DataContext.KillingCommand}"
                                    CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentControl}},Path=Content}" />
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Despite the large amount of code changes are cosmetic and the only change to the required binding properties and bind specific shutdown command to view the close button in the header view.

Presentation Views

The project "NavModule_One" Views directory add a new user control to the UserControl named CatalogView.xaml and rebuild the file to the form:

<UserControl x:Class="NavModule_One.Views.CatalogView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d">
    <Grid>
        <ListBox ItemsSource="{Binding CatalogItems}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!-- Utworzyć styl HeaderItemsControl z możliwością pokazywaia niższych kategorii -->
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path=Title}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</UserControl>

DocumentView.xaml

<UserControl x:Class="NavModule_One.Views.DocumentView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid x:Name="LayoutRoot"
        Background="White">
        <ScrollViewer>
            <RichTextBox>
                <FlowDocument>
                    <Paragraph>Text testowy do próby</Paragraph>
                </FlowDocument>
            </RichTextBox>
        </ScrollViewer>
        </Grid>
</UserControl>

ViewModelBase

Each concrete class ViewModel base class inherits from ViewModelBase contained in the draft secondary NavInfrastructure.dll. It contains only one function correctly namely the child associated with the closing of the class view. Will join the project links to libraries Prism and ServiceLocation. KillView function searches the list of specific views and removes it from the list. There is no implemented mechanism to detect changes in a file (which will be covered in another paper). It is worth noting that the property relating to the killing of the view that the delegate of transferring the function to the associated property in the file CatalogView.xaml.

using System.Linq;
using Microsoft.Practices.Prism.ViewModel;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.ServiceLocation;
 
namespace NavInfrastructure
{
    public abstract class ViewModelBase : NotificationObject
    {
        #region Killing behavior
        public virtual DelegateCommand<object> KillingCommand
        {
            get { return _killingCommand ?? (_killingCommand = new DelegateCommand<object>(KillView)); }
            set { _killingCommand = value; }
        }
        private DelegateCommand<object> _killingCommand;
        public virtual void KillView(object view)
        {
            // Arbitrary Container.
            IRegionManager manager = ServiceLocator.Current.GetInstance<IRegionManager>();
            // find and remove view.
            foreach (IRegion region in manager.Regions)
            {
                // Find current view
                // Ustalamy obiekt na liście widoków
                object removeView = region.Views.SingleOrDefault(v => v == view);
                if (removeView != null)
                    // Remove finding view.
                    // Usuwamy ustalony widok.
                    manager.Regions[region.Name].Remove(view);
            }
        }
        #endregion
    }
}

The Concrete Class ViewModel

Both classes DocumentViewModel and CatalogViewModel inherit from ViewModelBase.

Class CatalogViewModel

using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.Practices.Prism.Regions;
using NavInfrastructure;
using NavModule_One.Models;
 
namespace NavModule_One.ViewModels
{
    [Export]
    public class CatalogViewModel : ViewModelBase, INavigationAware
    {
        #region Private Field
        CacheManager _cacheManager;
        #endregion
 
        #region .ctor
 
        [ImportingConstructor]
        public CatalogViewModel(CacheManager cacheManager)
        {
            _cacheManager = cacheManager;
        }
 
        #endregion
 
        #region Property CurrentItem
        /// <summary>
        /// Current base item.
        /// </summary>
        public EntityBase CurrentItem
        {
            get { return _currentItem; }
            private set
            {
                if (value != _currentItem)
                {
                    _currentItem = value;
                    this.RaisePropertyChanged("CurrentItem");
                }
            }
        }
        private EntityBase _currentItem;
        #endregion
        #region Property CatalogItems
 
        private ObservableCollection<EntityBase> _catalogItems;
        public ObservableCollection<EntityBase> CatalogItems // 
        {
            get { return _catalogItems; }
            set
            {
                _catalogItems = value;
                RaisePropertyChanged("CatalogItems");
            }
        }
 
        #endregion
        #region INavigationAware Members
        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            // Only one view.
            return true;
        }
 
        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
        }
 
        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            var idEntity = int.Parse(navigationContext.Parameters["ParentId"]);
            this.CurrentItem = _cacheManager.CacheDocuments.Single(i => i.IDEntity == idEntity);
            PopulateViewModel();
        }
 
        #endregion
        #region Help method
        private void PopulateViewModel()
        {
            CatalogItems.Clear();
            foreach (var i in _cacheManager.CacheDocuments.Where(i => i.Parent != null && i.Parent.IDEntity == _currentItem.IDEntity))
            {
                CatalogItems.Add(i);
            }
        }
        #endregion
    }
}

The CatalogViewModel class because it is not intended to change the content of which does not require confirmation INavigationAware implements interface which is responsible for the behavior when navigating to this type of class. I recommend that you read the documentation PRISM  http://compositewpf.codeplex.com/.

Class DocumentViewModel:

using System;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.Practices.Prism.Regions;
using NavInfrastructure;
using NavModule_One.Models;
 
namespace NavModule_One.ViewModels
{
    /// <summary>
    /// ViewModel class's Document.
    /// </summary>
    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class DocumentViewModel : ViewModelBase, IConfirmNavigationRequest
    {
        #region Private Field
        CacheManager _cacheManager;
        #endregion 
        #region .ctor
        /// <summary>
        /// Importing Constructor. 
        /// </summary>
        /// <param name="cacheManager"> Manager data cache.</param>
        [ImportingConstructor]
        public DocumentViewModel(CacheManager cacheManager)
        {
            if (cacheManager == null) throw new ArgumentNullException("cacheManager");
            this._cacheManager = cacheManager;
        }
        #endregion 
        #region Property CurrentItem
        /// <summary>
        /// Current base item.
        /// </summary>
        public EntityBase CurrentItem
        {
            get { return _currentItem; }
            private set
            {
                if (value != _currentItem)
                {
                    _currentItem = value;
                    this.RaisePropertyChanged("CurrentItem");
                }
            }
        }
        private EntityBase _currentItem;
        #endregion
        #region IConfirmNavigationRequest Members
        /// <summary>
        /// Implementation confirm.
        /// </summary>
        /// <param name="navigationContext"></param>
        /// <param name="continuationCallback"></param>
        public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            continuationCallback(true);
        }
        #endregion
        #region INavigationAware Members
        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return CurrentItem.IDEntity.ToString() == navigationContext.Parameters["ParentId"];
        }
        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
        }
        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            var idEntity = int.Parse(navigationContext.Parameters["ParentId"]);
            this.CurrentItem = _cacheManager.CacheDocuments.Single(i => i.IDEntity == idEntity);
        }
        #endregion
    }
}

The DokumentViewModel class has two important features that deserve attention. In the first method of co-operation with the application indicating that the container each time we want to create your own instance of the specified document. PartCreationPolicyAttrybute gets NonShared value. This was the case where the CatalogViewModel instance was shared. The second most important item from our point of view is that there are two implementations of interface methods for INavigationAware - IsNavigationTarget and OnNavigationTo. The first is called when navigation is addressed to the same type of class and, depending on the result, is assigned to the instance data, or creates a new (region checks are no longer appropriate for instance to the address indicated.) The second is called when the next navigation is addressed to a given instance, and should start the imputation. And here it is briefly described in NavModuleOne.

The Other Way but Not Quite

Actually, it will only be a procedural version NavModule_One navigation.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using NavInfrastructure;
using System.Windows.Interactivity;
using System.ComponentModel.Composition;
using NavModule_Two.ViewModels;
 
namespace NavModule_Two.Views
{
    [Export]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class NavigationModuleTwoView : TreeViewItem
    {
        #region .ctor's
        public NavigationModuleTwoView()
        {
            InitializeComponent();
        }
        #endregion 
        #region SetComponent
        void InitializeComponent()
        {
            this.SetBinding(ItemsSourceProperty, new Binding() { Path = new PropertyPath("ContentTable") });
            this.HeaderTemplate = GetHeaderTemplate();
            this.SetBinding(HeaderProperty, new Binding() { Path = new PropertyPath("Root") });
            this.ItemTemplate = this.GetHierarchicalTemplate();
            this.Resources.Add("hierarchicalTemplate", this.ItemTemplate);
        } 
        private DataTemplate GetHeaderTemplate()
        {
            DataTemplate dataTemplate = new DataTemplate(typeof(EntityBase)); 
            //create stack pane;
            FrameworkElementFactory stackPanel = new FrameworkElementFactory(typeof(StackPanel));
            stackPanel.Name = "headerStackPanel";
            stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
            stackPanel.SetBinding(StackPanel.ToolTipProperty, new Binding()); 
            // Create Image 
            FrameworkElementFactory icon = new FrameworkElementFactory(typeof(ContentPresenter));
            icon.SetValue(ContentPresenter.MarginProperty, new Thickness(2));
            icon.SetBinding(ContentPresenter.ContentProperty, new Binding() { Path = new PropertyPath("Icon"), Converter = StringToImage.Default });
            stackPanel.AppendChild(icon); 
            // create text
            FrameworkElementFactory titleLabel = new FrameworkElementFactory(typeof(TextBlock));
            titleLabel.SetValue(TextBlock.TextProperty, new Binding("Title"));
            titleLabel.SetValue(TextBlock.FontWeightProperty, FontWeights.Bold);
            titleLabel.SetValue(TextBlock.VerticalAlignmentProperty, VerticalAlignment.Center);
            stackPanel.AppendChild(titleLabel); 
            //set the visual tree of the data template
            dataTemplate.VisualTree = stackPanel;
            return dataTemplate;
        }
        private HierarchicalDataTemplate GetHierarchicalTemplate()
        {
            //create the data template
            HierarchicalDataTemplate dataTemplate = new HierarchicalDataTemplate(typeof(EntityBase)); 
            //create stack pane;
            FrameworkElementFactory stackPanel = new FrameworkElementFactory(typeof(StackPanel));
            stackPanel.Name = "parentStackpanel";
            stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
            stackPanel.SetBinding(StackPanel.ToolTipProperty, new Binding()); 
            // Create Image as ContentPresenter
            FrameworkElementFactory content = new FrameworkElementFactory(typeof(ContentPresenter));
            content.SetValue(ContentPresenter.MarginProperty, new Thickness(2));
            content.SetBinding(ContentPresenter.ContentProperty, new Binding() { Path = new PropertyPath("Icon"), Converter = StringToImage.Default });
            stackPanel.AppendChild(content); 
            // create text
            FrameworkElementFactory titleLabel = new FrameworkElementFactory(typeof(TextBlock));
            titleLabel.SetBinding(TextBlock.TextProperty, new Binding() { Path = new PropertyPath("Title") });
            titleLabel.SetValue(TextBlock.VerticalAlignmentProperty, VerticalAlignment.Center);
            stackPanel.AppendChild(titleLabel); 
            //set the visual tree of the data template
            dataTemplate.VisualTree = stackPanel;
            dataTemplate.ItemsSource = new Binding() { Path = new PropertyPath("SubEntity") };
            return dataTemplate;
        }
        #endregion 
        #region Setting DataContext
        private NavigationModuleTwoViewModel _viewModel;
        [Import]
        public NavigationModuleTwoViewModel ViewModel // 
        {
            get { return _viewModel; }
            set
            {
                _viewModel = value;
                this.DataContext = _viewModel;
                // Samemu trzeba zadbać o właściwe dopisanie innych zadań
                Populate();
            }
        }
        #endregion 
        #region Help Method
        // Inne zadania
        private void Populate()
        {
            /* In Xaml appears so:
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Selected">
                    <i:InvokeCommandAction Command="{Binding SelectedCommand}"
                                           CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource  AncestorType={x:Type TreeView}}}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
            */
            // In Procedural Code:
            InvokeCommandAction iCASelect = new InvokeCommandAction();
            iCASelect.SetValue(InvokeCommandAction.CommandProperty, this._viewModel.SelectedCommand);
            BindingOperations.SetBinding(iCASelect,
                InvokeCommandAction.CommandParameterProperty,
                new Binding()
                {
                    Path = new PropertyPath("SelectedItem"),
                    RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(TreeView), 1)
                });
            var eventTriggerSelect = new System.Windows.Interactivity.EventTrigger("Selected");//or MouseDubleClick
            eventTriggerSelect.Actions.Add(iCASelect);
            var triggerColl = Interaction.GetTriggers(this);
            triggerColl.Add(eventTriggerSelect);
        }
        #endregion
    }
}

The rest is analogous to NavModule_One And there has not been implemented.

Summary

Known issues:

Lack of proper navigation using the arrow keys

The independent modules must either implement their own views TabControlItem shutdown procedure as DelegatCommand <object> the DataContext or the DataContext base class for navigation use ViewModelBase NavInfrastructure library. Another general solution could be to implement a general method for NavShell and gathering information from the individual views of their wish to confirm the closure eg IEventAggregator.

In my view, these examples can serve as a beginning to develop the topic in order to detect and resolve the emerging problems in such solutions. Some of the solutions presented in the above examples seem to be unnecessary or misguided but they are here because of attempts to unify both for use in WPF and Silverlight.

If people find this article interesting I would be interested in further developing the theme of PRISM. The present article allows you to add a ToolBar at the option of the module as well as a particular view.

Andrzej Skutnik

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