bhawk.hu – Magyar fejlesztői blog

bhawk.hu – Magyar fejlesztői blog


2019. március
h k s c p s v
« jún    
 123
45678910
11121314151617
18192021222324
25262728293031

Kategória


MVVM Light Toolkit – 2. rész

Gulyás GáborGulyás Gábor

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:

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:

public class ViewModelLocator
{
    static ViewModelLocator
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

        SimpleIoc.Default.Register<MainViewModel>();
    }

    public MainViewModel MainViewModel { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } }
}

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:

<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>

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:

DataContext="{Binding MainViewModel, Source={StaticResource ViewModelLocator}}"

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:

return (ServiceLocator.Current as SimpleIoc).GetInstanceWithoutCaching<MainViewModel>();

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.



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");
    }
}

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#:

public void Sample()
{
    var messagesCount = new ViewModelLocator().MessengerScenarioViewModel.Messages.Count;
    txtMessageCount.Text = messagesCount;
}

XAML:

<TextBlock x:Name="txtMessageCount" Text="{Binding MessengerScenarioViewModel.Messages.Count, Source={StaticResource ViewModelLocator}}" />

 

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:

var ViewModel = (this.DataContext as TIPUS);

Ü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.

Messenger.Default.Register<T>(
    object recipient,
    object token,
    bool receiveDerivedMessagesToo,
    Action<T> action
);

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.

Messenger.Default.Unregister<T>(
    object recipient,
    object token,
    Action<T> action
);

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.

Messenger.Default.Send<T>(
    T message,
    object token
);

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.

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.

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!

Minden ami Microsoft technológia! Több mint 8 éve foglalkozom programozással, ez idő alatt pedig rengeteg nyelvet elsajátítottam, leginkább a C#-ot kedvelem! Jelenleg szoftverfejlesztőként dolgozom!