테닝베어의 나날

[C#/WPF] MVVM 패턴에서 Event를 처리하는 방법 본문

IT/C# & WPF

[C#/WPF] MVVM 패턴에서 Event를 처리하는 방법

테닝베어 2021. 12. 4. 11:55

안녕하세요. 오늘은 저번에 이어 MVVM 패턴을 이용한 기능 구현을 이어가려고 합니다.

저번에 설명한 포스팅 글은 다음과 같습니다.

 

[C#/WPF] DataBinding 예제 및 설명

 

[C#/WPF] DataBinding 예제 및 설명

안녕하세요. 저번 시간에는 버튼을 누르면 카운팅 되어 Label에 숫자가 올라가는 것을 해보았습니다. 저번 시간 내용을 보시려면 아래 링크를 눌러주세요. 2021.11.27 - [분류 전체보기] - [C#/WPF] 버튼

cw-wd.tistory.com

 

XAML 코드는 다음과 같이 수정합니다.

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
<Window x:Class="ExWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ExWpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
 
        <Label
            Grid.Column="0"
            HorizontalContentAlignment="Center"
            VerticalContentAlignment="Center"
            Content="{Binding ClickCnt}"
            />
 
        <Button
            Grid.Column="1"
            Content="눌러주세요."
            Command="{Binding IncreaseCntCommand}"
            />
            
    </Grid>
</Window>
cs

 

저번 시간에 만들었던 코드와 비교해보면 딱 한 줄 바뀌었다는 것을 알 수 있습니다.바로 25번째 줄인데요.기존 코드는 Click 이벤트에 이벤트 핸들러를 달았다면, 지금은 Command라는 속성에 'IncreaseCntCommand'라는 메서드를 바인딩을 걸었습니다.IncreaseCntCommand 메서드 구현은 다음 코드를 보시죠.
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
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
 
namespace ExWpf
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;
        private void RaisePropertyChanged([CallerMemberName]string propName = "")
            => PropertyChanged?.Invoke(thisnew PropertyChangedEventArgs(propName));
 
        private int mClickCnt = 0;
        public int ClickCnt
        {
            get { return mClickCnt; }
            set
            {
                if(mClickCnt != value)
                {
                    mClickCnt = value;
                    RaisePropertyChanged(); //혹은 매개변수에 nameof(ClickCnt) 를 넣는다.
                }
            }
        }
 
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }
 
        private RelayCommand<object> _IncreaseCntCommand;
        public RelayCommand<object> IncreaseCntCommand
        {
            get
            {
                if (_IncreaseCntCommand == null) _IncreaseCntCommand = new RelayCommand<object>(IncreaseCnt);
                return _IncreaseCntCommand;
            }
        }
 
        private void IncreaseCnt(object? obj)
        {
            ClickCnt++;
        }
    }
}
cs


 

 

 

35~49 번째 줄을 보면 IncreaseCntCommand와 관련된 필드, 속성, 메서드가 있는 것을 알 수 있습니다.

DataBinding은 속성에만 걸 수 있습니다. 때문에 IncreaseCntCntCommand를 속성으로 만들었습니다.

IncreaseCntCntCommand의 타입은 RelayCommand 클래스입니다. RelayCommand는 기본으로 제공해주는 클래스가 아닌 직접 만든 클래스입니다. 왜냐하면 Command는 ICommand 인터페이스를 구현해야만 하기 때문입니다.

 

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
using System;
using System.Windows.Input;
 
namespace ExWpf
{
    public class RelayCommand<T> : ICommand
    {
        public event EventHandler? CanExecuteChanged;
 
        //public event EventHandler? CanExecuteChanged
        //{
        //    add { CommandManager.RequerySuggested += value; }
        //    remove { CommandManager.RequerySuggested -= value; }
        //}
 
        private Action<object?>? mExecute;
        private Predicate<object?>? mCanExecute;
 
        public RelayCommand(Action<object?>? _execute, Predicate<object?>? _canExecute = null)
        {
            mExecute = _execute;
            mCanExecute = _canExecute;
        }
 
        public bool CanExecute(object? parameter)
        {
            return mCanExecute != null ? mCanExecute(parameter) : true;
        }
 
        public void Execute(object? parameter)
        {
            mExecute?.Invoke(parameter);
        }
    }
}
cs

6번째 줄 : RelayCommand는 제네릭 타입이고, ICommand를 구현하고 있습니다.

 

16~23번째 줄 : '실행 메서드를 작동시켜도 되는지에 대해 확인하는 메서드'와 '확인 후에 작동하는 실행 메서드'를 세팅하는 부분입니다.

 

25~33번째 줄 : ICommand 구현부입니다. Command를 호출한다는 것은 CanExecute를 호출한 후 Execute를 호출하는 것으로 이해하면 됩니다. CanExecute가 False를 뱉으면 Execute를 호출하지 않습니다.

 

8~14번째 줄 : ICommand를 구현하면 추가해야 하는 이벤트입니다. 그냥 8번째 줄처럼 써도 되고, 10~14번째 줄처럼 CommandManager.RequerySuggested를 이용하여 사용해도 됩니다.

CommandManager.RequerySuggested는 어떨 때 사용될까요?

그냥 8번째 줄처럼 사용한다면 CanExecute는 Command가 호출될 때만 사용이 됩니다.

그런데 CommandManager.RequerySuggested에 이벤트 핸들러를 추가하면 WPF 측에서 알아서 해당 이벤트 핸들러를 호출해줍니다. 어떨 때? 애플리케이션에서 일어나는 윈도우 이벤트가 일어날 때마다!(어떤 부분을 클릭한다든지, 텍스트를 쓴다든지..)

그럼 CanExecute가 호출되면 뭐가 좋으냐? 가장 큰 장점으로는 아무런 추가 작업 없이 버튼의 활성화 비활성화를 알아서 처리해준다는 것입니다! 신기방기.. 하지만 알고리즘적 시간이 오래 걸리는 CanExecute는 부화를 일으킬 수도 있겠죠..? 잘 알아서 융통성 있게 사용하면 좋을 듯싶습니다!

 

 

이상으로 오늘의 포스팅을 마치겠습니다.

다음 글에서는 ViewModel 부분을 분리하는 것을 보여드리겠습니다.

감사합니다.

 

 

Comments