14장. WPF Command패턴, 데이터바인딩 DataBinding 개요, 실습
- C#/WPF
- 2021. 5. 14. 19:43
14장. WPF Command패턴, 데이터바인딩 DataBinding 개요, 실습
참조
목적
- WPF Command패턴을 학습하고 데이터바인딩을 실습합니다.
WPF Command 패턴의 이해 및 Command, 데이터바인딩 실습
- 전통적인 이벤트 기반 프로그래밍에서 컨트롤에 이벤트 핸들러 메서드를 코드 비하인드에서 연결하여 사용자의 이벤트를 처리했다. 그러나 이 방식은 이벤트처리 핸들러를 재사용 하거나 단위 테스트를 어렵게 한다.
- XAML UI 에서 버튼을 클릭시 MVVM 에서는 Click 이벤트 핸들러를 이용하기 보다는 Command 를 이용하기를 권장한다. 여러 버튼에서 하나의 Command를 공유할 수 있으므로 모든 컨트롤마다 Click 이벤트를 만드는 방법 보다는 효율적이기 때문이다.
- WPF의 명령(Command)은 ICommand 인터페이스를 구현하여 만들며 ICommand는 Execute 및 CanExecute 라는 두 가지 메서드와 CanExecuteChanged 이벤트를 제공한다.
- Execute 메서드는 실제 처리해야 하는 작업을 기술하고 CanExecute 메서드에서는 Execute 메서드의 코드를 실행할 지 여부를 결정하는 코드를 기술한다. CanExecute 가 false를 리턴하면 Execute 메서드느느 호출되지 않는다.
- 즉, CanExecute 메서드는 명령을 사용 가능하게 하거나 사용 불가능하게 할 때 사용되며 명령을 사용할 수 있는지 여부를 확인하기 위해 WPF에 의해 호출된다. 이 메서드는 키보드 GET 포커스, LOST 포커스, 마우스 업 등과 같은 UI 상호 작용 중에 대부분 발생한다.
- 사용자 정의 명령의 경우 CanExecute 메서드가 대부분의 시나리오에서 호출되지는 않으므로 어떤 조건에 따라 버튼을 활성화, 비활성화 해야 할 수도 있는데 ICommand 구현체에서 CanExecuteChanged 이벤트를 CommandManager의 RequerySuggested 이벤트에 연결하면 된다.
- CanExecute 메서드가 호출되어 CanExecute의 상태가 변경되면 CanExecuteChanged 이벤트가 발생해야 하며, WPF는 CanExecute를 호출하고 Command에 연결된 컨트롤의 상태를 변경한다.
//CanExecuteChanged 이벤트는 해당 ICommad 에 바인딩 된
//모든 명령 소스(예 : Button 또는 MenuItem) 에
//CanExecute에 의해 반환 된 값이 변경되었음을 알린다.
//Command 소스는 일반적으로 상태를 적절히 업데이트해야 하는데
//예를 들면 CanExecute()가 false를 반환하면 버튼이 비활성화 된다.
//CommandManager.RequerySuggested 이벤트는 CommandManager가 명령 실행에
//영향을 줄 수 있는 변경사항이 있다고 생각할 때 마다 발생하며 이때마다
//CanExecute가 호출된다.
//예를 들어, 이는 포커스의 변화 일 수 있는데, 이 이벤트가 많이 발생한다.
//따라서 본질적으로 이 코드의 역할은 CommandManager가 명령 실행 기능이 변경되었다고
//생각할 때마다(실제로 변경되지 않은 경우에도) CanExecuteChanged를 발생시키는 것이다.
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
- CommandManager.RequerySuggested 이벤트는 CanExecute 메서드를 강제로 실행할 수 있다.
- CanExecuteChanged 이벤트는 CommandManager의 RequerySuggested에 위임되어 모든 종류의 UI 상호작용을 통해 변경사항이 호출되는 정확한 알림을 제공한다.
- RequerySuggested 이벤트의 CommandManager.InvalidateRequerySuggested()를 호출하여 CommandManager의 RequerySuggested 이벤트를 발생하도록 할 수도 있다.
- Command 패턴에서는 몇가지 주체가 있는데 서비스를 요청하는 클라이언트(손님), 명령을 서술하는 Command Object(주문서), 명령을 요청하는 Command Invoker (다른말로는 Command Source 라고도 부른다.)(웨이터), 특정 명령을 실제 처리하는 Command Receiver(Target, 요리사) 가 있다.
실습
Emp.cs
namespace WPF14_Test
{
/// <summary>
/// Model Class
/// </summary>
public class Emp
{
public string Ename { get; set; }
public string Job { get; set; }
public override string ToString()
{
return $"[ {Ename} , {Job} ]";
}
}
}
MainWindowViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WPF14_Test
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private Emp _SelectedEmp;
public Emp SelectedEmp
{
get
{
return _SelectedEmp;
}
set
{
_SelectedEmp = value;
OnPropertyChanged("SelectedEmp");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string Pname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Pname));
}
public RelayCommand AddEmpCommand { get; set; }
public ObservableCollection<Emp> Emps { get; set; }
public MainWindowViewModel()
{
Emps = new ObservableCollection<Emp>();
Emps.Add(new Emp { Ename = "홍길동", Job = "Salesman" });
Emps.Add(new Emp { Ename = "길길동", Job = "Clerk" });
Emps.Add(new Emp { Ename = "정길동", Job = "Manager" });
Emps.Add(new Emp { Ename = "박길동", Job = "Salesman" });
Emps.Add(new Emp { Ename = "설길동", Job = "Clerk" });
AddEmpCommand = new RelayCommand(new Action<object>(AddEmp));
}
public void AddEmp(object param)
{
Emps.Add(new Emp { Ename = param.ToString(), Job = "New Job" });
}
}
}
RelayCommand.cs
using System;
using System.Windows.Input;
namespace WPF14_Test
{
public class RelayCommand : ICommand
{
//지역변수, 델리게이트
Func<object, bool> canExecute;
Action<object> executeAction;
//생성자
public RelayCommand(Action<object> executeAction) : this(executeAction, null)
{
}
public RelayCommand(Action<object> executeAction, Func<object, bool> canExecute)
{
this.executeAction = executeAction ??
throw new ArgumentNullException("Execute Action was null for");
this.canExecute = canExecute; //이 예제에서는 NULL
}
public event EventHandler CanExecuteChanged;
//CanExecute 메서드는 명령을 사용 가능하게 하너가 사용 불가능하게 할때
//WPF에 의해 호출
//예제와 같은 사용자 정의 명령의 경우 CanExecute 메서드가 알아서
//호출되지는 않으므로
//CanExecuteChanged 이벤트를 CommandManager의 RequerySuggested 연결하면 된다.
public bool CanExecute(object parameter)
{
//사원 이름을 입력하지 않으면 Add 버튼은 비활성화 된다.
if (parameter?.ToString().Length == 0) return false;
//canExecute는 Func 델리게이트이고 본예제에서는 NULL로 넘어온다.
//그러므로 result는 true 가 리턴된다.
bool result = this.canExecute == null ? true : this.canExecute.Invoke(parameter);
return result;
}
//실제 실행될 멸령은 executionAction 델리게이트가 참조하고 있는
//MainWindowViewModel의 AddEmp 메서드이다.
public void Execute(object parameter)
{
this.executeAction.Invoke(parameter);
}
}
}
MainWindow.xaml
<Window x:Class="WPF14_Test.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:WPF14_Test"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="400">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
<TextBlock Text="사원 이름을 입력하세요."/>
<TextBox x:Name="txtName" Text="{Binding SelectedEmp.Ename}"/>
<Button Command="{Binding AddEmpCommand}"
CommandParameter="{Binding Text, ElementName=txtName}"
Content="Add"/>
<ListBox ItemsSource="{Binding Emps}"
SelectedItem="{Binding SelectedEmp}"
DisplayMemberPath="Ename"
x:Name="empListBosx"/>
<Label x:Name="label"
Content="{Binding SelectedItem,
ElementName=empListBosx}"
HorizontalAlignment="Center"
Height="40"
Width="150"/>
</StackPanel>
</Window>
실행 결과
728x90
'C# > WPF' 카테고리의 다른 글
19장. WPF MVVM, ListBox의 컬렉션 정렬, 필터링, 탐색 실습(ListCollectionView) (0) | 2021.05.17 |
---|---|
18장. WPF IValueConverter를 이용한 데이터바인딩, DataType이 다른 경우의 Data Binding (0) | 2021.05.15 |
13장. WPF 데이터바인딩 실습, INotifyPropertyChanged, PropertyChanged 이용 (0) | 2021.05.13 |
12장. WPF DataContext를 이용한 데이터바인딩 실습 (0) | 2021.05.13 |
12장. WPF DataContext를 이용한 데이터바인딩 실습 (0) | 2021.05.08 |
이 글을 공유하기