poniedziałek, 26 grudnia 2011

Bridge Pattern

Bridge pattern (most) - strukturalny wzorzec projektowy pozwalający oddzielić abstrakcję obiektu od jego implementacji.

Zobaczmy na diagram klas:


Klasy które uczestniczą w tym wzorcu:
Abstraction - definiuje interfejs abstrackji oraz posiada referencję do implementacji
RefinedAbstraction - rozszerza interfejs abstrakcji
Implementator - definiuje interfejs implementatora. Interfejs ten nie musi być zgodny z abstrakcją. Przeważnie Implementator dostarcza niskopoziomowego interfejsu, a abstrakcja operuje na tym interfejsie.
ConcreteImplementator - implementuje interfejs Implementatora.

Dzięki wzorcowi oddzielamy interfejs od implementacji. Głównym celem jest wydzielenie operacji do interfejsu aby klient i serwis mogli działać oddzielnie.
Zysk z zastosowania wzorca:
  • oddzielenie interfejsu od implementacji
  • zwiększona elastyczność - zarówno interfejs jak i konkretną klasę można rozwijać indywidualnie
  • ukrycie implementacji przed klientem 
Przykład realizacji:


Code:
using System;
using System.Collections.Generic;
using System.Linq;

namespace BridgePatternExample
{
    public class Program
    {
        static void Main()
        {
            var customers = new Customers{DataContext = new CustomerContext()};
            customers.Add(new Customer{BirthYear = 2000, FirstName = "Mark", Id = 1, LastName = "Poldkowski"});
            customers.Add(new Customer { BirthYear = 2000, FirstName = "Sylwester", Id = 2, LastName = "Markowski" });
            customers.Add(new Customer { BirthYear = 2000, FirstName = "Paweł", Id = 3, LastName = "Szymanowski" });

            customers.Print();
        }
    }

    public abstract class CustomersTable
    {
        public DataContext DataContext { get; set; }

        public abstract void Add(EntityBase  customer);
        public abstract void Remove(EntityBase customer);
        public abstract EntityBase Find(int id);
        public abstract void Print();
    }

    public class Customers : CustomersTable
    {
        public override void Add(EntityBase customer)
        {
            DataContext.AddEntity(customer);
        }

        public override void Remove(EntityBase customer)
        {
            DataContext.RemoveEntity(customer);
        }

        public override EntityBase Find(int id)
        {
            return DataContext.FindEntity(id);
        }

        public override void Print()
        {
            DataContext.PrintCustomers();
        }
    }

    public abstract class DataContext
    {
        public abstract void AddEntity(EntityBase entity);
        public abstract void RemoveEntity(EntityBase entity);
        public abstract EntityBase FindEntity(int id);

        public abstract void PrintCustomers();
    }

    public class CustomerContext : DataContext
    {
        private List<EntityBase> _customers = new List<EntityBase>();

        public override void AddEntity(EntityBase entity)
        {
            _customers.Add(entity);
        }

        public override void RemoveEntity(EntityBase entity)
        {
            _customers.Remove(entity);
        }

        public override EntityBase FindEntity(int id)
        {
            return _customers.FirstOrDefault(entity => entity.Id == id);
        }

        public override void PrintCustomers()
        {
            foreach (var customer in _customers)
            {
                Console.WriteLine(customer);
            }
        }
    }

    public abstract class EntityBase
    {
        public int Id { get; set; }
    }

    public class Customer : EntityBase
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }

        public override string ToString()
        {
            return string.Format("{0} {1} {2}", FirstName, LastName, BirthYear);
        }
    }
}

niedziela, 25 grudnia 2011

Prototype Design Pattern

Wzorzec Prototype (prototyp) - mówi o tworzeniu obiektów na podstawie prototypu. Jak inne wzorce z grupy wzorców kreacyjnych, wzorzec ten ukrywa przed klientem proces tworzenia obiektu. W przeciwieństwie do innych obiektów, wzorzec ten tworzy kolejne obiekty na podstawie prototypu - klasy która została zainicjowana początkowymi wartościami. Wykorzystanie tego wzorca nie jest duże w przypadku aplikacji biznesowych, przydaje się on natomiast w przypadku aplikacji używanych przez inżynierów.
W .NET istnieje interfejs ICloneable, który dostarcza metody Clone.
Spójrzmy na diagram klas dla tego wzorca:






Klasy, które uczestniczą we wzorcu:
Prototype - udostępnia interfejs klonowania obiektu
ConcretePrototype1 i ConcretePrototype2 - implementują interfejs Prototype dając możliwość klonowania samego siebie
Client - tworzy nowe obiektu poprzez wywołanie metody Clone

Jak wspominałem wcześniej w .NET istnieje już gotowy interfejs ICloneable. Zobaczmy w jaki sposób można go zaimplementować:


Code:
using System;

namespace PrototypeExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person{FirstName = "Jacek", LastName = "Kowalski", BirthYear = 2000};
            Console.WriteLine(person);
            var clone = person.Clone();
            Console.WriteLine(clone);
        }
    }

    class Person : ICloneable
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }

        public override string ToString()
        {
            return string.Format("{0} {1} {2}", FirstName, LastName, BirthYear);
        }

        public object Clone()
        {
            return new Person{FirstName = FirstName, LastName = LastName, BirthYear = BirthYear};
            //equals to: return this.MemberwiseClone();
        }
    }
}

W kodzie w metodzie Clone widać zakomentowany kod. Otóż należy być bardzo uważnym podczas implementowania metody Clone. Chodzi o to iż są dwa rodzaje klonowania - deep copy oraz shell copy. Shell copy wykonuje tzw. płytką kopię czyli przepisuje wartości pól. Deep copy przepisuje to co znajduje się pod danym polem. Jeżeli jakieś pole jest referencją, to w przypadku shell copy przepiszemy tylko tę referencję, jeżeli mamy do czynienia z deep copy - obiekt zostanie skopiowany.

Miejmy powyższe zdanie w pamięci i zgodnie z zaleceniami większości deweloperów, podczas implementowania interfejsu ICloneable implementujmy deep copy.

Długi czas odpowiedzi Visual Studio 2010 SP1

Od jakiegoś czasu miałem program z Visual Studio 2010. Po zainstalowaniu dodatku Service Pack 1 ładowanie Designera zajmowało nawet 55 sekund (zgroza...). W takim tempie nie da się pracować.
Pierwszą myślą oczywiście było to, iż mój sprzęt nie spełnia już wymagań. Postanowiłem jednak zbadać w internecie co może powodować tak wolne przełączanie się między kodem a designerem.
Co dodatkowo zaobserwowałem to to, iż operacją która zajmowała tyle czasu było:

"Loading toolbox content from package 'Microsoft.VisualStudio.IDE.ToolboxControlsInstaller,ToolboxInstallerPackage'{2C298B35-07DA-45F1-96A3-BE55D91C8D7A}
Wrzuciłem frazę w wyszukiwarkę i okazało się, iż problem jest już od dłuższego czasu znany. Pod tym linkiem można znaleźć opis problemu i odpowiedzi użytkowników którzy także doświadczyli mniejszej wydajności podczas pracy z VS http://connect.microsoft.com/VisualStudio/feedback/details/551183/loading-toolbox-content-from-package-takes-55-seconds-or-more

Znajduje się tam także obejście jak pokonać problem. Wystarczy za pomocą edytora rejestru (oczywiście wcześniej tworzymy kopię klucza który zamierzamy wyedytować!) usuwamy wpis:

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\Packages{2C298B35-07DA-45F1-96A3-BE55D91C8D7A}

Po tej operacji i ponownym uruchomieniu systemu VS na powrót odzyskał sprawność.

Design Pattern Builder

Wzorzec budowniczego (Builder) rozdziela proces tworzenia obiektu od jego reprezentacji dzięki czemu ten sam proces tworzenia obiektu może tworzyć różne reprezentacje.

Własnymi słowami: Builder umożliwia w łatwy sposób konstrukcję skomplikowanych obiektów. Builder otrzymuje informację tylko nt. tego jakiego typu obiekt chcemy stworzyć. Cały proces tworzenia obiektu jest ukryty przed użytkownikiem. Przydaje się zwłaszcza tam, gdzie kod tworzący dany komponent jest powtarzalny.
W przeciwieństwie do wzorca Abstract Factory każdy etap tworzenia obiektu przez Buildera jest modyfikowalny. W przypadku Abstract Factory otrzymujemy obiekt w jednym kroku.

Zobaczmy na diagram klas:

Klasy które występują w tym wzorcu:
Director - tworzy obiekt za pomocą interfejsu dostarczonego przez Builder-a
Builder - definiuje interfejs tworzenia składowych Product-u
ConcreteBuilder - spełnia 3 role:
  • Tworzy obiekty na podstawie interfejsu buildera
  • przechowuje instancję obiektu który wyprodukuje
  • udostępnia interfejs umożliwiający pobranie stworzonego obiektu
Product:
  • reprezentuje obiekt który chcemy stworzyć
  • ConcreteBuilder inicjuje wewnętrzną strukturę obiektu 
 Zobaczmy na przykład. Mamy warsztat samochodowy, który zajmuje się składaniem samochodów osobowych i quadów. Pojazdy te składają się z tych samych elementów, tylko w innej konfiguracji i ilości.
Zobaczmy najpierw na schemat klas:


W diagramie tym klasa VehicleBuilder reprezentuje Buildera. Klasy CarBuilder oraz QuadBuilder to konkretni budowniczy obiektów. WorkShop to obiekt utożsamiany z Direcotr. Vehicle to konkretny produkt, a klasy Wheel, Engine, Doors to składowe produktu.

Zobaczmy na sam kod:


Code:
using System;
using System.Text;

namespace BuilderSample
{
    class Program
    {
        static void Main()
        {
            VehicleBuilder builder = new CarBuilder();
            var workShop = new WorkShop();
            workShop.MakeVehicle(builder);
            builder.Vehicle.Specyfication();
            Console.WriteLine("--------------------------------------------");
            builder = new QuadBuilder();
            workShop.MakeVehicle(builder);
            builder.Vehicle.Specyfication();
        }
    }

    class WorkShop
    {
         public void MakeVehicle(VehicleBuilder vehicleBuilder)
         {
             vehicleBuilder.BuildDoor();
             vehicleBuilder.BuildEngine();
             vehicleBuilder.BuildWhell();
         }
    }

    public class Vehicle
    {
        public Engine Engine { get; set; }
        public Wheel Wheel { get; set; }
        public Doors Doors { get; set; }
        public string Type { get; set; }

        public Vehicle(string type)
        {
            Type = type;
        }

        public void Specyfication()
        {
            if (Engine == null)
            {
                throw new NullReferenceException("Engine can't be null");
            }
            if (Wheel == null)
            {
                throw new NullReferenceException("Whell can't be null");
            }
            if (Doors == null)
            {
                throw new NullReferenceException("Doors can't be null");
            }

            var specyficataion = new StringBuilder();
            specyficataion.Append("Type: ");
            specyficataion.Append(Type);
            specyficataion.AppendLine();
            specyficataion.Append("Door count: ");
            specyficataion.Append(Doors.Count);
            specyficataion.AppendLine();
            specyficataion.AppendLine("Whell specyfication: ");
            specyficataion.Append("Whell count: ");
            specyficataion.Append(Wheel.Count);
            specyficataion.AppendLine();
            specyficataion.Append("Whell producer: ");
            specyficataion.Append(Wheel.Producer);
            specyficataion.AppendLine();
            specyficataion.Append("Doors count: ");
            specyficataion.Append(Doors.Count);
            Console.WriteLine(specyficataion);
        }
    }

    public abstract class VehicleBuilder
    {
        protected Vehicle vehicle;

        public Vehicle Vehicle
        {
            get { return vehicle; }
        }

        public abstract void BuildEngine();
        public abstract void BuildWhell();
        public abstract void BuildDoor();
    }

    public class CarBuilder : VehicleBuilder
    {
        public CarBuilder()
        {
            vehicle = new Vehicle("Car");
        }

        public override void BuildEngine()
        {
            Vehicle.Engine = new Engine{CylindersCount = 4, Power = 100, Type = "Car engine"};
        }

        public override void BuildWhell()
        {
            Vehicle.Wheel = new Wheel{Count = 4, Producer = "Debica"};
        }

        public override void BuildDoor()
        {
            Vehicle.Doors = new Doors{Count = 4};
        }
    }

    public class QuadBuilder : VehicleBuilder
    {
        public QuadBuilder()
        {
            vehicle = new Vehicle("Quad");
        }

        public override void BuildEngine()
        {
            Vehicle.Engine = new Engine{CylindersCount = 2, Power = 50, Type = "Quad engine"};
        }

        public override void BuildWhell()
        {
            Vehicle.Wheel = new Wheel{Count = 4, Producer = "Debowe, quad type"};
        }

        public override void BuildDoor()
        {
            Vehicle.Doors = new Doors{Count = 0};
        }
    }

    public class Doors
    {
        public int Count { get; set; }
    }

    public class Wheel
    {
        public int Count { get; set; }
        public string Producer { get; set; }
    }

    public class Engine
    {
        public string Type { get; set; }
        public double Power { get; set; }
        public int CylindersCount { get; set; }
    }
}

Po uruchomieniu programu zobaczymy na ekranie następujący wynik:


poniedziałek, 19 grudnia 2011

Unity wstrzykiwanie w konstruktorze kolekcji obiektów danego typu

Ostatnio napotkałem na problem wstrzykiwania w konstruktorze kolekcji obiektów danego typu. Zobaczmy na prostą hierarchię klas:


Code:
public abstract  class A
    {
        
    }

    public class B : A
    {
        
    }

    public class C : A
    {
        
    }


Chcąc następnie wstrzyknąć taką hierarchię w konstruktorze klasy jako:


Code:
    public class D
    {
        private readonly IEnumerable<A> _resolvedClasses;

        D(IEnumerable<A> resolvedClasses)
        {
            _resolvedClasses = resolvedClasses;
        }
    }

Otrzymamy następujący błąd:


Aby poprawnie wykonać operację wstrzyknięcia wszystkich klas potomnych po A należy zarejestrować każdy z typów jako nazwany:


Code:
unityContainer.RegisterType<A, B>("B");
unityContainer.RegisterType<A, C>("C");

Następnie dopisujemy linijkę rejestrującą IEnumerable od naszego typu:


Code:
unityContainer.RegisterType<IEnumerable<A>>(new InjectionFactory(x => x.ResolveAll<A>()));

Po takim zabiegu możemy już bez przeszkód korzystać z kolekcji obiektów danego typu w konstruktorach.

piątek, 9 grudnia 2011

Nwigacja w Prism - zależności między widokami czyli INavigationAware oraz IRegionMemberLifetime w rolach głównych

Kolejny artykuł z serii View Based Navigation in Prism. Tym razem kilka przydatnych wiadomości nt. dwóch bardzo przydatnych interfejsów z których na pewno będziemy korzystać nie raz, podczas implementacji nawigacji w naszej aplikacji tworzonej w WPF/Silverlight przy użyciu Prism-a.

W ostatnim poście opisałem w jaki sposób w Prism implementujemy nawigację. Z nawigacją związane są bardzo silnie dwa interfejsy:
  • INavigationAware

Code:
  public interface INavigationAware
  {
    void OnNavigatedTo(NavigationContext navigationContext);

    bool IsNavigationTarget(NavigationContext navigationContext);

    void OnNavigatedFrom(NavigationContext navigationContext);
  }

Metody zawarte w tym interfejsie:
  • IsNavigationTarget - decyduje czy dany widok uczestniczy w procesie nawigacji (domyślnie jeżeli widok/VM nie implementuje tego interfejsu jest przyjmowane true)
  • OnNavigateFrom - metoda wywoływana jest przed przystąpieniem do zmiany widoku
  • OnNavigateTo - metoda wywoływana jest po zmianie widoku (załadowaniu nowego widoku)
Cały proces nawigacji można prześledzić na diagramie zawartym w dokumentacji Prisma:



Jak zobaczyć w praktyce działanie powyższych metod? Skorzystajmy z stworzonego przez nas ostatnio przykładu i dołóżmy dodatkowe elementy
W widokach A i B dokładamy właściwość CurrentTime oraz Counter oraz implementujemy interfejs INavigationAware:


Code:
using System;
using Microsoft.Practices.Prism.Regions;
using System.ComponentModel;

namespace ModuleA
{
    public class ModuleAViewModel : INotifyPropertyChanged, INavigationAware
    {
        public DateTime CurrentTime
        {
            get { return DateTime.Now; }
        }

        private int _counter;
        public int Counter
        {
            get { return _counter; }
            set
            {
                _counter = value;
                RaisePropertyChanged("Counter");
            }
        }

        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            Counter++;   
        }

        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return true;
        }

        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

W VM B kod wygląda tak samo więc nie będę go tutaj dwa razy kopiować.

W widokach dokładamy dodatkowe TextBlock-i do wyświetlenia dodatkowych informacji:


Code:
<UserControl x:Class="ModuleB.ModuleBView"
             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>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock Text="Moduł B" />
        <TextBlock Text="{Binding CurrentTime, Mode=OneWay}" Grid.Row="1" />
        <TextBox Text="{Binding Counter}" Grid.Row="2" />
    </Grid>
</UserControl>

Odpalmy aplikację i poprzemieszczajmy się pomiędzy widokami:


Efekt jest taki, że data pozostaje cały czas taka sama, a licznik w obu modułach jest inkrementowany.

Jest to dla nas o tyle ważne, że już teraz widzimy iż nasze widoki nie są niszczone. Przechodząc pomiędzy kolejnymi gałęziami naszego meni nawigacyjnego widoki pod spodem pozostają te same (te same instancje klasy).
Korzystanie z tej samem instancji pomiędzy kolejnymi przemieszczeniami się, jest w większości przypadków dla nas korzystną opcją, jednak nie zawsze. Niekiedy wymagamy aby widok był tworzony za każdym razem od nowa. Aby osiągnąć taki rezultat, należy skorzystać z innego interfejsu:
  • IRegionMemberLifetime
Interfejs ten ma następującą definicję:


Code:
  public interface IRegionMemberLifetime
  {
    bool KeepAlive { get; }
  }

Występuje tutaj jedna właściwość KeepAlive, która definiuje czy dany widok w regionie ma być tworzony za każdym razem od nowa, czy też ma zostać użyta już raz stworzona instancja.
Aby zobrazować zasadę działania, zaimplementujemy ten interfejs w module B:


Code:
using System;
using System.ComponentModel;
using Microsoft.Practices.Prism.Regions;

namespace ModuleB
{
    public class ModuleBViewModel : INotifyPropertyChanged, INavigationAware, IRegionMemberLifetime
    {
        public DateTime CurrentTime
        {
            get { return DateTime.Now; }
        }

        private int _counter;
        public int Counter
        {
            get { return _counter; }
            set
            {
                _counter = value;
                RaisePropertyChanged("Counter");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            Counter++;
        }

        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return true;
        }

        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
        }

        public bool KeepAlive
        {
            get { return false; }
        }
    }
}

Zwracamy wartość false więc nasz widok nie powinien być przetrzymywany w pamięci. Co to oznacza w przypadku rozważanego przykładu?
Zobaczmy efekt działania po kilkakrotnym przemieszczeniu się pomiędzy modułami A i B



Na pierwszym screenie widać Moduł A, na drugim B. Licznik pierwszego modułu ma wartość 9 a drugiego 1. Także czas na pierwszym screenie nie zmienił się pomimo upływu go podczas przemieszczania się między widokami jak to ma miejsce w module A.
Z tego wyciągamy wnioski, iż moduł A został raz zainicjowany i jego instancja była używana w dalszej nawigacji. Moduł B za każdym razem był inicjowany od nowa.



Omówione tutaj interfejsy z pewnością nie raz przydadzą się nam w naszych aplikacjach w przypadku nawigacji.

środa, 7 grudnia 2011

Prism View Navigation - Region Navigation

Region Navigation to jeden z rodzajów View Navigation. Mówiąc najprościej jest to podmiana widoku aktualnie wyświetlanego w danym regionie na inny widok.

W Prism mamy do dyspozycji bardzo prostą metodę RequestNavigate. Można ją wywołać na dwa sposoby:
  • za pośrednictwem klasy Region:

Code:
            IRegion myRegion = region;
            myRegion.RequestNavigate("Name_of_view");
Metoda w tym przypadku przyjmuje tylko nazwę widoku który ma zostać wyświetlony w regionie.
  •  za pośrednictwem klasy IRegionManager:

Code:
IRegionManager regionManager = rm;
region.Manager.RequestNavigate(RegionNames.Content, new Uri("View", UriKind.Relative));
W tym przypadku podajemy region w którym ma zostać wyświetlony widok jak i jego nazwę.

W tym przykładzie pokażę w jaki sposób skorzystać z drugiej wersji, czyli za pomocą RegionManagera - co wydaje mi się, daje większe możliwości i większą elastyczność.

Na początek struktura projektu który będziemy tworzyć:


Aplikacja będzie prezentowała się w następujący sposób:






Po kliknięciu w przycisk Moduł A zostanie wyświetlona zawartość modułu A, a w Module B Modułu B - prosta sprawa :).

Myślę że nie będę więcej wklejać kodu, który można znaleźć w poprzednich postach z cyklu Prism. Tak więc tylko najważniejsze części kodu oraz ich opis.

Główne okno aplikacji:


Code:
<Window x:Class="Sample_application_1.Shell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"
        xmlns:Core="clr-namespace:Core;assembly=Core" Title="Shell" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        
        <StackPanel prism:RegionManager.RegionName="{x:Static Core:RegionNames.ToolbarRegion}" 
                    Grid.Row="0" Orientation="Horizontal"/>
        
        <ContentControl prism:RegionManager.RegionName="{x:Static Core:RegionNames.ContentRegion}" 
                        Grid.Row="1"/>
    </Grid>
</Window>

Tak więc dwa regiony: pierwszy będzie zawierać przyciski nawigacji, drugi treść naszych widoków.

Jako że potrzebujemy wspólnego mechanizmu nawigacji stworzymy w bibliotece Core klasę ApplicationCommands:


Code:
using Microsoft.Practices.Prism.Commands;

namespace Core
{
    public class ApplicationCommands
    {
        public static CompositeCommand NavigationCommand = new CompositeCommand();
    }
}

Wejdźmy do modułu A:


Code:
using Core;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Unity;
using ModuleA.Navigation;

namespace ModuleA
{
    public class ModuleAModule : IModule
    {
        private readonly IRegionManager _regionManager;
        private readonly IUnityContainer _container;

        public ModuleAModule(IRegionManager regionManager, IUnityContainer container)
        {
            _regionManager = regionManager;
            _container = container;
        }

        public void Initialize()
        {
            _regionManager.RegisterViewWithRegion(RegionNames.ToolbarRegion, typeof (ButtonModuleA));
            _container.RegisterType<object, ModuleA>(typeof(ModuleA).FullName);
        }
    }
}

Ważniejsze aspekty tego co tutaj zostało wykonane:
  • w konstruktorze wstrzykujemy sobie potrzebne serwisy - kontener oraz region manager
  • standardowo za pomocą ViewDiscovery dodajemy nasz przycisk do Toolbara
  • Najważniejsze: rejestrujemy nasz widok A w kontenerze jako reprezentację typu object. Jest to bardzo ważne i jeżeli chcemy skorzystać z dobrodziejstwa nawigacji musimy w ten sposób zarejestrować nasz widok. 
Zobaczmy następnie jaki kod zawiera ViewModel:


Code:
using Core;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Regions;

namespace ModuleA
{
    public class ModuleAViewModel
    {
    }
}

Właściwie to nic nie ma tutaj ciekawego. Tak samo będzie wyglądać VM drugiego modułu.

Chcemy aby nasz model nawigacji nie był związany z konkretnym widokiem. Implementacja poruszania się po naszej aplikacji zostanie stworzona w VM Shell-a. Zobaczmy więc na kod który znajduje się w Shell-u:


Code:
using Core;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Regions;

namespace Sample_application_1
{
    public class ShellViewModel
    {
        private readonly IRegionManager _regionManager;
        public DelegateCommand<object> NavigationCommand { get; private set; } 

        public ShellViewModel(IRegionManager regionManager)
        {
            _regionManager = regionManager;
            NavigationCommand = new DelegateCommand<object>(navigationPath =>
                                                                {
                                                                    if (navigationPath != null)
                                                                    {
                                                                        regionManager.RequestNavigate(RegionNames.ContentRegion, navigationPath.ToString());
                                                                    }
                                                                });
            ApplicationCommands.NavigationCommand.RegisterCommand(NavigationCommand);
        }
    }
}


Kolejno idąc tokiem kodu:
  • tworzymy deklarację komendy NavigateCommand która ma za zadanie przenieść nas do odpowiedniego modułu
  • W konstruktorze tworzymy ją i za pomocą RequestNavigate przenosimy się do odpowiedniego widoku.
  • Jednym z parametrów metody jest navigationPath, która zostanie przysłana jako parametr komendy a decyduje ona o adresie widoku do którego chcemy się przenieść
  • Rejestrujemy naszą komendę w CompositeCommand NavigationCommand

Widok przycisku Modułu A:


Code:
<UserControl x:Class="ModuleA.Navigation.ButtonModuleA"
             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" 
             xmlns:Core="clr-namespace:Core;assembly=Core" 
             xmlns:ModuleA="clr-namespace:ModuleA">
    <Grid>
        <Button Content="Moduł A" Command="{x:Static Core:ApplicationCommands.NavigationCommand}" 
                CommandParameter="{x:Type ModuleA:ModuleA}"/>
    </Grid>
</UserControl>

Bindujemy się do naszej komendy, a następnie jako parametr przekazujemy nazwę widoku, pobraną z enuma:


Code:
namespace ModuleA
{
    public enum ViewType
    {
        ModuleA
    }
}


Teraz możemy włączyć naszą aplikację i przemieszczać się pomiędzy widokami.