Search

'Command'에 해당되는 글 3건

  1. 2009.02.26 WPF Command-Part4 : DelegateCommand (12)
  2. 2009.01.20 WPF Command -Part2 : RoutedCommand
  3. 2009.01.12 WPF Command - Part1 (7)

WPF Command-Part4 : DelegateCommand

프로그래밍 2009.02.26 22:24 Posted by 아일레프

(미루고 미루다 이제야 마지막 포스팅이네요. ㅜ.ㅜ)

지난 포스팅까지 RoutedCommand에 대해서 살펴보았습니다. RoutedCommand라는 이름이 붙은 이유는 Command Target을 찾기 위해RoutedEvent를 사용하기 때문이라는 것도 알 수 있었죠. 그런데 개발자들은 다른 유형의 Command에 대한 필요성을 느끼게 되었습니다.

적절한 UI를 구성하기 위해 개발자들이 먼저 생각하는 것은 바로 View와 논리의 분리일 것입니다. 현재 UI Design Pattern으로 널리 사용되는 MVC, MVP, MVVM의 경우도 View와 Model, Logic의 결합도를 줄임으로써 확장성을 늘리고 테스트 하기 쉬운 구조를 만들어 낸 바 있습니다. 그런데 RoutedCommand의 경우는 RoutedCommand가 CommandTarget을 View의 Visual Tree내에서 찾기 때문에 View와 Logic을 완전히 분리하기 어렵습니다. 때문에 여러 개발자들은 다른 Command에 대한 필요성을 느끼게 되었고 그 결과 DelegateCommand라는 녀석을 만들어 냈습니다. 그리고 이 DelegateCommand는 Composite Application Guidance for WPF에 포함되게 됩니다. MSDN Magazine에 소개된 DelegateCommand의 구현 코드는 다음과 같습니다.

public class StringDelegateCommand : ICommand {
  Action<string> m_ExecuteTargets = delegate { };
  Func<bool> m_CanExecuteTargets = delegate { return false; };
  bool m_Enabled = false;
  public bool CanExecute(object parameter) {
    Delegate[] targets = m_CanExecuteTargets.GetInvocationList();
    foreach (Func<bool> target in targets) {
      m_Enabled = false;
      bool localenable = target.Invoke();
      if (localenable) {
        m_Enabled = true;
        break;
      }
    }

    return m_Enabled;
  }
  public void Execute(object parameter) {
    if (m_Enabled)
      m_ExecuteTargets(parameter != null ? parameter.ToString() : null);
  }
  public event EventHandler CanExecuteChanged = delegate { };
  ...
}
간단하지요? 위 녀석은 Command Parameter로 string 객체를 전달하기 때문에 StringDelegateCommand라는 이름이 붙었습니다. 좀 더 일반적인 녀석을 만들어볼까요? 다음과 같이 만들 수 있겠네요.
public class DelegateCommand<T> : ICommand
{
        Action<T> executeTargets = delegate { };
        Func<bool> canExecuteTargets = delegate { return false; };
        bool m_Enabled = false;
        public bool CanExecute(object parameter)
        {
            Delegate[] targets = canExecuteTargets.GetInvocationList();
            foreach (Func<bool> target in targets)
            {
                m_Enabled = false;
                bool localenable = target.Invoke();
                if (localenable)
                {
                    m_Enabled = true;
                    break;
                }
            }
            return m_Enabled;
        }
        public void Execute(object parameter)
        {
            if (CanExecute(parameter) == true)
                executeTargets(parameter != null ? (T)parameter : default(T));
        }
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        public event Action<T> ExecuteTargets
        {
            add
            {
                executeTargets += value;
            }
            remove
            {
                executeTargets -= value;
            }
        }
        public event Func<bool> CanExecuteTargets
        {
            add
            {
                canExecuteTargets += value;
            }
            remove
            {
                canExecuteTargets -= value;
            }
        }
    }
위 녀석은 Generic으로 구현된 DelegateCommand입니다. Command Parameter로 사용하기 위한 Type을 T로 지정해 사용하면 됩니다. 그리고 이 경우 ExecuteTargets에 Handler를 등록함으로써 하나의 Command이 실행되었을 때 여러 개의 Command Target이 실행되게 할 수 있습니다.

자, 다음과 같은 질문을 해봅시다. 왜 굳이 Command를 사용하는 것이 좋을까요? 만약 특정 버튼을 눌렀을 때 "Save"란 일을 하고 싶다면 이것은 충분히 이벤트 만으로 가능해 보이는데 말입니다. 다음과 같이 말이죠. 

<Button Click="Button_Click" Content="Save"/>
private void Button_Click(object sender, RoutedEventArgs e)
{

    Save();
}


위와 똑 같은 일을 하는 녀석을 Command로 만들어 보면 다음과 같을 것입니다.

<Button Command="{Binding SaveCommand}" Content="Save"/>
public Window1()
{
      InitializeComponent();
     
this.DataContext = this;
}
public ICommand SaveCommand
{
      
getreturn saveCommand; }
} 
어떤 차이가 있는지 보이십니까? 언뜻 보면 이벤트나 Command나 거기서 거기 같아 보입니다. 그러나 중요한 사실이 있습니다. Application을 구현할 때는 Business Logic을 변경할 때 보다 View를 변경하는 일이 더욱 많다는 사실입니다. Event로 Save Button을 만들었다고 합시다. 만 약 그 후에 고객이 Save Menu를 만들어 달라고 요청했다고 가정해 보겠습니다. 이 경우 event를 사용했다면 각 메뉴항목마다 이벤트를 또 걸어주어야 합니다. 만약 Command를 사용했다면? 단순히 xaml만을 교체해 Command를 Binding하는 것으로 끝나게 될 것입니다. Command로 느슨하게 결합되어 있기 때문입니다.

또한 제가 좋아하는 Command의 강력한 기능은 CanExecute입니다. 위 Save버튼이 활성화 되기 위한 조건을 CanExecute에 만들어 놓으면 모든 경우에 대해 버튼의 활성화 여부에 대해 걱정할 필요가 없습니다. 만약 Login 창을 만든다고 가정해 볼까요? UserID와 Password를 입력할 수 있는 TextBox가 필요하겠네요. 그리고 Login버튼이 있을 것입니다. 이 Login버튼이 활성화 될 수 있는 조건은 UserId와 Password TextBox에 적당한 Text가 쓰여졌을 때일 것입니다. 만약 Command를 사용하지 않는다면? 우리는 사용자가 새로운 문자를 UserId, Password에 입력할 때 마다 Login버튼의 활성화 여부를 확인해야 할 것입니다. 굉장히 우울한 사실이죠.

Command는 또한 Model, View, Logic을 분리하기 위해 많이 사용되는 MVP, MVVM Pattern에도 사용됩니다. MVVM패턴의 경우 직접 UIElement를 만드는 것이 아니라 보통 Template를 활용하는데 이 때 Template내에서 event를 사용하는 것이 아니라 Command를 바인딩 해주는 것이죠. 이 MVVM Pattern을 사용하면 Compile을 다시 하지 않고도 View를 교체하는 것이 가능해집니다. Event를 사용했을 때는 이와 같은 효과를 얻을 수 없습니다. Command Invoker와 Command Target이 Binding을 통해 느슨하게 결합되었기에 얻을 수 있는 이점이라 할 수 있습니다. 간단히 Person정보를 저장하는 녀석을 만들어 보겠습니다. 먼저 Model입니다.

public class PersonModel : INotifyPropertyChanged
{
        private string name;
        private string address;
        public string Name
        {
            set 
            {
                name = value
                if(PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Name"));
                }
            }
            get 
            {
                return name; 
            }
        }
        public string Address
        {
            set
            {
                address = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Address"));
                }
            }
            get
            {
                return address;
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void Clear()
        {
            Name= "";
            this.Address= "";
        }
    }

ViewModel은 다음과 같을 것입니다.
public class PersonViewModel
{
        PersonModel personModel = new PersonModel();
        public PersonModel PersonModel
        {
            get { return personModel; }
        }
        DelegateCommand<string> saveCommand; 
        public DelegateCommand<string> SaveCommand
        {
            get { return saveCommand; }
        } 
       
public PersonViewModel()
       
{
            saveCommand = new DelegateCommand<string>();
            saveCommand.CanExecuteTargets += () => !string.IsNullOrEmpty(personModel.Name) && !string.IsNullOrEmpty(personModel.Address);
            saveCommand.ExecuteTargets += s => this.Save(s);
        }
        private void Save(string parameter)
        {
            //Save Operation 

            //Clear PersonModel
           PersonModel.Clear();
        }
    }

View를 만들어 볼까요? 귀찮아서 그냥 윈도우로 하겠습니다.
<Window.Resources>
        <DataTemplate  DataType="{x:Type local:PersonViewModel}" >
            <StackPanel>
                <TextBox Text="{Binding PersonModel.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                <TextBox  Text="{Binding PersonModel.Address, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                <Button Content="Save" Command="{Binding SaveCommand}"/>
            </StackPanel>
        </DataTemplate>
        <local:PersonViewModel x:Key="PersonViewModel"/>
    </Window.Resources>
    <Window.Content>
        <StaticResource ResourceKey="PersonViewModel"/>
    </Window.Content>

위와 같이 Template를 사용하기 싫으신 분도 있을 텐데 그러면 다음과 같이 만들어 주면 됩니다. Template를 써야 MVVM Pattern이 되는 것은 아니니까요.  

    <
Window.Resources>
        <local:PersonViewModel x:Key="PersonViewModel"/>
    </Window.Resources>
    <Window.Content>
        <StackPanel DataContext="{StaticResource PersonViewModel}">
            <TextBox Text="{Binding PersonModel.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBox  Text="{Binding PersonModel.Address, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Content="Save" Command="{Binding SaveCommand}"/>
        </StackPanel>
    </Window.Content>
엄청나게 간단한 예제네요^^;; 쉽지요?

Command에 대한 Posting을 이제 끝내려고 합니다. . 생각했던 것 외로 제가 확실하게 몰랐던 부분이 있어 시간이 더 걸렸던 것 같습니다. 그러나 Command에 대한 논의는 이것으로 끝나지 않을 것 같습니다. 사실, 정확하게 WPF가 RoutedCommand를 어떻게 처리하고 CommandManager의 역할은 아직 잘 모르겠습니다. 나중에 살펴보고 제가 명확하게 알았다고 인지했을 때 여러분께 공유하도록 하겠습니다.

사족 :
사실, 예제로 Login을 들려고 했었는데 심각한 문제가 있었습니다. Password를 넣기 위해 사용되는 PasswordBox의 Password Property가 DependencyProperty가 아니라 Binding을 지원하지 않는 문제였지요. 잠시 패닉상태였습니다. ㅋ 이 부분에 대해 google에 Search를 해보니 멋진 글이 있어 소개하려 합니다. Attached Property를 이용해 PasswordBox의 Password를 Binding하는 예가 담겨있습니다. 사람들, 참 머리 좋아요~ WPF PasswordBox and Data binding
그리고 MSDN Magazine의 WPF Apps With The Model-View-ViewModel Design Pattern입니다.
마지막으로 The Build Your Own CAB Series Table of Contents 도 실로 멋지죠.

신고

WPF Command -Part2 : RoutedCommand

프로그래밍 2009.01.20 21:02 Posted by 아일레프
  • 이 포스트는 기본적인 RoutedCommand의 바인딩과 CommandBinding을 통한 Handler 등록 방법을 알고 있을 때 이해하기 쉽습니다.

이전 Post에서 만든 Command 객체는 치명적인 문제를 가지고 있었죠. 그 문제를 살펴보면

  1. Command Object와 Command Target이 분리되어있지 않다.
  2. Command를 실행했을 때의 작업을 수정하려면 Command 객체를 수정하고 재 컴파일 해야한다.

결국 1,2번 문제 모두가 Command Object와 Command Target이 분리되어있지 않기 때문에 생기는 문제입니다. Command Object와 Target을 분리하는 가장 간단한 방법은 무엇일까요? 예, 맞습니다. 바로 Event를 사용하는 것이죠. Command Object가 명령을 받았을 때의 행동을 직접 정의하는 것이 아니라 단지 Command가 실행되었을 때 Command Object가 이벤트를 발생시키고 해당 이벤트를 구독하는 녀석이 실제 명령을 수행하게 하는 것입니다. 그리고 이 이벤트를 구독하는 녀석이 바로 Command Target이 되는 것입니다. 누구나 쉽게 생각할 수 있는 이 방법을 통해 WPF는 RoutedCommand라는 녀석을 미리 구현해 놓았습니다. 그런데 저에게는 그렇게 쉽지 않더군요 ^^;; 

RoutedCommand라는 녀석의 이름답게 RoutedCommand는 RoutedEvent를 사용합니다. RoutedEvent를 사용해 자신의 Command Target을 찾는 것입니다. 다음과 같은 Visual Object Tree를 생각해봅시다.

 Button에 특정 RoutedCommand가 바인딩 되어있다고 합시다.(Button이 Invoker가 됩니다.) 위Button이 눌리면 RoutedCommand 에서 ExecuteEvent를 발생시킵니다. 이렇게 발생된 이벤트는 Button -> Grid -> Windows 순으로 Routing되는 것입니다. 좀 더 일반적으로 말하자면 Command Invoker부터 Visual Tree Root까지 ExecuteEvent가 Routing됩니다.

여기까지는 쉽죠? 예 쉽습니다.

그러면 적절한 시기에 Command 객체의 "CanExecute" 메소드에 대해 알아볼까요? 이 녀석은 일반적으로 Command가 현재 실행 가능한지 가능하지 Command Invoker에게 알려주는 역할을 합니다. 예를 들어 RoutedCommand에 바인딩 된 Command Invoker객체는 자신이 Render되었을 때 RoutedCommand의 CanExecute 메소드를 실행시킨 뒤 그 결과가 true라면 자신을 활성화 시키고 그렇지 않으면 비활성화 시킵니다.

여기까지도 쉬운가요? 쉬웠으면 좋겠습니다.

 그런데 RoutedCommand에서 사용되는 RoutedEvent와 일반 RoutedEvent와의 차이점은 여기서부터 시작됩니다. 이것에 대해 설명하기 위해 이전 포스트에서 사용했던 맥도날드의 Command Pattern을 살펴보기로 하죠.  

CanExecute

자, 주문하는 사람은 Command Invoker, 웨이터는 Command Object, 햄버거 만드는 사람은 Command Target입니다. 주문하는 사람은 웨이터에게 먼저 이렇게 물어볼 것 입니다. "빅맥 햄버거를 먹을 수 있나요?" 웨이터는 현재 빅맥 햄버거를 만들 수 있는 지 알아야 합니다. 때문에 햄버거 만드는 사람들에게 물어보겠죠. "지금 빅맥 햄버거를 만들 수 있는 사람 있습니까?" 중요한 것은 빅맥 햄버거를 만들 수 있는 사람이 적어도 한 명 있으면 주문하는 사람에게 "예, 지금 햄버거를 만들 수 있어요"라고 말한다는 사실 입니다.

자, 이전에 살펴본 Visual Object Tree입니다. 이전과 마찬가지로 Button에 특정 RoutedCommand가 바인딩 되어있다고 가정해 보겠습니다. Button이 Render되면 자신이 실행 가능한 녀석인지 알기 위해 RoutedCommand의 CanExecute 메소드를 실행합니다. 그리고 RoutedCommand는 CanExecute 이벤트를 발생시키죠. 이 이벤트의 Handler의 시그니쳐는 다음과 같습니다.

void CanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)

이 이벤트는 Button -> Grid -> Windows로 타고 올라갑니다. 그리고 만약 Handler 중에서 CanExecuteRoutedEventArgs의 CanExecute Property를 true로 만드는 녀석이 적어도 하나라도 있다면 RoutedCommand는 Button에게 "난 실행될 수 있는 Command야"라고 알려주게 됩니다. 적어도 하나라는 사실을 꼭 기억해 주시기 바랍니다.  

Execute

자 이제 맥도날드에서 주문을 해볼까요? 여러분은 웨이터(Command Object)를 통해 빅맥을 주문할 수 있다는 사실을 알았습니다.(CanExecute 메소드를 통해) 여러분이 빅맥을 주문하면 웨이터는 햄버거를 만드는 사람에게 햄버거를 만들라고 할 것입니다. 주의 해야하는 사실은 웨이터가 단 한 명의 사람에게 햄버거를 만들라고 해야 한다는 사실입니다. 만약 여러 명에게 햄버거를 만들라고 한다면 주문한 햄버거는 하나인데, 여러 개의 햄버거가 만들어져 있겠죠? RoutedCommand도 마찬가지 입니다. Button이 눌러지면 RoutedCommand의 Execute메소드가 실행되고 RoutedCommand는 ExecuteEvent RoutedEvent를 Command Invoker Node부터 발생시킬 것입니다.

이 경우도 Button -> Grid -> Windows순으로 ExecuteEvent가 발생하게 됩니다. 중요한 것은 특정 Command Target의 ExecuteEventHandler가 해당 Command를 처리한다면 e.handled를 true로 만들어 더 이상 Routing을 진행하지 않는 다는 것입니다. 즉 단 하나의 ExecuteEvent Handler만 실행된다는 사실이죠.

MSDN Magazine의 Understanding RoutedEvent and Command는 이 것을 다음과 같이 설명하고 있습니다.

The difference between routed commands and routed events is in how the command gets routed from the command invoker to the command handler. Specifically, routed events are used under the covers to route messages between the command invokers and the command handlers (through the command binding that hooks it into the visual tree).

There could be a many-to-many relationship here, but only one command handler will actually be active at any given time. The active command handler is determined by a combination of where the command invoker and command handler are in the visual tree, and where the focus is in the UI. Routed events are used to call the active command handler to ask whether the command should be enabled, as well as to invoke the command handler's Executed method handler.

위의 굵은 글씨로 표시된 부분이 바로 이를 설명하고 있는 것입니다.

위의 글에는 몇 가지 추가적인 내용이 있는데요, CommandHadler를 선택할 때 CommandInvoker의 VisualTree내의 위치와 현재 Focus가 어느 UI에 있는 지를 고려한다고 합니다. 이 부분은 다음 포스팅에서 설명하도록 하겠습니다.

신고

WPF Command - Part1

프로그래밍 2009.01.12 19:18 Posted by 아일레프

들어가기 전에

얼마 전에 MSDN MagazineUnderstanding RoutedEvent and Command라는 제목의 article이 올라왔습니다. RoutedEvent Command는 자주 사용했던 기능이기에 깊게 읽어보지는 않았습니다. “이 정도는 나도 알고 있다고라는 마음이었죠. 몇 줄 읽어보고 말았습니다..

그 후 Composite Application Guidance for WPF를 공부하기 시작했는데 “DelegateCommand<T>”라는 녀석이 나오더군요. 정말 전혀 모르겠더군요. Command에 대해 정확한 개념이 없다는 것을 알게 된 후 MSDN Magazine Command 부분을 다시 읽어 보았는데 영어가 암호문처럼 느껴지면서 적지 않은 절망감을 느꼈습니다. Article을 이해하기 위해 출퇴근 하는 지하철 안에서 몇 번을 읽었는지 모릅니다. 그리고 집에 와 Command Pattern에 대한 자료와 예제를 반복해 보았습니다. 만약 여러분이 저와 같은 고생을 하지 않고 간단히 WPF Command를 책하나 혹은 MSDN만으로 이해했다면 여러분을 낳아주신 부모님께 감사드릴 일입니다. 그리고 이 Post를 더 이상 읽을 필요도 없을 것입니다.

그러나 Command라는 노래만을 듣고 악보를 그려낼 만할 음감은 없지만 알고 싶다는 호기심과 끈기가 있는 분이라면 이 Post를 읽어주셨으면 합니다.(저는 이런 분들을 더 좋아합니다.) 여러분께 조금의 도움은 될 수 있을 것입니다.

서론은 그만두고, 본격적으로 포스팅을 하도록 하겠습니다. 바라건대 기본적인 RoutedCommand 사용법(CommandBinding, InputBinding) RoutedEvent를 공부하신 후 Magazine Article Understandeing RoutedEvent and Command을 읽어보시기 바랍니다. 그리고 궁금한 부분이나 도무지 풀리지 않을 때 이 포스트를 읽으시면 될 것 같습니다. 그럼 시작해보겠습니다.(해당 Article이 이해가 완전히 되시는 분은 이 포스트을 읽지 않으셔도 됩니다.)

 

Command Overview

RoutedCommand에 대해 알아보기 전에 Command에 대해 알아보도록 하겠습니다. Command1-특정 작업을 서술하는 녀석 그리고 그 2-특정 작업을 요청하는 녀석과 3-특정 작업을 처리하는 녀석을 분리하기 위해 그 필요성이 생겼습니다. ( 1,2,3번은 중요합니다. 모든 커맨드를 사용할 때 항상 1,2,3번에 해당하는 녀석이 무엇인지 마음속으로 추측 해보시기 바랍니다.)

보통 특정 작업을 서술하는 녀석을 Command Object라고 합니다. 그리고 특정 작업을 요청하는 녀석은 Command Invoker 혹은 Command Source, 특정 작업을 처리하는 녀석을 Command Target이라고 합니다. 정리하고 넘어가죠.

1.     특정 작업을 서술하는 녀석 – Command Object

2.     특정 작업을 요청하는 녀석 – Command Invoker, Command Source

3.     특정 작업을 처리하는 녀석 – Command Target


 여러분이 맥도널드에서 특정 햄버거를 주문한다고 가정해보죠. 여러분은 Command Invoker가 되는 것이고, 햄버거 주문을 받는 웨이터는 Command Object, 직접 햄버거를 만드는 사람은 Command Target이 되는 것입니다. 여러분이 햄버거를 주문했을 때 그 내부에서 햄버거를 어떻게 만드는 지 전혀 알지 못해도 되는 이유는 맥도널드가 이 Command Pattern을 사용하고 있기 때문입니다.

 

잠깐 MSDN Article의 예를 보고 넘어가도록 하겠습니다.

<Button Command=”ApplicationCommands.Save”>SAVE</Button>

 

<UserControl …>

           <UserCotrol.CommandBindings>

                     <CommandBinding Command=”ApplicationCommands.Save”

                       CanExecute=”OnCanExecute” Executed=”OnExecute”/>

           </UserControl.CommandBindings>

 

Public class MyControl : UserControl

{

           Private void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)

        {

                   e.CanExecute = true;

           }

 

           Private void OnExecute(object sender, ExecuteRoutedEventArgs e)

        {

                   DoSaveOperation();

           }

           Private void DoSaveOperation()

        {

                  

           }

}

위 예에서 1,2,3번에 해당하는 것이 무엇인지 알아보도록 할까요?

1.     Command Object : “ApplicationCommands.Save” static Command 객체

2.     Command Invoker : Button 객체

3.     Command Target : MyControl 객체

 
 맞추셨습니까? 너무 쉽다 구요? ^^;;; 다행입니다. 이것이 기본입니다. 다시 강조 드리지만 반드시 1,2,3번에 해당하는 녀석이 무엇인지 머리 속으로 쭉 생각하시기 바랍니다.

더 나가서 위 예제가 무엇을 하는 녀석인지 볼까요? WPF에는 미리 정의된 Command객체들이 있습니다. ApplicationCommand.Save도 그 중 하나입니다. Button을 누르면(Invoker가 작업을 요청하면) ApplicationCommand.Save Command Target을 찾습니다.(어떻게 찾는지는 다음 Posting에 설명드리겠습니다.) Command Target으로 MyControl 객체가 선택되고 DoSaveOperation()을 실행합니다.

 보시다시피 작업을 요청하는 녀석과 작업을 기술하는 녀석, 작업을 실행하는 녀석이 분리되어있습니다. 이런 것을 CommandPattern이라 부른답니다.

 

Command 만들어보기

위 예제의 세부적인 내용은 일단 미뤄두고 Command객체를 만들어 보도록 하겠습니다. Command 객체를 만들기 위해서는 ICommand라는 Interface를 상속받으면 됩니다. Interface는 이렇게 생겼습니다.

public interface ICommand

    {

event EventHandler CanExecuteChanged;

bool CanExecute(object parameter);

void Execute(object parameter);

    }

CanExecuteChanged이벤트와 CanExecute메소드는 잘 모르더라도 Execute 메소드의 역할은 알 것 같죠? 아마도 Invoker가 이 Command Invoke하면 Execute가 실행될 것 같습니다. 이 정도의 정보만으로도 충분합니다. 한번 AddCommand라는 녀석을 만들어보겠습니다.

public class AddCommand : ICommand

    {

        private string description ="Add";

        public string Description

        {

            get { return description; }

        }

        public bool CanExecute(object parameter)

        {

            return true;

        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)

        {

            System.Diagnostics.Debug.WriteLine("Add명령 실행");

        }

    }

CanExecute메소드는 일단 true를 반환하게 하고 Execute 메소드는 “Add명령 실행이라는 문자열을 출력하게끔 했습니다. 그리고 Description이라는 Property를 만들어 Command의 내용을 기술하게 했습니다. , 그럼 이 Command를 사용해볼까요?

<Window x:Class="CommandTest.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:loacl="clr-namespace:CommandTest"

    Title="Window1" Height="300" Width="300">

    <Window.Resources>

        <loacl:AddCommand x:Key="AddCommand"/>

    </Window.Resources>

    <Button Command="{StaticResource AddCommand}"

            Content="{Binding RelativeSource={RelativeSource Self}

                              ,Path=Command.Description}"></Button>

</Window>

먼저 Command객체를 XAML로 생성하기 위해 local이라는 prefix namespace를 추가했습니다. 그리고 객체를 생성해 Resources에 등록 했습니다. . 제가 여러분을 너무 무시하나요? 나머지는 그냥 스킵 하겠습니다. 하여간 위 프로그램을 실행하면

 

위와 같은 창이 생기게 되고 Add버튼을 누르면 “ADD 명령 실행이라는 문자열이 Output창에 출력될 것 입니다. 훌륭하군요. 제가 생각하는 결과가 실행되었습니다. 혹시 글을 읽으시면서 Command Object, Command Invoker, Command Target이 무엇인지 생각해보셨습니까? 그랬다면 여러분은 멋진 분입니다. 1,2,3에 해당하는 것이 무엇이지 알아봅시다.

1.     Command Object : AddCommand 객체

2.     Command Invoker : Button 객체

3.     Command Target : AddCommand 객체

? Command Object Command Target이 같네요. 분리되지 않았습니다. 게다가 방금 제가 만든 Command객체는 Command를 실행한 후의 작업이 항상 “Add 명령 실행이라는 문자열을 실행하게 됩니다. 이 작업을 수정하려면 Command객체를 수정하고 다시 컴파일 해야겠네요. 이건 전혀 훌륭하지 않습니다. 망했습니다. .

n  문제 : 어떻게 Command Object Command Target을 분리하지?

다음 장에는 WPF에서 RoutedCommand를 사용해 어떻게 Command Object Command Target을 분리하는 지 알아보도록 하겠습니다. 여러분도 이 문제를 어떻게 해결할 지 생각해보세요 ^^;;


신고
TAG Command, WPF


 

티스토리 툴바