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. 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 _XAML_u.
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é.