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
Gulyás GáborEgy 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!