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

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

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

V tomto díle načteme data z databáze a seznámíme se s významnou funkcí Caliburn.Micro – coroutines. Pokud se vám nechce programovat, ale rádi byste sledovali celý seriál spolu se zdrojovými kódy, podívejte se na web Coprojectu na Codeplexu.

ToDoListsView

Do složky “Views” přidejte nový objekt typu Silverlight User Control jménem “ToDoListsView“. Původní Grid s názvem “LayoutRoot” nahraďte tímto:

<Grid x:Name="LayoutRoot">
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="6*" />
		<ColumnDefinition Width="5*" />
	</Grid.ColumnDefinitions>
	<Grid.RowDefinitions>
		<RowDefinition Height="auto" />
		<RowDefinition Height="*" />
	</Grid.RowDefinitions>

	<Button x:Name="LoadData" Content="Load" />

	<ItemsControl x:Name="Lists" Grid.Row="1">
		<ItemsControl.ItemTemplate>
			<DataTemplate>
				<StackPanel>
					<TextBlock Text="{Binding Name}" FontWeight="Bold" 
								Style="{StaticResource DefaultTextBlockStyle}" />
					<TextBlock Text="{Binding Description}" TextWrapping="Wrap" 
								Style="{StaticResource DefaultTextBlockStyle}" />
				</StackPanel>
			</DataTemplate>
		</ItemsControl.ItemTemplate>
	</ItemsControl>
</Grid>

Jak již víme, Caliburn.Micro automaticky naváže zdroj dat prvku “Lists” na property stejného jména na view modelu. Stejně tak se jistě dovtípíte, že stisknutí tlačítka LoadData spustí na view modelu funkci jménem LoadData. Pojďme tedy na implementaci view modelu.

ToDoListsViewModel

Otevřete typ ToDoListsViewModel a přidejte do něj tento kód:

public IEnumerable<ToDoList> Lists { get; private set; }
public void LoadData()
{
	CoprojectContext context = new CoprojectContext();
	EntityQuery<ToDoList> query = context.GetToDoListsQuery().OrderByDescending(l => l.CreatedDate);
	context.Load(query, LoadDataCallback, null);
}

private void LoadDataCallback(LoadOperation<ToDoList> data)
{
	Lists = data.Entities;
	NotifyOfPropertyChange(() => Lists);
}

V Silverlightu je veškeré načítání dat prováděno asynchronně, na jiném vlákně, než běží vlastní uživatelské rozhraní. To je důležité k tomu, aby nedošlo k zaseknutí aplikace a ta byla schopna i přes načítání v pozadí reagovat na akce uživatele. Bohužel, asynchronní programování je trochu pracnější a často také komplikovanější, než běžný synchronní přístup.

Načítání dat přes RIA Services probíhá následovně: nejprve si vytvoříte context, což je jakási klientská část RIA služby, přes kterou komunikujete se serverem. Poté si z tohoto contextu vytáhnete query, což je torzo databázového dotazu. Tento dotaz můžete pomocí LINQu dále upravovat (třídit, filtrovat, apod.). Následně ho vrátíte zpět contextu s žádostí o provedení tohoto dotazu. Tady končí první fáze – teď je řada na serveru. aby data připravil. Jakmile jsou data připravena a načtena zpět na klientské straně, je zavolán callback (funkce), který jste předali contextu při žádosti o načtení dat.

Pokud teď spustíte aplikaci a kliknete na tlačítko Load, po chvíli by mělo okno Coprojectu vypadat asi takto:

Coroutines

Když tedy chceme provést nějakou akci až poté, co jsou data načtena, musíme ji umístit do callbacku (v našem případě to byla funkce LoadDataCallback). Snadno si dokážete představit, že pokud by mělo dojít k dalšímu načítání dat, případně nějaké komplikovanější logice, tak se nám původní funkce LoadData rozpadne na spousty menších funkcí, které budou volány jako callback asynchronní akce z předchozí funkce. Udržovat takovouto aplikaci jistě nebude nic příjemného. Naštěstí nám přichází C.M na pomoc s funkcí, které říká coroutines.

Coroutines v C.M stojí na objektech implementujících rozhraní IResult. Tyto objekty mají za úkol provést jednu asynchronní akci a poté dát vědět, když je tato událost dokončena. C.M poté prochází postupně všechny tyto objekty, které funkce vrací, spustí na nich danou akci a až result ohlásí, že skončil, pokračuje stejně s dalším resultem. K aplikaci zmíněného postupu se využívá klíčového slova “yield”. Možná jste si všimli podobnosti tohoto postupu s novými klíčovými slovy “asnyc” a “await” plánovaným do C# 5. Nejde o náhodu, tato funkce je v C# potřeba. Do té doby však budeme používat implementaci z C.M.

LoadDataResult

Pojďme si tedy připravit result, který bude načítat data ze serveru pomocí RIA Services. Ve složce Framework vytvořte nový typ “LoadDataResult“ s tímto obsahem:

public class LoadDataResult<TEntity> : IResult where TEntity : Entity
{
	public EntityQuery<TEntity> Query { get; set; }
	public DomainContext Context { get; set; }
	public LoadOperation<TEntity> Result { get; private set; }

	public event EventHandler<ResultCompletionEventArgs> Completed;

	public LoadDataResult(DomainContext context, EntityQuery<TEntity> query)
	{
		Query = query;
		Context = context;
	}

	public void Execute(ActionExecutionContext context)
	{
		Context.Load(Query, LoadDataCallback, null);
	}

	private void LoadDataCallback(LoadOperation<TEntity> data)
	{
		Result = data;
		OnCompleted();
	}

	private void OnCompleted()
	{
		var handler = Completed;
		if (handler != null)
		{
			handler(this, new ResultCompletionEventArgs());
		}
	}
}

Ačkoliv se může zdát třída komplikovaná, většina kódu slouží pouze pro inicializaci. Za pozornost stojí pouze funkce Execute a LoadDataCallback a ty jsou stejné, jako ty původní ve funkci LoadData(). Když je tedy tento result předán C.M, aby ho spustil, dojde ke spuštění funkce Execute. C.M poté čeká, než bude vyvolána událost Completed a pak pokračuje dál s dalším resultem v pořadí.

Původní funkci LoadData a LoadDataCallback z ToDoListsViewModel můžeme nahradit tímto:

public IEnumerable<IResult> LoadData()
{
	CoprojectContext context = new CoprojectContext();
	EntityQuery<ToDoList> query = context.GetToDoListsQuery().OrderByDescending(l => l.CreatedDate);
	var result = new LoadDataResult<ToDoList>(context, query);
	yield return result;

	Lists = result.Result.Entities;
	NotifyOfPropertyChange(() => Lists);
}

Načítání dat tedy stále probíhá asynchronně, na řádku s “yield” však dojde k pozastavení funkce LoadData až do chvíle, než jsou data opravdu načtena. V dalším řádku už jsou tedy tato data k dispozici. Celý proces, který probíhá ve funkci LoadData je nyní přehledný a snadno bychom do něj přidali další funkce.

Zobrazení ToDoItems

V aplikaci teď zobrazujeme pouze objekty typu ToDoList. Abychom mohli zobrazit i seznam vnořených prvků typu ToDoItem, musíme nejprve nestavit RIA Services (repsektive Entity Framework), aby klientovi posílaly i tyto data. V serverovém projektu Coproject.Web otevřete soubor Services/CoprojectService.cs. Pod funkci GetToDoLists přidejte tuto:

public IQueryable<ToDoList> GetToDoListsWithItems()
{
	return this.ObjectContext.ToDoLists.Include("ToDoItems");
}

ObjectContext je v tomto případě context z EntityFrameworku a příkazem Include ho žádáme, aby do entit, které nám vrátí, přidal i prvky z property ToDoItems. Pokud jsou tyto objekty k dispozici, RIA Services je automaticky předají klientské části (atribut [Include] jsme na zmíněných properties v metadatech nastavili už ve 2. díle).

Teď je nutné provést build celé solution, aby se změny projevily i v klientském projektu.

Tuto novou funkci teď využijeme – v ToDoListsViewModel.LoadData upravte proměnnou “query”, aby využívala místo context.GetToDoListsQuery tuto novou funkci (context.GetToDoListsWithItemsQuery).

Dále si otevřete ToDoListsView a následující kód přidejte pod dva TextBoxy v ItemTemplate prvku jménem “Lists”.

<ListBox ItemsSource="{Binding ToDoItems}"
				Margin="5,0,0,0" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
	<ListBox.ItemTemplate>
		<DataTemplate>
			<StackPanel Orientation="Horizontal">
				<TextBlock Text="{Binding DueDate,StringFormat='\{0:d\}'}" 
						   FontWeight="Bold" Margin="0,0,5,0" />
				<TextBlock Text="{Binding Content}" />
			</StackPanel>
		</DataTemplate>
	</ListBox.ItemTemplate>
</ListBox>

Když teď spustíte aplikaci, pravděpodobně vám nový seznam “uteče” z obrazovky. Obalte ho tedy ještě prvkem ScrollViewer:

<ScrollViewer Grid.Row="1" Style="{StaticResource ListsScrollViewerStyle}">
	<ItemsControl x:Name="Lists">
		...
	</ItemsControl>
</ScrollViewer>

To je vše – Coproject teď vypadá takto:

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