MVVM a design time
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