Webové "úterý" - Novinky v Silverlight 4 – díl 4.

S mírným zpožděním vám přinášíme čtvrtou část seriálu na téma novinek v technologii Silverlight..

Rozšiřitelnost navigačního modelu

Silverlight 3 představil navigační model, který se řídí podle adresy stránky. Každá adresa pak má asociovaný XAML soubor, jenž se zobrazoval v komponentě Frame. Té nově přidává Silverlight 4 vlastnost ContentLoader typu INavigationContentLoader. Vy tak můžete implementovat vlastní navigační logiku a řídit příchozí požadavky, přesměrování, autorizaci, chybové hlášky a další stejně jako například v MVC frameworcích.

Podpora bindingu příkazů

Silverlight přivádí k životu takzvané příkazy používané v architektuře Model-View-ViewModel (MVVM). Ta zařizuje separaci pohledu (View, vzhled reprezentován jako user-control) a ViewModelu (implementovaná logika pohledu).

Silverlight přidává podporu příkazů v tlačítkách (třída ButtonBase) a odkazech (HyperLink). Vlastnosti Command a CommandParameter dovolují pomocí deklarativního bindování tlačítek a odkazů z pohledu (View) volat příkazy ve ViewModelu. A to bez jediného řádku kódu v user-controlu.

Jako ukázka postačí jednoduchá kalkulačka. ViewModel bude třída nesoucí informaci o vstupu a výstupu - tedy sčítaná čísla a výsledek a zároveň implementuje INotifyPropertyChanged, aby mohla informovat uživatelské rozhraní o změně hodnot vlastností. V našem případě výsledku matematické operace sčítání. Poslední neméně důležitá bude vlastnost Calculate, která vrací třídu příkazu výpočtu. Příkaz reprezentuje třída CalculateCommand implementující rozhraní příkazu ICommand.

C#

public sealed class MathViewModel : INotifyPropertyChanged
{
private int number1, number2, result;

     public int Number1
{
get { return number1; }
set { number1 = value; NotifyPropertyChanged("Number1"); }
}

     public int Number2
{
get { return number2; }
set { number2 = value; NotifyPropertyChanged("Number2"); }
}

     public int Result
{
get { return result; }
set { result = value; NotifyPropertyChanged("Result"); }
}

     public ICommand Calculate
{
// vrací příkaz pro výpočet výsledku
get { return new CalculateCommand(this); }
}

     public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
// vyvolá událost změny vlastnosti,
// abychom informovali formulář
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

/// <summary>
/// Příkaz bindovaný na tlačítko provedení výpočtu
/// </summary>
public class CalculateCommand : ICommand
{
private MathViewModel viewModel;
public event EventHandler CanExecuteChanged;
public CalculateCommand(MathViewModel mathViewModel)

     {
// uchováme si ViewModel, na který se bindujeme
this.viewModel = mathViewModel;
}

public bool CanExecute(object parameter)
{
// příkaz lze vykonat
return true;
}

     public void Execute(object parameter)
{
// provést matematickou operaci ve ViewModelu
viewModel.Result = viewModel.Number1 + viewModel.Number2;
}
}

VB.NET

Public Class MathViewModel
Implements INotifyPropertyChanged

          Dim pNumber1 As Integer, pNumber2 As Integer, pResult As Integer

          Public Property Number1 As Integer
Get
Return pNumber1
End Get
Set(ByVal value As Integer)
pNumber1 = value
NotifyPropertyChanged("Number1")
End Set
End Property

Public Property Number2 As Integer
Get
Return pNumber2
End Get
Set(ByVal value As Integer)
pNumber2 = value
NotifyPropertyChanged("Number2")
End Set
End Property

Public Property Result As Integer
Get
Return pResult
End Get
Set(ByVal value As Integer)
pResult = value
NotifyPropertyChanged("Result")
End Set
End Property

Public ReadOnly Property Calculate As ICommand
Get
' vrací příkaz pro výpočet výsledku
Return New CalculateCommand(Me)
End Get
End Property

Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

Private Sub NotifyPropertyChanged(ByVal propertyName As String)
' vyvolá událost změny vlastnosti,
' abychom informovali formulář
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class

''' <summary>
''' Příkaz bindovaný na tlačítko provedení výpočtu
''' </summary>
Public Class CalculateCommand
Implements ICommand
Private viewModel As MathViewModel

Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
Public Sub New(ByVal mathViewModel As MathViewModel)
' uchováme si ViewModel, na který se bindujeme
Me.viewModel = mathViewModel
End Sub

Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
' příkaz lze vykonat
Return True
End Function

Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
' provést matematickou operaci ve ViewModelu
viewModel.Result = viewModel.Number1 + viewModel.Number2
End Sub
End Class

Podívejme se blíže na samotný příkaz CalculateCommand. Ten je dostupný z ViewModelu pomocí vlastnosti Calculate. Při nicializaci se konstruktoru předává i objekt ViewModelu, aby při pozdějším vyvolání výpočtu implementovanou metodou Execute měl příkaz k dispozici všechny potřebné hodnoty.

Je dobré vědět, že ViewModel je na příkaz pomocí vlastnosti dotázán už ve chvíli, kdy se inicializuje formulář. Nikoliv, až při volání příkazu. Je to kvůli implementované metodě CanExecute a události CanExecuteChanged. Ty obě slouží k notifikaci stavu, kdy tento příkaz může / nemůže být proveden. Automaticky to v uživatelském prostředí znamená, že se například tlačítko zneplatní, pokud CanExecute vrací false. Proto se vytváří instance příkazu již před prvním zobrazením formuláře, aby se tlačítka a odkazy bindované na případné neplatné příkazy zakázaly.

Nyní ukážu, jak napsat XAML kód využívající tento ViewModel. Nejprve zpřístupníme jmenný prostor s třídou MathViewModel. Například já mám v kořenovém elementu UserControl tuto definici:

xmlns:viewModels="clr-namespace:SilverlightApplication1.ViewModels"

Do sekce Resources vytvoříme odkaz na konkrétní třídu ViewModelu:

<UserControl.Resources>
<viewModels:MathViewModel x:Name="mathViewModel" />
</UserControl.Resources>

Hlavní element se bude odkazovat na tuto instanci atributem DataContext. Další kód vytváří formulář s vstupním a výstupním polem a tlačítkem:

<Grid x:Name="LayoutRoot" DataContext="{StaticResource mathViewModel}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF94A7DD" Offset="0" />
<GradientStop Color="#FFD8DFF1" Offset="1" />
</LinearGradientBrush>
</Grid.Background>

<sdk:Label Grid.ColumnSpan="2" FontSize="15" HorizontalAlignment="Center">Kalkulačka</sdk:Label>

<sdk:Label Grid.Row="1">Vstup 1:</sdk:Label>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Number1, Mode=TwoWay}" />

<sdk:Label Grid.Row="2">Vstup 2:</sdk:Label>
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding Number2, Mode=TwoWay}" />

<sdk:Label Grid.Row="3">Výsledek:</sdk:Label>
<TextBlock Grid.Row="3" Grid.Column="2" Text="{Binding Result, Mode=OneWay}" />

<Button Grid.Row="4" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top" Width="150" Height="25" Command="{Binding Calculate}">

Počítej

</Button>
</Grid>

Všimněte si, že tlačítko nemá žádný funkční kód. Pouze ve vlastnosti Command provádíme binding na příkaz Calculate. Takový binding je velmi podobný například tomu u textových polí.

pocitej

Po spuštění vidíme formulář, který odpovídá MVVM architektuře a navíc v pohledu není jediný řádek vlastního kódu. A i přesto formulář reaguje na akce uživatele při stisku tlačítka.

Krom vlastnost Command má třída tlačítka a odkazu navíc vlastnost CommandParameter. Ta definuje hodnotu předávaného parametru paremeter při volání CanExecute a Execute ve třídě příkazu. Například předávat dynamickou hodnotu podle obsahu nějakého textového pole lze zařídit pomocí jednoduchého bindingu:

CommandParameter="{Binding ElementName=TextBlock1, Path=Text }"

Je dobré vědět, že se jedná o nepovinou vlastnost. Pokud ji nevyplníme, bude se předávat null. Využívat však můžete všechny dostupné způsoby bindování.

- Tomáš Jecha