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}">
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}">
--></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>
<!----></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>
<!----></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
{
[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()
{
_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
{
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class NavigationModuleOneViewModel : NotificationObject
{
#region Declaration field class
CacheManager _cacheManager;
IRegionManager _regionManager;
#endregion
#region .ctor
[ImportingConstructor] public NavigationModuleOneViewModel(
CacheManager cacheManager, IRegionManager regionManager )
{
if (cacheManager == null) throw new ArgumentNullException("cacheManager");
if (regionManager == null) throw new ArgumentNullException("regionManager");
this._cacheManager = cacheManager;
this._regionManager = regionManager;
_categories = new ObservableCollection<EntityBase>();
((NotificationObject)_cacheManager).PropertyChanged += new PropertyChangedEventHandler(NavigationDocumentsViewModel_PropertyChanged);
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 {
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();
_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
{
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)
{
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:
<!----></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 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>
<!---->
<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)
{
IRegionManager manager = ServiceLocator.Current.GetInstance<IRegionManager>();
foreach (IRegion region in manager.Regions)
{
object removeView = region.Views.SingleOrDefault(v => v == view);
if (removeView != null)
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
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)
{
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
{
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class DocumentViewModel : ViewModelBase, IConfirmNavigationRequest
{
#region Private Field
CacheManager _cacheManager;
#endregion
#region .ctor
[ImportingConstructor]
public DocumentViewModel(CacheManager cacheManager)
{
if (cacheManager == null) throw new ArgumentNullException("cacheManager");
this._cacheManager = cacheManager;
}
#endregion
#region Property CurrentItem
public EntityBase CurrentItem
{
get { return _currentItem; }
private set
{
if (value != _currentItem)
{
_currentItem = value;
this.RaisePropertyChanged("CurrentItem");
}
}
}
private EntityBase _currentItem;
#endregion
#region IConfirmNavigationRequest Members
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