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

Coproject – demo RIA aplikace krok za krokem, díl 4.

Napsáno pro Silverlight od Augustin Šulc [31.01.2011]

V tomto díle se podíváme trochu více na Caliburn.Micro a připravíme si hlavní moduly budoucí aplikace.

Pro snazší pochopení budu opět některé věci z C.M zjednodušovat. Pokud by vás však zajímaly detailní informace, podívejte se sem.

ViewModel

Jak jsem již naznačil v minulém díle, celá struktura aplikace a jednotlivé její obrazovky se v C.M definují přes strukturu ViewModelů. C.M se už poté sám postará o provázání s Views a zobrazení uživateli. Toto rozdělení umožňuje přehledně udržovat i komplexní aplikace a zároveň zjednodušuje spolupráci mezi designerem a programátorem.

Screen

Základním předkem pro všechny ViewModely je třída Screen. Není sice nutné dědit od této třídy, vlastní ViewModel si můžete napsat, jak chcete, ovšem proč si neušetřit práci? Třída Screen již implementuje INotifyPropertyChanged a také obsahuje virtuální funkce užitečné pro hierarchie jednotlivých obrazovek:

OnInitialize
OnActivate
OnDeactivate
TryClose

Conductor

V případě, že plánujete, že váš view model bude zároveň fungovat jako rodič/nadřazený view model nějakých jiných view modelů využijete jistě třídu Conductor. Tak k funkcím ze Screen přidává další právě pro správu vnořených view modelů:

ActivateItem
DeactivateItem
GetChildren

Conductor.Collection.OneActive

Nejtypičtějším případem Conductoru je ten, který sice obsahuje několik vnořených view modelů, vždy je však aktivní pouze jeden z nich (např. záložky v okně prohlížeče). Tady nám C.M přináší již připravenou implementaci v podobě Conductor.Collection.OneActive. Tato třída má např. property Items a také přetížené funkce pro přepínání mezi aktuálním vnořeným prvkem.

V Coprojectu budeme používat hlavně tuto třídu, jelikož shell bude obsahovat jednotlivé moduly, nebo například modul To-do bude obsahovat kolekci právě otevřených detailů ToDoItem.

Pokud se chcete dovědět více o view modelech v C.M, odkáži vás na tento článek.

Moduly

Pojďme tedy využít tyto třídy v Coprojectu. Nejprve si připravíme moduly aplikace. Jistě si vzpomenete z prvního dílu, že naše aplikace bude mít moduly Home, Messages, To do a Milestones. Nejprve si opět vytvoříme interface, který použijeme k označení modulů. Vytvořte interface ViewModels.Interfaces.IModule:

namespace Coproject.ViewModels.Interfaces
{
	public interface IModule
	{
		string Description { get; }
	}
}

Ve složce ViewModels potom vytvoříme první modul – třídu HomeViewModel:

[Export(typeof(IModule))]
public class HomeViewModel : Screen, IModule
{
	public string Description { get; private set; }

	public HomeViewModel()
	{
		DisplayName = "Home";
		Description = "Project overview & activity"; 
	}
}

Jak vidíte, nic zvláštního – nastavíme popisky a upozorníme MEF, že jde o modul aplikace. Property DisplayName je již součástí třídy Screen, takže není nutno ji zde implementovat jako Description.

Vytvořte i další moduly podle tabulky. Nezapomeňte na atribut [Export]!

[název třídy] - [DisplayName] - [Description]
MessagesViewModel - Messages - All messages
ToDoListsViewModel - To Do - To-do lists
MilestonesViewModel - Milestones - string.Format("Milestones (today is {0:D})", DateTime.Today)

pokud jste vše udělali správně, Solution Explorer by měl vypadat takto:

Teď už jen potřebujeme tyto moduly nějak dostat do shellu.

Upravte předka třídy ShellViewModel takto:

public class ShellViewModel : Conductor<IModule>.Collection.OneActive, IShell

A přidejte následující konstruktor:

[ImportingConstructor]
public ShellViewModel([ImportMany]IEnumerable<IModule> modules)
{
	Items.AddRange(modules);
}

To je vše – MEF teď bude vědět, že má do konstruktoru dodat všechny implementace IModule, které má k dispozici (tj. všechny moduly). Dejte do konstruktoru breakpoint a spusťte debugování aplikace – uvidíte, že parametr modules opravdu obsahuje všechny moduly. Výhoda tohoto přístupu je zřejmá – nikde jsme nemuseli ručně nastavovat jaké moduly se mají zobrazovat. Kdybychom chtěli do aplikace přidat nový modul, prostě ho jen označíme jako [Export] a on se objeví v menu aplikace. Pokud byste se chtěli dovědět více o importování v MEFu, doporučuji tento článek.

Debugování Silverlight aplikací

Debugování Silvelright aplikací není vždy jednoduché, pokusím se vám ukázat postup, který mi bezpečně funguje. Základní věcí je pochopit, že SL aplikace se zkládají ze dvou částí: webového serveru a klientské části. Obvykle, když stisknete ve Visual Studiu F5 (Start Debugging), tak se spustí pouze debugování serverové části (breakpointy v klientské části zůstanou zašedlé).

1. Než budete schopni aplikaci debugovat, musíte ji nejprve spustit. K tomu je nejlepší použít Start Without Debugging:

image

To spustí jak serverovou část, tak i klientskou (v prohlížeči). Já si obvykle prohlížeč zavřu a dané URL otevři v Internet Exploreru, jelikož ho na nic jiného nepoužívám (snadněji se pak hledá správný proces, vysvětlím za okamžik). Tuto akci už povětšinou nebudete muset opakovat, jelikož server poběží neustále a okno prohlížeče také nebudete zavírat.

2. Před každým debugováním pak musíte spustit build aplikace, aby se spustila poslední verze.

3. Ve Visual Studiu použijte Attach to Process (CTRL+ALT+P):

image

4. Pokud chcete debugovat server, připojte se k procesu WebDev.WebServer40.EXE (stačí stisknout w, e, b a automaticky se vybere správný řádek):

Pokud chcete debugovat klienta, vyberte proces svého prohlížeče (ve sloupci Type bude mít napsáno Silverlight). V mém případě to je iexplorer.exe:

Pokud tedy chcete debugovat klienta, stačí stisknout:

CTRL+SHIFT+B (Build Solution)
CTRL+ALT+P (Attach to Process)
i, e, ENTER (vybrat správný proces)

To je vše. Nezapomeňte build na začátku a v prohlížeči potom stisknout F5 (refresh), aby se načetla poslední verze aplikace.

Menu

Moduly už tedy máme v shellu načteny, nikde je však zatím nezobrazujeme. K tomu budeme muset upravit ShellView. Otevřete tedy ShellView.xaml a přidejte následující prvek hned pod TextBlock s textem “Coproject”:

<ListBox x:Name="Items" Style="{StaticResource NavigationMenuStyle}">
	<ListBox.ItemTemplate>
		<DataTemplate>
			<TextBlock Text="{Binding DisplayName}" />
		</DataTemplate>
	</ListBox.ItemTemplate>
	<ListBox.ItemsPanel>
		<ItemsPanelTemplate>
			<StackPanel Orientation="Horizontal" />
		</ItemsPanelTemplate>
	</ListBox.ItemsPanel>
</ListBox>

Tip: když stisknete CTRL+K,D tak vám Visual Studio zformátuje celý dokument. Nemusíte tedy ručně odsazovat jednotlivé řádky, které jste vložili ze schránky.

Vložili jsme sice jen ListBox, který by měl zobrazovat prvky, které mu předložíme, ale pokud aplikaci spustíte, uvidíte, že moduly jsou již na svém místě:

Opět jsme totiž narazili na konvence Caliburn.Micro. Jelikož se ListBox jmenuje “Items”, tak pokud C.M objeví ve ViewModelu property se stejným názvem automaticky prvek na tuto property naváže. Jelikož je shell zároveň conductor s kolekcí jednotlivých modulů, property Items již obsahuje. Pokud bychom chtěli, můžeme samozřejmě navázat vlastnost ItemsSource zmíněného ListBoxu ručně pomocí {Binding Items} a C.M by nám toto nastavení nepřepsal. Konvence tedy můžete využívat, nic vás k tomu však nenutí a pokud chcete, můžete vše udělat “ručně”. Z výše zmíněného je také jasné, že DataContext celého view je automaticky nastaven na příslušný view model.

Vybraný modul

Pod nový ListBox přidejte do ShellView ještě toto:

<TextBlock x:Name="ActiveItem_Description" Style="{StaticResource CurrentPageTitleStyle}" />
<ContentControl x:Name="ActiveItem" Style="{StaticResource MainContentStyle}" />

Jak asi sami uhádnete, první prvek zobrazí popisek aktuálního modulu a druhý obsah daného modulu. Když aplikaci opět spustíte, uvidíte toto:

Zkuste přepínat v menu mezi moduly a uvidíte, že se změna ihned projevuje. To je způsobeno další konvencí C.M. Pokud nějaký ovládací prvek podporuje vybírání položek (vlastnost SelectedItem), C.M převede jeho jméno do jednotného čísla a pokusí se přidat Active, Selected nebo Current před toto jméno. Pokud pak najde takovou property na view modelu, automaticky se na ni oboustranně (TwoWay) naváže. Třída Conductor má property ActiveItem.

Tyto kovence přes jméno prvku mají jednu nevýhodu – binding zatím nefunguje ve fázi návrhu. Pokud tedy chcete používat nějaká testovací data už v designeru, musíte psát binding ručně.

View location

Jistě jste si všimli, že poté, co vyberete modul se na místě, kam jsme umístili ContentControl pro obsah modulu, zobrazí hláška “'Coproject,Views.MessagesView not found’”. Jde o chybovou hlášku C.M o tom, že nemohl najít view pro daný view model. Konvence pro hledání views je v C.M následující: dojde k odstranění slova “Model” z plného názvu view modelu. Tuto logiku můžete samozřejmě upravit podle svých představ, většinou to však nebývá nutné.

Ačkoliv se to možná nezdá, jde o velice flexibilní postup podporující jednoduchou strukturu, jako máme například v Coprojectu:
/ViewModels/MessagesViewModel + /Views/MessagesView

Bude však stejně dobře fungovat u složitějších projektů se strukturou rozdělenou po modulech:
/MessagesModule/ViewModels/ListViewModel + /MessagesModule/Views/ListView
případně
/ViewModels/MessagesModule/ListViewModel + /Views/MessagesModule/ListView

Zdrojové kódy

Na webu Coprojectu si můžete stáhnout zdrojové kódy, případně diskutovat a navrhovat nové funkce. Ozvěte se!

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