14장. WPF Command패턴, 데이터바인딩 DataBinding 개요, 실습

14장. WPF Command패턴, 데이터바인딩 DataBinding 개요, 실습

참조

목적

  • WPF Command패턴을 학습하고 데이터바인딩을 실습합니다.

WPF Command 패턴의 이해 및 Command, 데이터바인딩 실습

  • 전통적인 이벤트 기반 프로그래밍에서 컨트롤에 이벤트 핸들러 메서드를 코드 비하인드에서 연결하여 사용자의 이벤트를 처리했다. 그러나 이 방식은 이벤트처리 핸들러를 재사용 하거나 단위 테스트를 어렵게 한다.
  • XAML UI 에서 버튼을 클릭시 MVVM 에서는 Click 이벤트 핸들러를 이용하기 보다는 Command 를 이용하기를 권장한다. 여러 버튼에서 하나의 Command를 공유할 수 있으므로 모든 컨트롤마다 Click 이벤트를 만드는 방법 보다는 효율적이기 때문이다.
  • WPF의 명령(Command)은 ICommand 인터페이스를 구현하여 만들며 ICommand는 ExecuteCanExecute 라는 두 가지 메서드와 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>

실행 결과

1

728x90

이 글을 공유하기

댓글

Designed by JB FACTORY