Nie zawsze dobrym rozwiązaniem jest budowanie komendy dla każdej operacji wykonywanej na widoku, wręcz może okazać się uciążliwe przekazanie danych z formularza do komendy. W takiej sytuacji z pomocą przychodzą delegaty. Implementacja interfejsu ICommand niesie ze sobą potrzebę deklaracji dwóch metod Execute, CanExecute oraz zdarzenie CanExecuteChanged. Jako że w programowaniu nie istnieje jedno rozwiązanie problemu i w tym przypadku można delegować wykonanie metod Execute oraz CanExecute do modelu widoku.
Komenda RelayCommand z delegacją logiki działania na zewnątrz obiektu.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Windows.Input;
namespace PictOgr.MVVM.Base
{
public class RelayCommand<TParam> : ICommand where TParam : class
{
private readonly Action<TParam> execute;
private readonly Func<TParam, bool> canExecute;
public RelayCommand(Action<TParam> execute, Func<TParam, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute(parameter as TParam);
}
public void Execute(object parameter)
{
execute(parameter as TParam);
}
}
public class RelayCommand : RelayCommand<object>
{
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
: base(execute, canExecute)
{
}
}
}
Podczas tworzenia instancji na bazie klasy RelayCommand przekazujemy do niej dwa delegaty: execute, canExecute.
Wykonanie metody CanExecute nie zawsze jest istotne dlatego też w celu uproszczenia wykluczenia delegowania dla tej metody powstała klasa RelayCommand rozszerzająca klasę bazową o tej samej nazwie i już określonym typie generycznym (object). Dzięki temu możemy przy tworzeniu nowej komendy wykorzystać prostą składnię: new RelayCommand(executeDelegate);
Przykład wykorzystania RelayCommand
Przy budowaniu widoku konfiguracji dla PictOgra, wykorzystany został mechanizm RelayCommand do ustawiania wzorca ścieżki z dostępnych komponentów nazw.
Przykład wykorzystania RelayCommand.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System.Windows.Input;
using Autofac.Extras.NLog;
using CQRS.Bus.Query;
using PictOgr.MVVM.Base;
namespace PictOgr.MVVM.Configuration.ViewModels
{
public class ConfigurationViewModel : BaseViewModel
{
private string pathFormat;
public string PathFormat
{
get { return pathFormat; }
set
{
pathFormat = value;
OnPropertyChanged(nameof(PathFormat));
}
}
public ICommand AddNameModuleCommand { get; private set; }
public ConfigurationViewModel(IQueryBus queryBus, ILogger logger) : base(queryBus, logger)
{
PathFormat = string.Empty;
AddNameModuleCommand = new RelayCommand(AddNameModule);
}
private void AddNameModule(object parameter)
{
var nameModule = parameter.ToString();
PathFormat += nameModule;
}
}
}
Tak przygotowany kod pozwala obsłużyć dowolną ilość modułów nazwy jakie będą zaimplementowane w aplikacji.
RelayCommand pozwolił na delegacje tego mechanizmu do modelu widoku i operowaniu bezpośrednio z wykorzystaniem dostępnych właściwości.
Właściwość PathFormat, wyświetla bieżący wzorzec nazwy w widoku konfiguracji.
Każdy dodany nowy moduł nazwy (Button), wykorzystuje komendę AddNameModuleCommand z parametrem przechowującym identyfikator modułu nazwy.
Testowanie RelayCommand
Poniżej znajduje się proste testy sprawdzające działanie klasy RelayCommand w projekcie E2E.
Testowanie RelayCommand.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using PictOgr.MVVM.Base;
using Shouldly;
using Xunit;
namespace PictOgr.E2E
{
public class RelayCommandTest
{
private string executeParam;
private readonly RelayCommand relayCommand;
private bool canExecute;
public RelayCommandTest()
{
relayCommand = new RelayCommand(ExecuteDelegate, CanExecute);
}
private bool CanExecute(object parameter)
{
var testString = parameter.ToString();
canExecute = !string.IsNullOrWhiteSpace(testString);
return canExecute;
}
private void ExecuteDelegate(object parameter)
{
executeParam = parameter.ToString();
}
[Fact]
public void execute_relaycommand_should_set_param_and_can_execute()
{
var testString = "ok";
relayCommand.CanExecute(testString);
relayCommand.Execute(testString);
executeParam.ShouldBe(testString);
canExecute.ShouldBeTrue();
}
[Fact]
public void execute_relaycommand_should_not_set_param_and_can_execute()
{
var testString = string.Empty;
relayCommand.CanExecute(testString);
canExecute.ShouldBeFalse();
}
}
}
Zakończenie
Mechanizm RelayCommand w większości przypadków niweluje potrzebę tworzenia innych klas komend, skraca to kodowanie i powoduje powstawanie mniej błędów.
Dziękuję za wytrwałość i zachęcam do komentowania.
Jest to post przygotowany na potrzeby konkursu „Daj Się Poznać 2017” organizowanym przez Macieja Aniserowicza.
Blog | https://mrdev.pl |
Projekt | https://mrdev.pl/pictogr-pomysl |
GitHub | github.com/krzysztofowsiany/pictogr |
Snapchat | www.snapchat.com/add/gocom7 |
www.facebook.com/PictOgr-1729700930654225 | |
twitter.com/gemu_gocom | |
RSS | http://mrdev.pl/category/daj-sie-poznac-2017/feed |