A felhőalapú technológiákra váltás egy izgalmas kihívás - ha tudod miként kezdj neki! Ebben a cikkben szeretném megosztani...
MVVM Light Toolkit – 2. rész
Egy több képernyőből álló alkalmazás esetében nagyot profitálhatunk az MVVM-ből, ugyanis az MVVM Light Toolkit – és maga az MVVM szemlélet – az adatkötésekkel nagyban megkönnyíti a képernyők, vagy éppen lapok közötti adatmegosztást. A navigáció során, paramétereken keresztül történő kommunikáció helyett nézzünk néhány kifinomultabb eszközt a probléma megoldására!
Az alábbi bejegyzés elolvasása előtt javasolt az MVVM Light Toolkit-ről szóló sorozat első részének áttekintése, ahol az alapvető működés kerül bemutatásra.
ViewModelLocator – SimpleIoc
Az MVVM Light Toolkit önmagába foglal egy SimpleIoc nevezetű megoldást. A SimpleIoc egy Inversion of Control (IoC) konténer, melyet tipikusan a következőkre használhatunk:
- Osztályokat regisztrálhatunk a konténeren belül
- Alkalmazásunk bármely pontjáról lekérhetünk egy példányt a regisztrált osztályból
- A Singleton pattern-hez hasonlóan úgynevezett egyke példányokat kérhetünk le – a gyakorlatban ez annyit tesz, hogy egyetlen közös példányt használunk az alkalmazás életciklusa során, a példányban tárolt adatot az alkalmazás bármely részén elérjük
- Akár több példányt is tárolhatunk egyszerre, mindegyiket egy egyéni kulccsal eltárolva, így könnyen váltogathatunk közöttük
Az MVVM Light Toolkit a SimpleIoc-t tipikusan a ViewModel osztályok regisztrálására és példányosítására használja. Ezzel nem csak a ViewModel-ek közötti keresztlekérdezéseket tudjuk egyszerűbbé tenni, de a dizájn időben – Visual Studio-ban, vagy Blend-ben – történő adatmegjelenítést is rém egyszerűen megvalósíthatjuk, melyet a cikk végén letölthető példakódban be is mutatok.
Lássuk miként is lehet egy ViewModelLocator-t implementálni alkalmazásunkban – egyébként az implementációs lépéseket áttekintve rá fogunk jönni, hogy a ViewModelLocator nem más mint egy osztály, aminek az adattagjai lesznek a ViewModel osztályok egy-egy példányai, ezeket fogjuk átadni a felület DataContext paraméterének.
Implementáció
ViewModelLocator.cs:
[ecko_code_highlight language=”c#”]public class ViewModelLocator
{
static ViewModelLocator
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel MainViewModel { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } }
}[/ecko_code_highlight]
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
Első lépésként ezt érdemes megtennünk. Hogy miért? Egyszerűen nem a SimpleIoc az egyetlen provider amit használhatunk Írhatunk egy sajátot is akár, az IServiceLocator interfész implementálásával, valamint egy egyéni GetInstance függvény deklarálásával. Ha az alkalmazásunkban mindenhol a ServiceLocator.Current.GetInstance hívással kérdezünk le új példányt az objektumokból, akkor egy váltásnál megspóroljuk az összes hívás átírását, csak ezt az egy sort kell majd módosítanunk.
SimpleIoc.Default.Register<MainViewModel>();
Minden osztályt regisztrálnunk kell a SimpleIoc-n belül, mielőtt példányokat kérnénk le belőlük. Ezt kivétel nélkül minden esetben meg kell tennünk.
public MainViewModel MainViewModel { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } }
Végezetül pedig lekérünk egy új példányt a ViewModel-ből. Ha még nincs példány, akkor a SimpleIoc a háttérben automatán példányosítja az osztályt. A továbbiakban ugyanezt a példányt kapjuk meg mindig – kivéve ha másképp rendelkezünk, de erről még később írok.
App.xaml:
Az App.xaml fájlban meghatározhatunk úgynevezett erőforrásokat (Resource). Ezeknek több fajtája létezik, egyikük a kulcsokhoz társított osztálypéldányok – hasonlóképp, mint az IValueConverter osztályok esetében. Az App.xaml fájlban meghatározott erőforrásokat az alkalmazáson belül bárhonnan elérjük – legalábbis a projekten belül -, így a ViewModelLocator példányunkat itt hozzuk létre:
[ecko_code_highlight language=”html”]<Application x:Class=”MvvmCrossCommuncation.App”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:MvvmCrossCommuncation”
xmlns:vm=”clr-namespace:MvvmCrossCommuncation.Core.ViewModels;assembly=MvvmCrossCommuncation.Core”
StartupUri=”MainWindow.xaml”>
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key=”ViewModelLocator” />
</ResourceDictionary>
</Application.Resources>
</Application>[/ecko_code_highlight]
xmlns:vm=”clr-namespace:MvvmCrossCommuncation.Core.ViewModels;assembly=MvvmCrossCommuncation.Core”
Először a xaml-ben megszokott módon meg kell adnunk egy új névteret, ahol ViewModelLocator osztályunkat létrehoztuk – általában én a ViewModel fájlok mellé szoktam helyezni.
<vm:ViewModelLocator x:Key=”ViewModelLocator” />
Ezt követően a megadott névtérből a ViewModelLocator egy új példányát létrehozzuk, majd megadunk neki egy tetszőleges kulcsot az x:Key paraméteren keresztül. A kulcsra a következő lépésben lesz szükségünk.
Az ilyen formában történő deklarációra gondolhatunk úgy, mintha a C# kódban a ViewModelLocator ViewModelLocator = new ViewModelLocator(); kódot írnánk be, tehát egy új példányt hozunk létre az osztályból.
DataContext
Mint azt már megtanultuk, az adatkötések működésre bírásához szükségünk van a DataContext megadására – ezzel adjuk meg, hogy a bekötötött adattagok pontosan hol is találhatók meg, milyen objektumon belül. Ezt azonban a korábbiaktól eltérően nem a C# kódban fogjuk megadni, hanem a felület XAML kódjában:
[ecko_code_highlight language=”html”]DataContext=”{Binding MainViewModel, Source={StaticResource ViewModelLocator}}”[/ecko_code_highlight]
Jól látjuk, hogy a DataContext itt szintén egy adatkötésből származik, de honnan is érkezik ez az adat? A MainViewModel maga az adattag neve, melyet a ViewModelLocator osztályunkban meghatároztunk, tehát ide minden esetben az adott ViewModel adattagjának a neve fog kerülni. Van azonban egy Source paraméterünk is, melyben a ViewModelLocator-t adtuk meg az adattag forrásának. Ezt azért tehetjük meg, mert az App.xaml fájlban felvettünk egy erőforrást ezzel a kulcs értékkel, ami a ViewModelLocator osztály egy példányát takarja.
A továbbiakban az adatkötések a korábban megszokott módon elvégezhetők, készen is vagyunk a ViewModelLocator bekötésével!
Jó ha tudod
Új példány lekérdezése, gyorsítótárazás nélkül
Lehetőség van minden alkalommal új példány lekérdezésére egy adott osztályból, ezt specifikusan a SimpleIoc tudja megvalósítani. A következő kóddal tehető meg a gyorsítótárazás nélküli példányosítás:
[ecko_code_highlight language=”c#”]return (ServiceLocator.Current as SimpleIoc).GetInstanceWithoutCaching<MainViewModel>();[/ecko_code_highlight]
Statikus adatok tervezési időben
A DataContext XAML-ben történő meghatározásának van egy nagy előnye: már dizájn időben is tudjuk, hogy honnan fogjuk megjeleníteni az adatokat. Ennek velejárója pedig, hogy a ViewModel-be kézzel felvett adatok mind-mind megjelennek a felületen már a Visual Studio, illetve Blend környezetben is. Az MVVM Light Toolkit ezt egy fokkal tovább is gondolta, a ViewModelBase.IsInDesignModeStatic változóval megtudhatjuk, hogy az előbbi két környezet egyikében vagyunk-e és feltölthetjük sablon adatokkal az alkalmazást.
[ecko_code_highlight language=”c#”]
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<CrossCommunicationViewModel>();
SimpleIoc.Default.Register<ScenariosViewModel>();
SimpleIoc.Default.Register<MessengerScenarioViewModel>();
if (ViewModelBase.IsInDesignModeStatic)
{
var ViewModel = new ViewModelLocator().MessengerScenarioViewModel;
ViewModel.Messages.Add(„See you in Blend or Visual Studio!”);
ViewModel.Messages.Add(„We have added this from code-behind.”);
ViewModel.Messages.Add(„This is a test message”);
}
}
[/ecko_code_highlight]
Tartsuk szem előtt, hogy a felületen csak akkor fognak megjelenni az adatok, ha újraforgatjuk a kódot!
ViewModel-ek között kommunikáció
Mivel a regisztrált osztályok példányai az alkalmazás teljes életciklusa alatt bárhonnan lekérdezhetővé váltak, így egyszerű dolgunk van, ha a ViewModel-ek között szeretnénk kommunikációt végrehajtani. Ezt megtehetjük C# és XAML kódból egyaránt. A következő példában egy adott ViewModel gyűjteményében az elemek számát kérdezzük le:
C#:
[ecko_code_highlight language=”c#”]public void Sample()
{
var messagesCount = new ViewModelLocator().MessengerScenarioViewModel.Messages.Count;
txtMessageCount.Text = messagesCount;
}[/ecko_code_highlight]
XAML:
[ecko_code_highlight language=”html”]<TextBlock x:Name=”txtMessageCount” Text=”{Binding MessengerScenarioViewModel.Messages.Count, Source={StaticResource ViewModelLocator}}” />[/ecko_code_highlight]
XAML DataContext elérése kódból
Ha szeretnénk a XAML-ben megadott DataContext-et a felület mögötti kódból elérni, használjuk a következő kódot:
[ecko_code_highlight language=”c#”]var ViewModel = (this.DataContext as TIPUS);[/ecko_code_highlight]
Ügyeljünk a megfelelő típuskonverzióra, valamint var helyett is használjuk az adott adattípust.
Messenger
Nem, most nem valamiféle csevegőszolgáltatásról fogunk beszélgetni! Az MVVM Light Toolkit csomagjában helyet kapott egy Messenger osztály, mellyel alkalmazásunk moduljai közötti kommunikációt fogunk megvalósítani.
A Messenger ereje abban rejlik, hogy úgynevezett csatornákra iratkozhatunk fel, melyeken meghatározott adattípusú üzeneteket várunk és ezeket felfolgozzuk valamilyen logikával. Ha az alkalmazás egy pontjáról üzenetet küldünk ezen a csatornán, a kezelő megkapja és a megadott logikával feldolgozza azt.
[ecko_code_highlight language=”c#”]Messenger.Default.Register<T>(
object recipient,
object token,
bool receiveDerivedMessagesToo,
Action<T> action
);[/ecko_code_highlight]
recipient: Az üzenet fogadója – egyszerűen this értéket írjuk be.
token (opcionális): A token paramétert felfoghatjuk egy úgynevezett kategóriaként, vagy a csatorna neveként. Ha a paramétert nem adjuk meg, akkor minden T típusú üzenetet fogadunk, ellenkező esetben csak az ugyanezzel a tokennel elküldött üzeneteket.
receiveDerivedMessagesToo (opcionális): A paraméter true értékre állításával a T típusból származtatott üzenettípusokat is fogadni fogjuk.
action: Azon függvény, mely fel fogja dolgozni a kapott üzenetet. Bemeneti paramétere egy T típusú objektum.
[ecko_code_highlight language=”c#”]Messenger.Default.Unregister<T>(
object recipient,
object token,
Action<T> action
);[/ecko_code_highlight]
A paraméterek jelentése ugyanaz, mint az előző esetben. A különbség, hogy ez a művelet megszünteti a regisztrált üzenetkezelőt.
[ecko_code_highlight language=”c#”]Messenger.Default.Send<T>(
T message,
object token
);[/ecko_code_highlight]
Elküldi T üzenetet – opcionálisan megadható token-nel. Az üzenet minden esetben elküldésre kerül, akár van fogadó fél, akár nincs.
[ecko_contrast]
A Messenger hátrányai
A Messenger kétségtelenül egy remek megoldásnak tűnik, azonban nem árt tisztában lenni ennek a megoldásnak a hátrányaival. Egy komplex alkalmazás esetében, ahol már rengeteg csatornán keresztül küldünk üzenetet, rendkívül nehézkessé válhat a hibák felderítése, ugyanis nem tudhatjuk, hogy egy adott üzenet az app mely pontjáról érkezett.
Hasonlóképpen egy üzenet elküldésekor nem kapunk visszajelzést arra vonatkozóan, hogy egy adott üzenettípusra van-e feliratkozott fogadó fél. Lehetséges, hogy a küldött üzenetek nem is érnek célba, mert nem regisztráltuk a fogadó felet, vagy valami hiba történt annak működése során és az a szál már régen elszállt.
Ez persze nem jelenti azt, hogy egyáltalán ne használjuk, de legyünk tisztában a megoldás korlátaival és eszerint határozzuk meg annak felhasználási köreit.[/ecko_contrast]
GitHub
A bejegyzéshez tartozó példakód elérhető a GitHub kódtáramban, érdemes megnézni a gyorsabb megértés érdekében!
Hiányolsz valamit a cikkből? Kérdésed van? Szólj hozzá a témához!