WPF, XAML

(동영상)C#, WPF Command 패턴의 이해 및 데이터바인딩 실습

 

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

 

n  전통적인 이벤트 기반 프로그래밍에서 컨트롤에 이벤트 핸들러 메소드를 코드 비하인드에서 연결하여 사용자의 이벤트를 처리했다. 그러나 이방식은 이벤트처리 핸들러를 재사용하거나 단위 테스트를 어렵게 한다.

n  XAML UI에서 버튼을 클릭시 MVVM에서는 Click 이벤트 핸들러를 이용하기 보다는 Commamd를 이용하기를 권장한다. 여러 버튼에서 하나의 Command를 공유할 수 있으므로 모든 컨트롤마다 Click 이벤트를 만드는 방법 보다는 효율적이기 때문이다.

n  WPF의 명령(Command) ICommand 인터페이스를 구현하여 만들며 ICommand Execute CanExecute라는 두 가지 메서드와 CanExecuteChanged 이벤트를 제공한다.

n  Execute 메서드는 실제 처리해야 하는 작업을 기술하고 CanExecute 메소드에서는 Execute 메소드의 코드를 실행할지 여부를 결정하는 코드를 기술한다. CanExecutefalse를 리턴하면 Execute 메소드는 호출되지 않는다.

n  CanExecute 메소드는 명령을 사용 가능하게 하거나 사용 불가능하게 할 때 사용되며 명령을 사용할 수 있는지 여부를 확인하기 위해 WPF에 의해 호출된다. 이 메소드는 키보드 GET포커스, LOST포커스, 마우스 업 등과 같은 UI 상호 작용 중에 대부분 발생한다.

n  사용자 정의 명령의 경우 CanExucute 메서드가 대부분의 시나리오에서 호출되지는 않으므로 어떤 조건에 따라 버튼을 활성화, 비활성화 해야 할 수도 있는데 ICommand 구현체에서 CanExecuteChanged 이벤트를 CommandManager RequerySuggested 이벤트에 연결하면 된다.

n  CanExecute 메소드가 호출되어 CanExecute의 상태가 변경되면 CanExecuteChanged 이벤트가 발생해야 하며,  WPFCanExecute를 호출하고 Command에 연결된 컨트롤의 상태를 변경한다.

 

// CanExecuteChanged 이벤트는 해당 ICommand에 바인딩 된

// 모든 명령 소스( : Button 또는 MenuItem)

// CanExecute에 의해 반환 된 값이 변경 되었음을 알린다.

 

// 커맨드 소스는 일반적으로 상태를 적절히 업데이트해야 하는데

// 예를 들면 CanExecute() false를 반환하면 버튼이 비활성화 된다).

 

// CommandManager.RequerySuggested 이벤트는 CommandManager가 명령 실행에

// 영향을 줄 수있는 변경 사항이 있다고 생각할 때마다 발생하며 이때마다

// CanExecute가 호출된다.

 

// 예를 들어, 이는 포커스의 변화 일 수 있는데. 이 이벤트가 많이 발생한다.

// 따라서 본질적으로 이 코드의 역할은 CommandManager명령 실행 기능이 변경되었다고 생각할 때마다(실제로 변경되지 않은 경우에도) CanExecuteChanged를 발생시키는 것이다.

public event EventHandler CanExecuteChanged

{

            add { CommandManager.RequerySuggested += value; }

            remove { CommandManager.RequerySuggested -= value; }

}

n  CommandManager.RequerySuggested 이벤트는 CanExecute 메서드를 강제로 실행할 수 있다.

n  CanExecuteChanged 이벤트는 CommandManagerRequerySuggested에 위임되어 모든 종류의 UI 상호작용을 통해 변경사항이 호출되는 정확한 알림을 제공한다.

n  RequerySuggested 이벤트의 CommandManager.InvalidateRequerySuggested()를 호출하여 CommandManager RequerySuggested 이벤트를 발생하도록 할 수도 있는데 다음 예제를 참조 하자.

356c10c7f7a281e4f3b5b6e43a63f643_1597660

n  Command 패턴에서는 몇가지 주체가 있는데 서비스를 요청하는 클라이언트(손님), 명령을 서술하는 Command Object(주문서), 명령을 요청하는 Command Invoker(웨이터), 특정 명령을 실제 처리하는 Command Receiver(Target, 요리사)가 있다.

[먼저 간단한 예제를 만들고 이해를 해보자.]

n  프로젝트 명 : CommandExam

n  [실행화면]

356c10c7f7a281e4f3b5b6e43a63f643_1597660

n  Model : [Emp.cs]

namespace CommandExam

{

    class Emp

    {

        public string Ename { get; set; }

        public string Job { get; set; }

public override string ToString()

        {

            return "[" + Ename + "," + Job + "]";

        }

    }

}

 

n  Command Object : [RelayCommand.cs]

 

 

using System;

using System.Windows.Input;

 

namespace CommandExam

{

    class RelayCommand : ICommand

    {

        #region Variables

        Func<object, bool> canExecute;

        Action<object> executeAction;    

        #endregion

 

        #region Construction/Initialization

        public RelayCommand(Action<object> executeAction) : this(executeAction, null)

        {

        }

 

        public RelayCommand(Action<object> executeAction, Func<object, bool> canExecute)

        {

            //if (executeAction == null) throw new ArgumentNullException("Execute Action was null for ICommanding Operation.");

            //this.executeAction = executeAction;

            this.executeAction = executeAction ?? throw new ArgumentNullException("Execute Action was null for ICommanding Operation.");

            this.canExecute = canExecute;

        }

        #endregion

 

        #region ICommand Member

//CommandManager.RequerySuggested 이벤트가 호출될 때마다 실행

// CanExecuteChanged 이벤트가 호출될 때마다 실행

        public bool CanExecute(object param)

        {

            // 사원이름을 입력하지 않으면 Add 버튼은 비활성화 된다.

            if (param?.ToString().Length == 0) return false;

            bool result = this.canExecute == null ? true : this.canExecute.Invoke(param);

            return result;

        }

 

        public void Execute(object param)

        {

            //System.Windows.MessageBox.Show(param.ToString());

            this.executeAction.Invoke(param);

        }

        public event EventHandler CanExecuteChanged

        {

            add { CommandManager.RequerySuggested += value; }

            remove { CommandManager.RequerySuggested -= value; }

        }

        #endregion

    }

}

 

n  Command Receiver : [MainWindowViewModel]

 

using System.Collections.ObjectModel;

using System.ComponentModel;

using System.Runtime.CompilerServices;

 

namespace CommandExam

{

    class MainWindowViewModel : INotifyPropertyChanged

    {

        public 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(AddEmp);

        }

 

        public void AddEmp(object param)

        {

            Emps.Add(new Emp { Ename = param.ToString(),Job="New Job"});

        }

    }

}

 

n  Client : [MainWindow.xaml], 여기서 ButtonCommand Invoker

 

<Window x:Class="CommandExam.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:CommandExam"

        mc:Ignorable="d"

        Title="MainWindow" SizeToContent="Width">

    <Window.DataContext>

        <local:MainWindowViewModel/>

    </Window.DataContext>

    <StackPanel>

        <TextBlock>사원 이름을 입력하세요.</TextBlock>

        <TextBox x:Name="txtName"  Text={Binding SelectedEmp.Ename}/>

        <Button Command="{Binding AddEmpCommand}" CommandParameter="{Binding Text, ElementName=txtName}">Add</Button>

        <ListBox ItemsSource="{Binding Emps}"

                 SelectedItem="{Binding SelectedEmp}"

                 DisplayMemberPath="Ename"

                 x:Name="empListBox"/>

        <Label x:Name="label" Content="{Binding SelectedItem, ElementName=empListBox}"

               HorizontalAlignment="Center"

               Height="40"

               Margin="10,0,0,0" Width="137"/>   

</StackPanel>

</Window>

 

n  위 예제에서 Command InvokerMainWindow.xaml의 버튼 이며  Command Target(Receiver)MainWindowViewModel, ConcreteCommand ObjectRelayCommand 객체이다.

 356c10c7f7a281e4f3b5b6e43a63f643_1597660

 


 

Comments