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.

Brak komentarzy:

Prześlij komentarz