XAML.cz Magazín moderních technologií založených na XAML

MVVM a design time

Napsáno pro WPF od Jarda Jirava  [10.09.2010]

Velice často se budete jako vývojáři a designéři dostávat do situací, kdy budete chtít navrhnout uživatelské rozhraní a při tomto navrhování byste rádi měli k dispozici vzorová data.

Existuje mnoho způsobů, jak tohoto chování dosáhnout, ale při použití Model-View-ViewModel návrhového vzoru jsme k tomuto chování přeci jen o krůček blíže. Samozřejmě existuje hned několik frameworků, které podporují zároveň MVVM a dávají k dispozici data pro design time. Já bych však rád v tomto článku ukázal tu nejjednodušší možnost, jak dostat vzorová data do aplikace. K této ukázce pak využiji předchozího příkladu s DataGridem a maličko jej pozměním tak, abych využil MVVM vzor a zároveň si mohl připravit vzorová data.

Vzorová aplikace

Vycházet budu z aplikace, kterou jsem představil již v minulém článku, takže jsou již vytvořeny potřebné třídy modelu a to TrackItem a tuto třídu též dokáži naplnit daty v době runtime z databáze.

Příprava ViewModelu

Model je tedy definován a mohu se vrhnout na ViewModel, který bude hrát nemalou úlohu ve využití vzoru pro design time. ViewModel může zůstat velice jednoduchý a zprostředkovat kolekci instancí třídy TrackItem  pro View. Toho docílím tím, že vypublikuji vlastnost s názvem TrackItems, která je typu ObservableCollection<T>. ViewModel dále dle zvyklostí implementuje rozhraní INotifyPropertyChanged, tak aby View bylo vždy informováno při změně hodnoty některé z vlastností.

Výsledná třída ViewModelu tak může vypadat následovně:

public class TrackViewModel : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    private Tracks _trackItems;

    public TrackViewModel() {
        TrackItems = new Tracks();
    }

    public TrackViewModel(ITrackService service)
        : this() {
        var items = service.GetTracks();
        foreach (var item in items) {
            TrackItems.Add(item);
        }
    }

    protected virtual void OnPropertyChanged(string propertyName) {
        var handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public Tracks TrackItems {
        get {
            return _trackItems;
        }
        set {
            _trackItems = value;
            OnPropertyChanged("TrackItems");
        }
    }
}

Všimněte si, že jsem implementoval dva konstruktory, jeden bezparametrický, který vytváří všechny public vlastnosti a druhý parametrický, který přebírá servisní třídu, jenž zprostředkovává data.

Dříve než se pustím do samotné deklarace View, asi by bylo vhodné naznačit, jak dojde k výslednému propojení, byť se může na první pohled zdát, že je to příliš brzy. Jelikož již mám vytvořen ViewModel a z minula vlastně z velké části připravené i View je třeba si otevřít soubor App.xaml.cs a provést propojení mezi View a ViewModelem. Zde si přepíšeme metodu OnStartup a vytvoříme potřebné instance.

public partial class App : Application {
    protected override void OnStartup(StartupEventArgs e) {
        base.OnStartup(e);
        var service = new TrackService();
        var view = new MainWindow();
        var viewModel = new TrackViewModel(service);
        view.DataContext = viewModel;
        view.Show();
    }
}

Myslím, že stačí jen velice stručně okomentovat, že nejdříve dojde k vytvoření servisní třídy, která poskytuje data, následně vytvořím hlavní okno a poté instanci ViewModelu, kde do konstruktoru předám vytvořenou instanci servisní třídy.

Na předposledním řádku pak přiřadím do vlastnosti DataContext vytvořený ViewModel a naposledním řádku samotné View zobrazím.

Tímto způsobem jsem zajistil správné chování aplikace v době runtime.

Deklarace View

Dostávám se tak k samotné deklaraci View, které nejdřív představím v té podobě, jakou by ji navrhl asi každý vývojář tak, aby si zajistil správnou funkcionalitu v době běhu aplikace.

<Window.Resources>
    <CollectionViewSource x:Key="TracksSource" Source="{Binding TrackItems}">
    </CollectionViewSource>
</Window.Resources>
<DockPanel>
    <DataGrid x:Name="grid" ItemsSource="{Binding Source={StaticResource TracksSource}}" CanUserAddRows="False"
            AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding TrackName}" />
            <DataGridTextColumn Header="Composer" Binding="{Binding TrackComposer}" />
            <DataGridTextColumn Header="Album" Binding="{Binding TrackAlbum}" />
            <DataGridTextColumn Header="Artist" Binding="{Binding TrackArtist}" />
            <DataGridTextColumn Header="Genre" Binding="{Binding TrackGenre}" />
        </DataGrid.Columns>
    </DataGrid>
</DockPanel>

Jak vidíte, tak v deklaraci se změnilo jen velice málo. Zásadní změnu je možné vidět v naplnění vlastnosti Source u CollectionViewSource, kde místo statického zdroje dat, jenž odkazoval na kolekci dat je nyní použit binding na vlastnost ViewModelu. Ostatní zůstává beze změny a já tak mohu přistoupit k hlavnímu bodu dnešního článku, jak zajistit design time data.

Design time data

Design time data můžeme využít právě v okamžiku, kdy budeme designovat jednotlivé prvky a deklarovat jejich vzhled a rozmístění. Zároveň nám dají lepší představu o tom, jak celé UI bude ve výsledku vypadat. A samozřejmě to nemusí znamenat pomoc jen pro nás vývojáře, ale také pro designéra, kterému tato data dají jistý základ a on pak bude moci připravit pro koncového uživatele lepší uživatelský prožitek z používání aplikace.

Data pro design time samozřejmě můžeme připravit ve ViewModelu, a mnoho MVVM frameworků k tomu využívá různých design time providerů, která poskytují data. Jak jsem však již na začátku uvedl, chci ukázat jednoduchý způsob a tak data budou deklarována v samotném XAMLu.

Stejně jako v době běhu aplikace se naplní vlastnost DataContext i v době návrhu naplníme tuto vlastnost a to deklarativně. A samozřejmě k tomu využijeme i stejných tříd, které budou použity v runtime.

Nejdříve si připravíme potřebné xml namespace, abychom mohli využít nejen třídy ViewModelu, ale také Modelu samotného, v mém případě se jedná o doplnění těchto řádků do root elementu Window

xmlns:vm="clr-namespace:MVVMDesign.ViewModels"
xmlns:m="clr-namespace:MVVMDesign.Models"
Následně pak na stejné úrovni, jako jsem deklaroval element Window.Resources doplním i následující deklaraci
<Window.DataContext>
    <vm:TrackViewModel>
        <vm:TrackViewModel.TrackItems>
            <m:TrackItem TrackAlbum="Audioslave" TrackName="Exploder" TrackLength="654" TrackGenre="Rock" TrackComposer="Chris Corell" TrackArtist="1" />
            <m:TrackItem TrackAlbum="Audioslave" TrackName="Hypnotize" TrackLength="456" TrackGenre="Rock"
                    TrackComposer="Chris Corell" TrackArtist="1" />
            <m:TrackItem TrackAlbum="Audioslave" TrackName="What you are" TrackLength="356" TrackGenre="Rock"
                    TrackComposer="Chris Corell" TrackArtist="1" />
            <m:TrackItem TrackAlbum="Accept" TrackName="Balls to the Wall" TrackLength="754" TrackGenre="Rock" TrackComposer="F. Babes" TrackArtist="1" />
            <m:TrackItem TrackAlbum="Accept" TrackName="Fast As a Shark" TrackLength="521" TrackGenre="Rock"
                    TrackComposer="F. Babes" TrackArtist="1" />
        </vm:TrackViewModel.TrackItems>
    </vm:TrackViewModel>
</Window.DataContext>

Nyní již stačí jen celý soubor uložit a výsledek si můžete zkontrolovat v Design okně Visual Studia 2010 nebo taktéž v nástroji Expression Blend. Máme hotovo, jak vidíte velice jednoduché a přímočaré deklarování jednotlivých součástí dat.

Tímto způsobem může vývojář připravit vzorová data pro designéra a ten si data může dále rozšiřovat a upravovat dle potřeb a nároků na funkčnost a schopnosti aplikace.

Závěrem

Tímto článkem jsem chtěl představit možnosti, které nám Visual Studio a Expression Blend nabízejí již v době návrhu a to v kombinaci s použitím vzoru Model-View-ViewModel pro efektivní a elegantní vytváření výsledného vzhledu aplikace. Přeci jen spouštění aplikace a nutnost čekat na natažení dat jen proto, abychom mohli překontrolovat, jak se projeví některé změny do stylu nebo šablony (template) může být časově zdlouhavé.

Komentáře

ukládám komentář, vyčkejte prosím..
  1. Buďte první, kdo napíše komentář.

@xamlcz

  • RT @jvanrhyn: XAML, It's a bit like olives. Takes a while to get used to. But once you're used to it. It is actually pretty good. <3 XAML
  • RT @moser_christian: WPF Inspector 0.9.7 is released. It supports .NET 3.5 and 4.0 The project is now open source and available on CodeP ...
  • Jeff Handley oznámil vydání WCF RIA Services v.1.0 SP1 RTM http://bit.ly/gOgckn ke stažení na http://bit.ly/gVAXdK
  • jedna výzva pro Brno. Byl někdo z vás na přednášce o RIA v MS Akvárku? Dejte o sobě vědět. Děkuji
  • také jste uvažovali o tom, že zkusíte na projekt použít Caliburn Micro nebo naopak Prism 4? A co tak obojí, šlo by to nebo ne? Již brzy