Pokazywanie postów oznaczonych etykietą Commands. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Commands. Pokaż wszystkie posty

niedziela, 27 listopada 2011

MVVM cz.6 - Commands part 2

Tak jak wspomniałem w poprzednim wpisie, tym razem zajmiemy się tworzeniem własnej klasy implantującej interfejs ICommand .
1. Implementacja interfejsu ICommand - WPF
Klasa implementująca ICommand może wyglądać w następujący sposób

public class DelegateCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;

}
}
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (canExecute == null) return true;
return canExecute(parameter);
}
public void Execute(object parameter)
{
if (execute == null) return;
execute((object)parameter);
}
}

Klasa DelegateCommand przyjmuje w konstruktorze dwa parametry - delegaty do funkcji. Pierwszy z nich jest to delegat do funkcji, która ma się wykonać, gdy komenda może zostać wykonana.Drugi natomiast jest to delegat do funkcji, która sprawdza czy dana komenda może zostać wykonana.Ważnym elementem jest tutaj następujący kawałek kodu :

public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}

który podłącza naszą klasę do WPF-owego systemu komend.
Teraz wystarczy w ViewModelu stworzyć obiekt typu DelegateCommand przekazać do niego odpowiednie funkcje, a następnie zbindować go do widoku. Może to wyglądać w następujący sposób:

  • ViewModel


  • public ICommand AddCommand
    {
    get;
    set;
    }
    public WatchListViewModel()
    {
    AddCommand = new DelegateCommand(val => Add(), val => CanAdd());
    }

  • Widok (XAML)


  • <Button Command="{Binding AddCommand}"/>


2. Implementacja interfejsu ICommand - Silverlight
Przedstawiony powyżej przykład skompiluje się jedynie w aplikacji WPF-owej. Silverlight niestety nie posiada CommandManagera, dlatego też gdy chcemy używać komend właśnie w tej technologii musimy zmodyfikować nasz kod na następujący:

public class DelegateCommand : ICommand
{

public event EventHandler CanExecuteChanged;
private Action<object> execute;
private Func<object, bool> canExecute;
private bool previousState;
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;

}

public DelegateCommand(Action<object> execute)
{
this.execute = execute;
}

public bool CanExecute(object parameter)
{
if (canExecute == null) return true;
bool currentState = canExecute((object)parameter);
if (currentState != previousState)
{
previousState = currentState;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
return currentState;
}
return currentState;

}

public void Execute(object parameter)
{
if (execute == null) return;
execute((object)parameter);
}


public void RaiseCanExecute()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}

Jak widać w funkcji

public bool CanExecute(object parameter)

sami musimy zadbać o wywołanie zdarzenia

public event EventHandler CanExecuteChanged;

Ponadto dopisana została funkcja

public void RaiseCanExecute()

funkcję tą musimy wywoływać zawsze gdy zmieni się jakaś właściwość, która ma mieć wpływ na stan przycisku zbindowanego do naszej komendy.
3. Przykład
Załóżmy,że na widoku mamy przycisk edytuj, który powinien być aktywny tylko i wyłącznie wtedy, gdy zaznaczono jakiś element na gridzie. Przycisk ten jest zbindowany do komendy EditCommand. Jak już wcześniej wspomniano, z powodu braku w Silverlighcie CommandManagera sami musimy wywołać zdarzenie informujące o potrzebie zmiany stanu przycisku. Dlatego też, w ViewModelu została stworzona właściwość SelectedItem

public Department SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
NotifyPropertyChanged(() => SelectedItem);
EditCommand.RaiseCanExecute();
}
}

która reaguje na zmiany zaznaczenia na gridzie. W chwili, gdy zaznaczenie się zmienia, wywoływany jest seter z tej właściwości, w którym to wywołujemy funkcję EditCommand.RaiseCanExecute(). Dzięki czemu informujemy kontrolkę o ewentualnej potrzebie zmiany jej stanu.
4. Podsumowanie
Komendy w bardzo prosty i wygodny sposób pozwalają nam wywoływać metody bezpośrednio z ViewModelu. Niestety mają one również swoje ograniczenia:

  • reagują jedynie na zdarzenie Click,

  • można je wykorzystać jedynie na kontrolkach dziedziczących po klasie ButtonBase oraz kontrolkach typu MenuItem

MVVM cz.6 - Commands part 1

1. Wstęp
Każdy programista, który napisał chociaż parę linijek kody w WinFormsach wie, że kontrolki używają eventów do powiadamiania o zmianach swojego stanu. W przypadku, gdy interesuje nas odpowiednia reakcja na zdarzenie, podpinamy się do niego odpowiednią funkcją i wykonujemy założone przez nas operację. W analogiczny sposób można postępować w WPF-ie oraz w Silverlighcie, jednakże podejście takie niejako mija się z modelem MVVM. Posiadanie event handlerów w kodzie, ściśle wiąże nam widok (XAML) z code behind lub nawet z samym ViewModelem danego widoku. Ponadto używanie eventów jest problematyczne (a właściwie niemożliwe), gdy widoki zostaną zdefiniowane w Resourcach. Dodatkową wadą używania event handlerów jest fakt, że aplikacje w których widok jest mocno związany z codebehind nie należą do aplikacji, które w łatwy sposób można poddać testom automatycznym.
2. Interfejs ICommand
W celu rozluźnienia połączenia między widokiem a ViewModelem wprowadzono interfejs

public interface ICommand
{

event EventHandler CanExecuteChanged;
bool CanExecute(object parameter);
void Execute(object parameter);
}

Funkcja

public bool CanExecute(object parameter)

ma za zadanie sprawdzić, czy daną komendę można wykonać. Jeżeli nie,funkcja zwraca false i kontrolka zbindowana do danej komendy jest wygaszana (właściwość IsEnabled danej kontrolki ustawiana jest na false). Z kolei funkcja

public void Execute(object parameter)

jest to odpowiednik event handlera podłączonego do zdarzenia. Zdarzenie

public event EventHandler CanExecuteChanged;

informuje widok o tym czy dana komenda może się wykonać czy też nie - dzięki temu kontrolka wie kiedy się odświeżyć(zmienić wartość właściwości IsEnabled).

3. Sposób użycia
Załóżmy, że mamy okno na którym znajduje się przycisk. Po naciśnięciu przycisku chcielibyśmy aby wykonała się jakaś czynność. Każdy obiekt typu Window oraz Control posiada kolekcję CommandBindings, która przechowuje wszystkie komendy danego okna.
W celu dodania nowej komendy należy zrobić następującą rzecz. Po pierwsze musimy stworzyć nową komendę. Robimy to przy pomocy następującego kodu:

private static RoutedCommand AddCommand = new RoutedCommand();

W celu dodania komendy do danego okna/kontrolki wykonujemy następującą operację

this.CommandBindings.Add(new CommandBinding(AddCommand,ExecuteAddCommand,CanExecuteAddCommand));

Funkcja Add z klasy CommandBindingCollection przyjmuje w parametrze obiekt typu CommandBinding. Konstruktor takiego obiektu składa się z trzech parametrów:

  • RoutedCommand - obiekt typu RoutedCommand

  • delegat typu ExecutedRoutedEventHandler - jest to delegat do funkcji, która ma się wykonać w przypadku gdy dana komenda spełnia warunki do “uruchomienia”

  • delegat typu CanExecuteRoutedEventHandler - jest to delegat do funkcji, która sprawdza czy daną komendę można wykonać. W celu zabronienia wykonania komendy należy ustawić argument ExecutedRoutedEventArgs CanExecute na false.


Na koniec należy jeszcze zbindować naszą komendę z przyciskiem. Przykładowy kod dodawania komendy może wyglądać w następujący sposób.

public partial class WatchList : Window
{
private static RoutedCommand AddCommand = new RoutedCommand();
public WatchListViewModel viewModel = new WatchListViewModel();
public WatchList()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(AddCommand, ExecuteAddCommand, CanExecuteAddCommand));
}

private void CanExecuteAddCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = viewModel.CanAdd();}

private void ExecuteAddCommand(object sender, ExecutedRoutedEventArgs args)
{
viewModel.Add();
}
}

Bindowanie w przycisku do komendy może wyglądać w następujący sposób (XAML)


gdzie local jest aliasem na namespace, w którym znajduje się okno.
4. Podsumowanie
Użycie komend w pewnym stopniu oddzieliło nam widok od codebehind-a, jednakże wciąż nie jest to rozwiązanie idealne. Z codebehind-a wywołujemy metody ViewModelu, więc niejako ciągle jesteśmy bezpośrednio zależni od widoku. W części drugiej zaprezentuję w jaki sposób można stworzyć własny obiekt implementujący interfejs ICommand, dzięki czemu ograniczymy do minimum ilość kodu pisanego w codebehind.

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Online Project management