2Bbear's knowledge workshop

https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/11-UsingDelegateCommands

참고 코드

https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/11-UsingDelegateCommands

내가 만든 코드

기본적인 코드는 비슷하니 패스


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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using Prism.Commands;
using Prism.Mvvm;
 
namespace UsingDelegateCommands.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private bool _isEnabled;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                SetProperty(ref _isEnabled, value);
                ExecuteDelegateCommand.RaiseCanExecuteChanged();
            }
        }
 
        private string _updateText;
        public string UpdateText
        {
            get { return _updateText; }
            set { SetProperty(ref _updateText, value); }
        }
 
 
        public DelegateCommand ExecuteDelegateCommand { get; private set; }
 
        public DelegateCommand<string> ExecuteGenericDelegateCommand { get; private set; }        
 
        public DelegateCommand DelegateCommandObservesProperty { get; private set; }
 
        public DelegateCommand DelegateCommandObservesCanExecute { get; private set; }
 
 
        public MainWindowViewModel()
        {
            Console.WriteLine("JJH: MainWindowViewModel MainWindowViewModel");
            ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
 
            DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
 
            DelegateCommandObservesCanExecute = new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled);
 
            ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
        }
 
        private void Execute()
        {
            Console.WriteLine("JJH: MainWindowViewModel Execute");
            UpdateText = $"Updated: {DateTime.Now}";
        }
 
        private void ExecuteGeneric(string parameter)
        {
            Console.WriteLine("JJH: MainWindowViewModel ExecuteGeneric");
            UpdateText = parameter;
        }
 
        private bool CanExecute()
        {
            Console.WriteLine("JJH: MainWindowViewModel CanExecute");
            return IsEnabled;
        }
    }
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Window x:Class="UsingDelegateCommands.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="Using DelegateCommand" Width="350" Height="275">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <CheckBox IsChecked="{Binding IsEnabled}" Content="Can Execute Command" Margin="10"/>
        <Button Command="{Binding ExecuteDelegateCommand}" Content="DelegateCommand" Margin="10"/>
        <Button Command="{Binding DelegateCommandObservesProperty}" Content="DelegateCommand ObservesProperty" Margin="10"/>
        <Button Command="{Binding DelegateCommandObservesCanExecute}" Content="DelegateCommand ObservesCanExecute" Margin="10"/>
        <Button Command="{Binding ExecuteGenericDelegateCommand}" CommandParameter="Passed Parameter" Content="DelegateCommand Generic" Margin="10"/>
        <TextBlock Text="{Binding UpdateText}" Margin="10" FontSize="22"/>
    </StackPanel>
</Window>
 
cs




MainWindowViewModel 과 MainWindow가 XAML에서 ViewModelLocator로 연결되어 있는 상태이다.


메인윈도우 뷰모델을 확인하면.



IsChecked 컨트롤은 IsEnabled 프로퍼티와 연결되어 있다. 이게 two way인가보다 프로퍼티의 상태를 보면

get과 set이 있는데, get의 경우 단순히 _isEnabled를 반환하고

set의 경우 _isEnabled를 value값으로 초기화하고, ExecuteDelegateCommand.RaiseCanExecuteChanged();를 실행시킨다.



프로그램의 실행 순서로 보았을 때, 생성자가 제일먼저 실행되고 그 뒤에 CanExecute가 실행되는 것을 확인 할 수 있다.



생성자를 살펴보면

1
2
3
4
5
6
7
8
9
10
11
12
public MainWindowViewModel()
        {
            Console.WriteLine("JJH: MainWindowViewModel MainWindowViewModel");
            ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
 
            DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
 
            DelegateCommandObservesCanExecute = new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled);
 
            ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
            Console.WriteLine("JJH: MainWindowViewModel MainWindowViewModel end");
        }
cs


이렇게 구성되어 있는데, 


ExecuteDelegateCommand는 버튼의 DelegateCommand에 붙어있다.


ExecuteDelegateCommand는 이렇게 public DelegateCommand ExecuteDelegateCommand { get; private set; } 프로퍼티로 선언되어 있다.


이 ExecuteDelegateCommand에 들어간 new DelegateCommand(Execute, CanExecute);를 살펴보면


DelegateCommand를 생성하여 담았는데, 첫번째 매개변수에 실행할 함수, 두번째 매개변수에 CanExecute 즉 실행 여부 함수를 담아야 한다.


따라서 DelegateCommand버튼을 누를 경우 실행 순서가 CanExecute ->Execute  순으로 일어나게 될 것이다.




CanExecute가 먼저 실행되고 그 뒤에 Execute가 실행되는 모습을 볼 수 있다.


따라서 DelegateCommand(실행할 메소드, 실행 여부 판단 메소드)로 구성되는 것을 알 수 있었다.


==================================================================


이제 DelegateCommand ObservesProperty에 대해 확인해보면

해당 버튼은 DelegateCommandObservesProperty라는 프로퍼티와 연결되어 있다.

생성자에서  DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled); 이렇게 DelegateCommand가 붙어있는데 기본적인 딜리게이트 커맨드의 생성자는 이해했지만 ObservesProperty가 뭔지 모르겠다.


선언문을 확인해보면

//

        // 요약:

        //     Observes a property that implements INotifyPropertyChanged, and automatically

        //     calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.

        //

        // 매개 변수:

        //   propertyExpression:

        //     The property expression. Example: ObservesProperty(() => PropertyName).

        //

        // 형식 매개 변수:

        //   T:

        //     The object type containing the property specified in the expression.

        //

        // 반환 값:

        //     The current instance of DelegateCommand

        public DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression);


이렇게 되어 있는데


 INotifyPropertyChanged 인터페이스를 구현한 property를 관찰합니다. 그리고 속성 변경 알림에서 자동적으로 DelegateCommandBase.RaiseCanExecutechanged를 호출합니다.  


저 RaiseCanExecuteChanged는 뭘까요?


namespace Prism.Commands

{

    //

    // 요약:

    //     An System.Windows.Input.ICommand whose delegates can be attached for Prism.Commands.DelegateCommandBase.Execute(System.Object)

    //     and Prism.Commands.DelegateCommandBase.CanExecute(System.Object).

    public abstract class DelegateCommandBase : ICommand, IActiveAware

    {

        //

        // 요약:

        //     Creates a new instance of a Prism.Commands.DelegateCommandBase, specifying both

        //     the execute action and the can execute function.

        protected DelegateCommandBase();


        //

        // 요약:

        //     Gets or sets a value indicating whether the object is active.

        public bool IsActive { get; set; }


        //

        // 요약:

        //     Occurs when changes occur that affect whether or not the command should execute.

        public event EventHandler CanExecuteChanged;

        //

        // 요약:

        //     Fired if the Prism.Commands.DelegateCommandBase.IsActive property changes.

        public event EventHandler IsActiveChanged;


        //

        // 요약:

        //     Raises Prism.Commands.DelegateCommandBase.CanExecuteChanged so every command

        //     invoker can requery to check if the command can execute. Note that this will

        //     trigger the execution of Prism.Commands.DelegateCommandBase.CanExecuteChanged

        //     once for each invoker.

        public void RaiseCanExecuteChanged();





DelegateCommand 가 상속한 DelegateCommandBase 안에 있는 RaiseCanExceuteChanged입니다.

요약을 읽어보면

command가 execute 할 수 있을 때, Prism.Commands.DelegateCommandBase.CanExcecutechanged를 올립니다 모든 command 호출자가 확인할 수 있도록 말입니다. 각 호출자에 대해서 한번씩 실행하게 됩니다.


어...음...번역이 매끄럽지 못한데. 이해를 해보자면

ObservesProperty(람다식)을 실행하게 되면 DelegateCommand를 반환합니다. 다만 이때 반환되는 DelegateCommand는 Property가 변경될때 어떠한 메소드가 trigger로 실행되게 됩니다. 그것이 람다식으로 넘긴 함수 ( )=>IsEnabled가 실행되게 됩니다.

즉 여기서의 IsEnabled는 get{return _isEnabled;}가 실행되게 되는 것입니다. 


정확히 이해하자면.

ObservesProperty는 프로퍼티가 변경되어있는지 확인하는 메소드이며 변경되었다면 해당 Command를 실행 할 수 있게합니다.

매개변수로 넘어가는 람다 메소드 즉 프로퍼티 식은, 관찰할 프로퍼티이며 해당 프로퍼티가 바뀔 경우 반응하게 됩니다.

ObservesProperty(변경을 관찰할 프로퍼티)



new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled); 의 경우

CanExecute가 붙지 않았으므로 프로퍼티가 변경됨에 상관없이 항상 실행이 가능한 상태인데.

여기에 CanExecute를 관찰하는 ObservesCanExecute를 실행함으로써 다른 CanExecute를 이용 할 수 있게 만든 코드입니다.

즉 하나의 DelegateCommand가 굳이 하나의 CanExecute만을 사용하는 것이 아닌 런타임시에 변경 되게 할 수있는 코드입니다.

매개변수로 프로퍼티의 상태를 나타내는 get{}을 넘겼으므로 프로퍼티 상태에 따라서 해당 Command를 사용 할 수 있게 됩니다.



 ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);

의 경우 제네릭을 사용하여 만들어진 Command인데.

이건 진짜 모르겠네.


대충 이해하자면 ExecuteGeneric를 실행시키는데 이 실행여부 함수로 ObservesCanExecute를 이용하여 IsEnabled가 실행 가능하게 true 일경우 이 command를 실행 가능하게 하는 것이고.



private void ExecuteGeneric(string parameter)

        {

            parameter = "what?";

            Console.WriteLine("JJH: MainWindowViewModel ExecuteGeneric");

            UpdateText = parameter;

        }


이렇게 구현되어 있는데. 

이 Execute가 실행되면 화면 하단의 시간이 출력되던 부분에서 waht? 이 출력되게 된다.