GoDev

Blog programisty.

PictOgr - delegowanie wykonania akcji do modelu widoku.

PictOgr - delegowanie wykonania akcji do modelu widoku. 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 ExecuteCanExecute 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;
		}
	}
}

PictOgr. 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();
		}
	}
}

PictOgr.

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.


Daj Się Poznać 2017

Jest to post przygotowany na potrzeby konkursu „Daj Się Poznać 2017” organizowanym przez Macieja Aniserowicza.

Blog http://godev.gemustudio.com
Projekt http://godev.gemustudio.com/pictogr-pomysl
GitHub github.com/krzysztofowsiany/pictogr
Snapchat www.snapchat.com/add/gocom7
Facebook www.facebook.com/PictOgr-1729700930654225
Twitter twitter.com/gemu_gocom
RSS http://godev.gemustudio.com/category/daj-sie-poznac-2017/feed