S mírným zpožděním vám přinášíme čtvrtou část seriálu na téma novinek v technologii Silverlight..
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.
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í.
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