프리즘 4.1 도움말 번역입니다.



MVVM 패턴 구현하기



모델-뷰-뷰모델( MVVM ) 패턴은 응용프로그램의 비즈니스 로직과 프리젠테이션 로직을 그것의 유저 인터페이스( UI )와 명확하게 분리하는 데 도움을 줍니다. 응용프로그램 로직과 UI 를 깔끔하게 분리하는 상태를 유지하는 것은 다양한 개발 및 설계 이슈들을 제시하는데 도움을 주며, 당신의 응용프로그래이 더 쉽게 테스트되고, 유지보수되고, 진화될 수 있도록 만들어 줄 수 있습니다. 또한 코드 재사용 가능성을 많이 증대시켜 줄 수 있으며, 개발자와 UI 디자이너가 응용프로그램에서 그들의 주요 관심사를 개발하는데 있어 더 쉽게 협력할 수 있도록 해 줍니다.


MVVM 패턴을 사용하면, 응용프로그램의 UI 와 기저에 깔린 프리젠테이션 로직 및 비즈니스 로직이 세 개의 분리된 클래스로 나뉩니다: 뷰는 UI 와 UI 로직을 캡슐화합니다; 뷰모델은 프리젠테이션 로직과 상태를 캡슐화합니다. 모델은 응용프로그램의 비즈니스 로직과 데이터를 캡슐화합니다.


프리즘은 실버라이트와 WPF 응용프로그램에서 MVVM 패턴을 구현하는 방법을 보여주는 샘플들과 참조 구현들을 포함하고 있습니다. 또한 프리즘 라이브러리는 당신의 응용프로그램에서 그 패턴을 구현하는데 도움을 줄 수 있는 기능들도 제공합니다. 이 기능들은 MVVM 패턴을 구현하기 위한 가장 공통적인 경험들을 포함하고 있으며, testability 를 지원하도록 설계되었으며, 익스프레션 블렌드와 비주얼 스튜디오에서 잘 동작하도록 설계되었습니다.


이 챕터는 MVVM 패턴에 대한 개요를 제공하며, 그것의 기본적인 특징들을 구현하는 방법에 대해 기술합니다. 챕터 6 은 프리즘 라이브러리를 사용해 더욱 진보된 MVVM 시나리오를 구현하는 방법을 기술합니다.



클래스의 책임과 특징들



MVVM 패턴은 프리젠테이션 모델( PM ) 패턴과 가까운 변종이며, 데이터 바인딩, 데이터 템플릿, 커맨드, 비헤이비어( behavior ) WPF 와 실버라이트의 핵심 기능들의 일부를 활용하기 위해서 최적화되어 있습니다.


MVVM 패턴에서, 뷰는 UI 와 UI 로직을 캡슐화하니다. 뷰 모델은 프리젠테이션 로직과 상태를 캡슐화합니다. 그리고 모델은 비즈니스 로직과 데이터를 캡슐화하니다. 뷰는 뷰 모델과 데이터 바인딩, 커맨드, 변경 통지 이벤트 등을 통해 상호작용합니다. 뷰 모델은 모델에 대한 업데이트를 질의하고 관찰하고 조직화하는데, 뷰의 출력에 필요한 형태로 데이터를 변환하고 유효화하고 그룹화합니다.


다음 그림은 세 개의 MVVM 클래스들과 그것들의 상호작용을 보여 줍니다.


MVVM 클래스들과 그것들의 상호작용




모든 개별 프리젠테이션 패턴들과 유사하게, MVVM 패턴을 사용하는 것의 핵심은 효율적으로 응용프로그램의 코드를 올바른 클래스들에 생성하는 적절한 방식을 이해하는데 달려 있으며, 이 클래스들이 다양한 시나리오에서 상호작용하는 방식을 이해하는 것에 달려 있습니다. 다음 섹션들에서는 MVVM 패턴의 각 클래스들의 책이과 특징들에 대해 기술합니다.



뷰 클래스


뷰의 책임은 스크린에서 사용자가 보게 되는 외형과 구조를 정의하는 것입니다. 이상적으로 볼 때, 뷰의 코드-비하인드는 단지 InitializeComponent 메서드를 호출하는 생성자만을 포함하는 것입니다. 어떤 경우에, 코드-비하인드는 복잡한 애니메이션과 같이 XAML 에서 표현하기 어렵거나 비효율적인 비주얼 비헤이비어를 구현하는 UI 로직 코드를 포함할 수 있습니다. 혹은 뷰의 일부인 비주얼 엘리번트들이 직접적으로 조작될 필요가 있는 코드가 있을 때 UI 로직 코드를 포함할 수 있습니다. 단위 테스트를 필요로 하는 뷰에 로직 코드를 배치해서는 안 됩니다. 일반적으로, 뷰의 코드-비하인드에 있는 로직 코드는 UI 오토메이션 테스팅 접근법을 통해 테스트될 수 있을 것입니다.


실버라이트와 WPF 에서, 뷰의 데이터 바인딩 표현식은 그것의 데이터 칸텍스트에 대해 평가됩니다. MVVM 에서, 뷰의 데이터 칸텍스트는 뷰 모델로 설정됩니다. 뷰 모델은 뷰 가 바인딩할 수 있는 프라퍼티나 커맨드를 구현합니다. 그리고 변경 통지 이벤트를 통해 어떠한 상태 변경을 뷰에 통지합니다. 일반적으로 뷰와 그것의 뷰 모델은 1 대 1 관계를 가집니다.


보통, 뷰는 Control 상속 클래스이거나 UserControl 상속 클래스입니다. 그러나, 어떤 경우에는, 뷰가 데이터 템플릿을 통해 표현될 수도 있습니다. 그것은 오브젝트가 출력될 때 오브젝트를 가시적으로 표현하기 위해서 사용되는 UI 엘리먼트들을 지정합니다. 데이터 템플릿을 사용하면, 비주얼 디자이너가 뷰 모델이 렌더링될 방식을 쉽게 결정하거나, 기반 오브젝트 자체를 변경하지 않고도 그것의 가시적인 표현이나 그것을 출력하기 위해서 사용될 컨트롤의 동작을 변경할 수 있습니다.


데이터 템플릿은 코드-비하인드를 가지지 않는 뷰라고 생각할 수 있습니다. 그것들은 UI 에서 오브젝트가 출력되기를 요청받을 때마다 특정 뷰 모델 타입을 바인딩하도록 설계되어 있습니다. 런타임에, 그 뷰는 데이터 템플릿에 정의되어 있는대로 자동적으로 인스턴스화될 것이며, 그것의 데이터 컨텍스트가 관련 뷰 모델로 설정될 것입니다.


WPF 에서, 당신은 데이터 템플릿을 응용프로그램 레벨에서 뷰 모델 타입과 연관시킬 수 있습니다. 그러면 WPF 는 오브젝트가 UI 에서 출력될 때마다 특정 타입의 뷰 모델 오브젝트들에 대해 자동적으로 데이터 템플릿을 적용하게 될 것입니다. 이는 묵시적 데이터 템플리팅이라고 알려져 있습니다. 실버라이트에서, 당신은 오브젝트를 출력할 컨트롤 안에 뷰 모델 오브젝트를 위한 데이터 템플릿을 명식적으로 지정해야만 합니다. 두 경우 모두, 데이터 템플릿은 그것을 사용하는 컨트롤 내부에 인라인으로 정의되거나, 부모 뷰의 외부의 리소스 딕셔너리에 정의되거나, 뷰의 리소스 딕셔너리에 선언적으로 병합될 수 있습니다.


요약하면, 뷰는 다음과 같은 핵심 특징들을 가집니다:

  • 뷰는 윈도우, 페이지, 유저 컨트롤, 데이터 템플릿과 같은 비주얼 엘리먼트입니다. 뷰는 뷰에 포함된 컨트롤들을 정의하고, 그것들의 비주얼 레이아웃과 스타일링을 정의합니다.
  • 뷰는 DataContext 프라퍼티를 통해 뷰 모델을 참조합니다. 뷰 내부의 컨트롤들은 그 프라퍼티들에 바인딩된 데이터이며, 뷰 모델에 의해 노출된 커맨드들입니다.
  • 뷰는 뷰와 뷰 모델 간의 데이터 바인딩 동작을 커스터마이즈할 수 있습니다. 예를 들어, 뷰는 value converter 를 사용해 데이터를 UI 에 출력할 형식으로 변환하거나, 유효화 규칙을 사용하여 사용자에게 입력 데이터에 대한 부가적인 유효성 검사를 제공할 수도 있습니다.
  • 뷰는 뷰 모델에서의 상태 변화나 UI 와 사용자의 인터랙션에 의해 발생된 애니메이션이나 트랜지션과 같은 UI 비주얼 비헤이비어를 정의하고 다룹니다.
  • 뷰의 코드-비하인드는 XAML 에서 표현하기 어려운 비주얼 비헤이비어를 구현하기 위해서 UI 로직을 정의하거나, 뷰에서 정의된 특정 UI 컨트롤들에 대한 직접적인 참조를 요청할 수도 있습니다.


뷰 모델 클래스


MVVM 패턴에서 뷰 모델은 프리젠테이션 로직과 뷰를 위한 데이터를 캡슐화합니다. 그것은 뷰에 대한 직접적인 참조나 뷰의 특정 구현이나 타입에 대한 정보를 가지고 있지 않습니다. 뷰 모델은 뷰가 바인드할 수 있는 프라퍼티와 커맨드를 구현합니다. 그리고 변경 통지 이벤트를 통해 어떠한 상태 변경을 뷰에 통지합니다. 뷰 모델이 제공하는 프라퍼티와 커맨드는 UI 에 의해 제공되는 기능을 정의하지만, 뷰는 그 기능이 렌더링되는 방식을 결정합니다.


뷰 모델은 뷰와 그것이 요구하는 모델 클래스들 간의 상호작용을 조직할 책임이 있습니다. 일반적으로 뷰 모델과 모델 클래스들 간에는 1 대 다 관계가 존재합니다. 뷰 모델은 뷰에 모델 클래스를 직접적으로 노출하는 것을 선택할 것이며, 그래서 뷰 내의 컨트롤들은 그것들에 직접적으로 데이터를 바인딩할 것입니다. 이 경우, 모델 클래스들은 데이터 바인딩과 적절한 변경 통지 이벤트들을 지원하도록 설계될 필요가 있을 것입니다. 이 시나리오에 대한 더 많은 정보를 원한다면, 이 토픽의 뒤에 나오는 데이터 바인딩 섹션을 참조하십시오.


뷰 모델은 모델 데이터를 변환하거나 조작하므로, 그것은 뷰에 의해 쉽게 소비될 수 있습니다. 뷰 모델은 뷰를 특별하게 지원하기 위해서 부가적인 프라퍼티들을 정의할 수 있습니다; 이 프라퍼티들은 보통은 모델의 일부가 아닙니다( 혹은 모델에 추가될 수 없습니다 ). 예를 들어, 뷰 모델은 두 필드의 값을 뷰에 제출하기 쉽게 만들기 위해서 결합하거나, 최대 길이를 가진 필드의 인풋을 위해 남은 문자열 개수를 계산할 수도 있습니다. 또한 뷰 모델은 데이터의 일관성을 보장하기 위해서 데이터 유효성 로직을 구현할 수도 있습니다.


그리고 뷰 모델은 뷰가 UI 의 가시적인 변경을 제공하는데 사용할 수 있는 논리적 상태들을 정의할 수도 있습니다. 뷰는 뷰 모델의 상태를 반영하는 레이아웃이나 스타일링을 정의할 것입니다. 예를 들어, 뷰 모델은 데이터가 비동기적으로 웹 서비스에 대해 제출되고 있음을 지시하는 상태를 정의할 수 있습니다. 뷰는 이 상태가 유지되는 동안에 사용자에게 가시적 피드백을 제공하기 위해서 애니메이션을 출력할 수 있습니다.


일반적으로, 뷰 모델은 UI 에서 표현될 수 있고 사용자가 호출할 수 있는 커맨드나 액션을 정의할 것입니다. 일반적인 예는 사용자가 데이터를 웹 서비스나 데이터 저장소로 제출할 수 있도록 해 주는 Submit 커맨드를 뷰 모델이 제공할 때입니다. 뷰는 그 커맨드를 버튼으로 표현하는 것을 선택할 것이며, 사용자는 데이터를 제출하기 위해서 그 버튼을 클릭할 수 있습니다. 일반적으로, 커맨드가 이용 가능한 상태가 아닐 때, 그것과 관련된 UI 표현은 비활성화됩니다. 커맨드들은 사용자 액션을 캡슐화하고, 그것을 UI 에서의 가시적인 표현과 명확하게 분리하는 방법을 제공합니다.


요약하면, 뷰 모델은 다음과 같은 핵심 특징들을 가집니다:

  • 뷰 모델은 비가시적인 크래스이며, WPF 나 실버라이트 기저 클래스를 상속하지 않습니다. 그것은 유스 케이스나 응용프로그램에서의 사용자 태스크를 지원하기 위해서 요구되는 프리젠테이션 로직을 캡슐화합니다. 뷰 모델은 뷰와 모델과는 별개로 테스트 가능합니다.
  • 뷰 모델은 일반적으로 뷰에 대한 직접적인 참조를 하지 않습니다. 그것은 뷰가 바인드할 수 잇는 프라퍼티와 커맨드들을 제공합니다. 그것은 INotifyPropertyChanged INotifyCollectionChanged 인터페이스를 통한 변경 통지 이벤트를 통해 뷰에 어떠한 상태 변경들을 통지합니다.
  • 뷰 모델은 뷰와 모델의 상호작용을 조직화합니다. 그것은 데이터를 변환하고 조작해, 데이터가 뷰에 의해 소비되기 쉬운 형태로 만들어질 수 있으며, 모델 상에서 제출되지 않는 부가적인 프라퍼티들을 구현할 수도 있습니다. 또한 IDataErrorInfo INotifyDataErrorInfo 인터페이스들을 통해 데이터 유효성을 구현할 수도 있습니다.
  • 뷰 모델은 뷰가 가시적으로 사용자에게 표현할 수 있는 논리적 상태를 정의할 수 있습니다.
노트:
뷰 혹은 뷰 모델?
특정 기능이 어디에서 구현되어야 하는지는 불명확합니다. 범용적인 경험 법칙에 따르면: 스크린상의 UI 의 특정 가시적 외형과 관련된 것들과 나중에 다시 스타일링될 수 있는( 심지어 당신이 현재는 다시 스타일링할 것을 계획하고 있지 않더라도 ) 것들은 뷰로 들어 가야 합니다; 응용프로그램의 논리적 행위에 있어 중요한 것들은 뷰 모델로 들어 가야 합니다. 부가적으로, 뷰 모델은 뷰의 특정 비주얼 엘리먼트에 대한 명시적인 정보를 가지고 있지 않기 때문에, 뷰에서 비주얼 엘리먼트들을 프로그램적인 방법으로 다루기 위한 코드는 뷰의 코드 비하인드에 존재하거나 비헤이비어에 캡슐화되어야 합니다. 이와 유사하게, 데이터 바인딩을 통해 뷰에서 출력될 데이터 아이템들을 조작하고 획득하는 코드는 뷰 모델에 존재해야 합니다.
예를 들어, 리스트 박스에서 선택된 아이템에 대한 하이라이트 컬러는 뷰에 정의되어야 하지만, 출력할 아이템들의 리스트와 선택된 아이템 자체에 대한 참조는 뷰 모델에 의해 정의되어야 합니다.



모델 클래스


MVVM 패턴에서 모델은 비즈니스 로직과 데이터를 캡슐화합니다. 비즈니스 로직은 응용프로그램 로직으로 정의되는데, 이는 응용프로그램 데이터에 대한 획득과 관리와 관련이 있습니다. 그리고 비즈니스 룰이 데이터의 일관성과 유효성을 보장하도록 하는 것을 내포합니다. 재사용성의 기회를 최대화하기 위해서, 모델은 어떠한 유스 케이스-지정 혹은 유저 태스크-지정 행위나 응용프로그램 로직을 포함해서는 안 됩니다.


일반적으로, 모델은 응용프로그램을 위한 클라이언트측 도메인 모델을 표현합니다. 그것은 응용프로그램의 데이터 모델과 비즈니스 로직 및 유효성 로직을 지원하는 것에 기반한 데이터 구조를 정의할 수 있습니다.이 모델은 데이터 접근이나 캐싱을 지원하기 위한 코드를 포함할 수도 있는데, 이는 보통 이를 위해 사용되는 분리된 데이터 저장소나 서비스를 통해서 이루어집니다. 보통, 모델과 데이터 접근 레이어는 ADO.NET Entity Framework, WCF Data Services, WCF RIA Services 와 같은 데이터 접근 혹은 서비스 전략의 일부로서 생성됩니다.


일반적으로, 모델은 뷰에 바인딩되기 쉽도록 해 주는 기능들을 구현합니다. 이는 보통 그것이 INotifyPropertyChanged INotifyCollectionChanged 인터페이스들을 통해서 프라퍼티 변경 통지나 컬렉션 변경 통지를 지원한다는 것을 의미합니다. 오브젝트 컬렉션을 표현하는 모델들 클래스들은 보통 ObservableCollection<T> 클래스를 상속하는데, 이 클래스는 INotifyCollectionChanged 인터페이스에 대한 구현을 제공합니다.


모델은 IDataErrorInfo( 혹은 INotifyDataErrorInfo ) 인터페이스들을 통해 데이터 유효화나 데이터 리포팅을 지원할 수도 있습니다. 이 인터페이스들은 WPF 나 실버라이트의 데이터 바인딩으로 하여금 값이 변경될 때를 통지받을 수 있도록 해서, UI 가 갱신될 수 있도록 해 줍니다. 또한 그것들은 UI 레이어에서 데이터 유효화나 에러 리포팅을 지원할 수 있도록 합니다.


노트:

당신의 모델 클래스들이 요청된 인터페이스를 구현하지 않는다면?

가끔 당신이 INotifyPropertyChanged, INotifyCollectionChanged, IDataErrorInfo, INotifyDataErrorInfo 인터페이스를 구현하지 않는 모델 오브젝트를 가지고 작업하게 될 경우가 있을 것입니다. 그런 경우, 뷰 모델은 모델 오브젝트들을 래핑할 필요가 있을 것이며, 요청된 프라퍼티들을 뷰에 노출할 필요가 있을 것입니다. 이 프라퍼티들의 값들은 모델 오브젝트들에 의해 직접적으로 제공될 것입니다. 뷰 모델은 그것이 노출하는 프라퍼티들을 위해 요청된 인터페이스들을 구현할 것이며, 그래야지 뷰가 쉽게 그것들을 데이터 바인딩할 수 있을 것입니다.


모델은 다음과 같은 핵심 특징들을 가지고 있습니다:

  • 모델 클래스들은 응용프로그램의 데이터 로직 및 비즈니스 로직을 캡슐화하는 비가시적 클래스들입니다. 그것들은 응용프로그램의 데이터들을 관리하고 요청된 비즈니스 룰과 데이터 유효성 로직을 캡슐화함으로써 그 데이터의 일관성과 유효성을 보장할 책임을 가집니다.
  • 모델 클래스들은 뷰나 뷰 모델에 대한 직접적인 참조를 가지지 않습니다. 그리고 그것들이 구현되는 방식에 대한 종속성을 가지지 않습니다. 
  • 모델 클래스들은 일반적으로 프라퍼티 변경 통지 이벤트 및 컬렉션 변경 통지 이벤트들을 제공하는데, 이는 INotifyPropertyChanged 인터페이스와 INotifyCollectionChanged 인터페이스를 통해 지원됩니다. 이는 그것들이 뷰에 쉽게 데이터 바인딩되도록 해 줍니다. 오브젝트에 대한 컬렉션을 표현하는 모델 클래스들은 ObservableCollection<T> 클래스를 상속합니다.
  • 모델 클래스들은 일반적으로 IDataErrorInfo INotifyDataErrorInfo 인터페이스를 통해 데이터 유효화나 에러 리포팅을 제공합니다.
  • 모델 클래스들은 일반적으로 데이터 접근 및 캐싱을 캡슐화하는 서비스나 저장소와 함께 사용됩니다.


클래스 상호작용들



MVVM 패턴은 응용프로그램의 사용자 인터페이스, 프리젠테이션 로직, 그리고 비즈니스 로직과 데이터를 분리된 클래스로 나눔으로써 그것들 간의 명확한 분리를 제공합니다. 그러므로, 당신이 MVVM 을 구현할 대, 응용프로그램의 코드에서 이전 섹션에서 기술했던 것처럼 정확한 클래스들을 생성하는 것이 중요합니다.

잘 설계된 뷰, 뷰 모델, 모델 클래스들은 코드 및 행위의 정확한 타입만을 생성하는 것은 아닙니다; 그것들은 데이터 바인딩, 커맨드, 데이터 유효성 인터페이스들을 통해 서로 쉽게 상호작용할 수 있도록 설계되기도 합니다.

뷰와 그것의 뷰 모델 간의 상호작용은 아마도 고려해야 할 가장 중요한 것일 것입니다만, 모델과 뷰 모델 간의 상호작용도 중요합니다. 다음 섹션들은 이러한 상호작용들을 위한 다양한 패턴들을 기술하며, 응용프로그램에서 MVVM 패턴을 구현할 때 그것들을 설계하는 방법에 대해서 기술합니다.


데이터 바인딩

데이터 바인딩은 MVVM 패턴에서 매우 중요한 역할을 수행합니다. WPF 와 실버라이트는 모두 강력한 데이터 바인딩 기능을 제공합니다. 당신의 뷰 모델과 당신의 뷰 모델 클래스들은 ( 이상적으로는 ) 데이터 바인딩을 지원해서 그것들이 이러한 기능을의 이점을 취할 수 있도록 설계되어야 합니다. 일반적으로, 이는 그것들은 올바른 인터페이스들을 구현해야만 한다는 것을 의미합니다.

실버라이트와 WPF 의 데이터 바인딩은 다중 데이터 바인딩 모드를 지원합니다. 단방향( one-way ) 데이터 바인딩을 사용하며, UI 컨틀롤들이 뷰 모델에 바인딩될 수 있으며, 그래서 그것들은 디스플레이가 렌더링될 때 기저 데이터의 값을 반영합니다. 양방향( two-way ) 데이터 바인딩은 UI 에서 사용자가 데이터를 수정할 때 데이터를 자동적으로 갱신합니다.

뷰 모델에서 데이터가 변경될 때 UI 가 최신상태를 유지하도록 하기 위해서는, 적절한 변경 통지 인터페이스를 구현해야 합니다. 만약 그것이 데이터 바인딩될 수 있는 프라퍼티들을 정의한다면, 그것은 INotifyPropertyChanged 인터페이스를 구현해야 합니다. 만약 뷰 모델이 컬렉션을 표현한다면, 그것은 INotifyCollectionChanged 인터페이스를 구현하거나, 이 인터페이스에 대한 구현을 제공하는 ObservableCollection<T> 클래스를 상속해야 합니다. 이 인터페이스들은 모두 기저 데이터가 변경될 때 발생하는 이벤트를 정의합니다. 데이터가 바인딩된 컨트롤들은 이벤트가 발생될 때 자동적으로 갱신될 것입니다.

많은 경우에, 뷰 모델은 오브젝트들을 반환하는 프라퍼티들을 정의할 것입니다( 그리고 순서대로 부가적인 오브젝트들을 반환하는 프라퍼티들일 수도 있습니다 ). WPF 와 실버라이트에서 데이터 바인딩은 Path 프라퍼티를 통해서 내부 프라퍼티들에 대한 바인딩을 지원합니다. 그러므로, 뷰의 뷰 모델을 위해 다른 뷰 모델이나 모델에 대한 참조를 반하는 것이 매우 일반적인 일입니다. 뷰에 전근할 수 있는 뷰 모델과 모델은  INotifyPropertyChanged INotifyCollectionChanged 인터페이스를 적절히 구현해야 합니다.


다음 섹션들은 MVVM 에서 데이터 바인딩을 지원하기 위해서 요구되는 인터페이스들을 구현하는 방법에 대해 기술합니다.



INotifyPropertyChanged 구현하기


뷰 모델이나 모델 클래스에서 INofityPropertyChanged 인터페이스를 구현하는 것은, 기저 프라퍼티 값이 변경될 때 그것들이 뷰에 있는 데이터 바인딩된 컨트롤들에 변경 통지들을 제공할 수 있도록 해 줍니다.  이 인터페이스를 구현하는 것은 직관적입니다. 아래에 코드 샘플이 있습니다( MVVM QuickStart 의 Questionnaire 클래스를 참조하십시오 ).



INotifyPropertyChanged 인터페이스를 많은 뷰 모델 클래스들에서 구현하는 것은 반복적이고 에러 가능성이 높습니다. 왜냐하면 이벤트 파라미터에 프라퍼티 이름을 지정할 필요가 있기 때문입니다. 프리즘 라이브러리는 뷰 모델 클래스에서 상속할 수 있는 편리한 기저 클래스를 제공하는데, 이는 INotifyPropertyChanged 인터페이스를 형-안정적인 방식으로 제공합니다. 그 예가 아래에 나와 있습니다.



이를 상속한 뷰 모델은 RaisePropertyChanged 를 프라퍼티 이름을 지정해 호출하거나 프라퍼티를 참조하는 람다 표현식을 사용해서 호출함으로써 프라퍼티 변경 이벤트를 발생시킬 수 있습니다. 그 예가 아래에 나와 있습니다.



노트:

람다 표현식을 사용할 때, 이 방식은 약간의 성능 비용을 포함합니다. 왜냐하면 람다 표현식은 각 호출마다 평가되어야만 하기 때문입니다. 이러한 접근법의 이점은 당신이 프라퍼티의 이름을 변경했더라도 컴파일 타임 타입 안정성 및 리팩토링 지원을 제공한다는 것입니다. 성능 부하가 작고 당신의 응용프로그램에 대해 거의 영향을 주지 않는다고 하지만, 많은 변경 통지들이 발생한다면 이 비용들이 누적됩니다. 이 경우, 람다 메서드를 사용하지 않는 방식을 사용하는 것을 고려해야 합니다.


보통, 당신의 모델이나 뷰 모델은 모델이나 뷰 모델의 다른 프라퍼티들로부터 계산되는 값들을 가진 프라퍼티들을 포함할 것입니다. 프라퍼티들에 대한 변경을 다룰 때, 계산된 프라퍼티들에 대한 통지 이벤트들도 발생될 수 있도록 하십시오.



INotifyCollectionChanged 구현하기


당신의 뷰 모델이나 모델이 아이템들의 집합을 표현하거나 아이템들의 집합을 반환하는 하나 이상의 프라퍼티들을 가지고 있을 수 있습니다. 이러한 경우에, 당신은 ListBox DataGrid 컨트롤과 같은 ItemsControl 에 그 컬렉션들을 출력하기를 원할 것입니다. 컬렉션을 표현하는 뷰 모델이나 컬렉션을 반환하는 프라퍼티들은 ItemSource 프라퍼티를 통해 데이터 바인딩될 수 있습니다.



변경 통지 요청들을 적절하게 지원하기 위해서, 뷰 모델이나 모델은 그것들이 컬렉션을 표현하고 있을 때 INofityCollectionChanged 인터페이스를 구현해야 합니다( 부가적으로 INotifyPropertyChanged 인터페이스를 구현할 수도 있습니다 ). 만약 뷰 모델이나 모델이 컬렉션에 대한 참조를 반환하는 프라퍼티를 정의한다면, 반환되는 컬렉션 클래스는 INotifyCollectionChanged 인터페이스를 구현해야 합니다.


그러나 INofityCollectionChanged 인터페이스를 구현하는 것은 어려운 일이 될 수 있습니다. 왜냐하면 그것은 컬렉션 내에서 아이템들이 추가되거나, 삭제되거나, 변경될 때에 대한 통지를 제공해야 하기 때문입니다. 그 인터페이스를 직접적으로 구현하기보다는, 보통 그것을 이미 구현해 놓은 컬렉션 클래스로부터 상속을 받거나 그 클래스를 사용하는 것이 더 쉽습니다. ObservableCollection<T> 클래스는 이 인터페이스에 대한 구현을 제공하고, 기저 클래스로 사용되거나 아이템의 컬렉션을 표현하는 프라퍼티를 구현하는데도 사용됩니다.


만약 데이터 바인딩을 위해서 뷰에 컬렉션을 제공할 필요는 있지만, 사용자의 선택을 트래킹하거나 컬렉션 내에서의 필터링, 정렬, 그룹화를 지원할 필요가 없다면, 당신은 그냥 단순하게 당신의 뷰 모델상의 프라퍼티가 ObservableCollection<T> 인스턴스를 반환하도록 정의하면 됩니다.



만약 컬렉션 클래스에 대한 참조를 획득한다면( 예를 들어, INotifyCollectionChanged 를 구현하지 않는 다른 컴포넌트나 서비스로부터 참조를 획득한다면 ), 당신은 보통 그 컬렉션을 ObservableCollection<T> 인스턴스의 컬렉션으로 래핑할 수 있으며, 이는 IEnuerable<T>List<T> 파라미터를 취하는 생성자 중의 하나를 사용합니다.



ICollectionView 구현하기


이전 코드 예제들은 뷰에 데이터 바인딩된 컨트롤들을 통해 출력될 수 있는 아이템에 대한 컬렉션들을 반환하는 단순한 뷰 모델 프라퍼티들을 구현하는 방법을 보여 줍니다. ObservableCollection<T> 클래스는 INofityCollectionChanged 인터페이스를 구현하기 때문에, 뷰의 컨트롤들은 자동적으로 갱신되어 아이템들이 추가되거나 삭제될 때마다 컬렉션의 현재 아이템 리스트를 반영할 것입니다.


그런데, 당신은 종종 뷰에 아이템 컬렉션이 출력되는 방식을 좀 더 잘 제어하고 싶다거나 출력된 아이템 컬렉션과 사용자의 상호작용을 트래킹하고 싶다거나, 그러한 동작을 뷰 모델 자체에서 수행하고 싶을 때가 있을 것입니다. 예를 들어, 당신은 아이템 컬렉션이 뷰 모델에서 제공하고 있는 프리젠테이션 로직에 따라 필터링되고 정렬되기를 원하거나, 뷰에서 현재 선택되어 있는 아이템에 대한 트랙을 유지하여 뷰 모델에서 구현된 커맨드들이 현재 선택된 아이템에 대한 동작을 수행할 수 있도록 할 필요가 있을 것입니다.


WPF 와 실버라이트는 ICollectionView 인터페이스를 구현하는 다양한 클래스들을 제공함으로써 이러한 시나리오들을 지원합니다. 이 인터페이스는 컬렉션이 필터링되고, 정렬되고, 그룹화되는 것을 허용하는 프라퍼티들과 메서드들을 지원합니다. 그리고 현재 선택된 아이템들이 트래킹 되고 변경되는 것을 허용합니다. 실버라이트와 WPF 는 모두 이 인터페이스의 구현을 제공합니다 - 실버라이트는 PagedCollectionView 클래스를 제공하고, WPF 는 ListCollectionView 를 제공합니다.


컬렉션 뷰 클래스는 기저 아이템 컬렉션을 래핑하여 그것들이 자동적으로 그것들에 대한 트래킹, 정렬, 필터링, 페이징을 제공할 수 있도록 합니다. 이 클래스들의 인스턴스는 CollectionViewSource 클래스를 사용하여 XAML 안에서 선언적으로 생성되거나 프로그램적으로 생성될 수 있습니다.


노트:

WPF 에서는 컬렉션에 컨트롤이 바인딩될 때마다 자동적으로 기본 컬렉션 뷰가 생성될 것입니다. 실버라이트에서는 바인딩된 컬렉션이 ICollectionViewFactory 인터페이스를 지원할 때만 자동적으로 컬렉션 뷰가 생성될 것입니다.


컬렉션 뷰 클래스는 기저 컬렉션에 대한 중요한 상태 정보에 대한 트랙을 유지하기 위해서 뷰 모델에 의해 사용될 수 있습니다. 반면에 뷰의 UI 와 모델의 기저 데이터 사이에서의 명확한 관심사 분리가 유지됩니다. 실제적으로, CollectionView 들은 특별히 컬렉션들을 지원하기 위해서 설계된 뷰 모델입니다.


그러므로, 당신이 뷰 모델에서 컬렉션의 아이템들에 대한 필터링, 정렬, 그룹화, 선택 트래킹을 구현할 필요가 있다면, 당신의 뷰 모델은 뷰에 노출되는 각 컬렉션에 대해 컬렉션 뷰 클래스의 인스턴스를 생성해야만 합니다. 그리고 나서 뷰 모델 내의 컬렉션 뷰 클래스에 의해 제공되는 메서드를를 사용해 CurrentChanged 이벤트와 같은 선택 변경 이벤트나 컨트롤 필터링, 정렬, 그룹화에 대한 이벤트들을 구독해야 합니다.


뷰 모델은 ICollectionView 참조를 반환하는 읽기 전용 프라퍼티를 구현해서 뷰의 컨트롤들이 컬렉션 뷰 오브젝트를 데이터 바인딩하고 그것들과 상호작용할 수 있도록 해야 합니다. ItemsControl 을 상속한 모든 WPF 및 실버라이트 컨트롤들은 자동적으로 ICollectionView 클래스들과 상호작용합니다.


다음 코드 예제는 실버라이트에서 PagedCollectionView 를 사용하여 현재 선택된 고객의 트랙을 유지하는 것을 보여 줍니다.



그리고 나서 당신은 뷰에서 ListBox 와 같은 ItemsControl 에 뷰 모델의 Customers 프라퍼티를 바인딩할 수 있는데, 이는 컨트롤의 ItemsSource 프라퍼티를 통해서 이루어 집니다. 그 예는 아래와 같습니다.



사용자가 UI 에서 고객을 선택할 때, 뷰 모델은 그 정보를 전달받을 것이며, 그것은 현재 선택된 고객과 관련된 명령들을 수행할 수 있습니다. 또한 뷰 모델은 컬렉션 뷰의 메서드를 호출함으로써 UI 에서의 현재 선택을 프로그램적으로 변경할 수도 있습니다. 그 예가 아래에 나와 있습니다.



컬렉션 뷰에서 선택이 변경될 때, 아이템의 선택 상태를 가시적으로 표현하기 위해서 UI 가 자동적으로 갱신됩니다. 이 구현은 WPF 에서도 유사한데, 앞의 예제에서의 PagedCollectionView 가 보통 ListCollectionView BindingListCollectionView 로 대체된다는 차이가 있기는 합니다. 그 예가 아래에 나와 있습니다.




커맨드들


뷰에서 출력되고 편집될 데이터에 대한 접근을 제공하는 것과 별개로, 뷰 모델은 사용자에 의해서 수행될 하나 이상의 동작이나 연산을 정의할 것입니다. WPF 와 실버라이트에서, UI 를 통해 사용자가 수행할 수 있는 동작이나 연산은 일반적으로 커맨드로서 정의됩니다. 커맨드들은 UI 의 컨트롤에 쉽게 바인딩될 수 있는 동작이나 연산들을 표현하는 편리한 방식을 제공합니다. 그것들은 그 동작이나 연산들을 구현하는 실제 코드를 캡슐화하고, 그 동작이나 연산들이 뷰에서의 실제 가시적 표현들과 분리될 수 있도록 도와 줍니다.


커맨드들은 그것들이 뷰와 상호작용할 때 다른 방식으로 가시적으로 표현되거나 사용자에 의해 실행될 수 있습니다. 대부분의 경우, 그것들은 마우스 클릭의 결과로서 실행되지만, 그것들은 터치 제스쳐나 다른 입력 이벤트나 단축키 누름의 결과로 실행될 수도 있습니다. 뷰의 컨트롤들은 뷰 모델의 커맨드들에 데이터 바인딩되므로, 사용자는 컨트롤이 정의하는 입력 이벤트나 제스쳐를 사용해서 그것들을 실행할 수 있습니다. 뷰의 UI 컨트롤과 커맨드들 사이의 상호작용은 양방향입니다. 이 경우, 커맨드는 사용자가 UI 와 상호작용할 때 실행될 수 있습니다. 그리고 기저 커맨드가 활성화되거나 비활성화되는 것에 따라, UI 가 자동적으로 활성화되거나 비활성화됩니다.


뷰 모델은 커맨드를 커맨드 메서드나 커맨드 오브젝트로서 구현할 수 있습니다( 커맨드 오브젝트는 ICommand 인터페이스를 구현합니다 ). 두 경우 모두, 뷰의 코드 비하인드 파일 내에서의 복잡한 이벤트 처리 코드를 요구하지 않고도 뷰와 커맨드의 상호작용이 선언적으로 정의될 수 있습니다. 예를 들어, WPF 와 실버라이트의 특정 컨트롤들은 본질적으로 커맨드들을 지원하며, 뷰 모델에 의해서 제공되는 ICommand 오브젝트에 데이터 바인딩될 수 있는 Command 라는 프라퍼티를 제공합니다. 이 외의 경우에, 뷰 모델에 의해 제공되는 커맨드 메서드나 커맨드 오브젝트를 컨트롤에 연관시키기 위해서 커맨드 비헤이비어가 사용될 수 있습니다.


노트:

비헤이비어들은 강력하고 유연한 확장성 메커니즘으로 상호작용 로직이나 동작을 캡슐화합니다. 이는 뷰의 컨트롤들과 선언적으로 연관될 수 있습니다. 커맨드 비헤이비어는 커맨드 오브젝트나 커맨드 메서드를 컨트롤과 연관시키는데 사용될 수 있는데, 해당 컨트롤이 커맨드들과의 상호작용을 위해서 특별히 설계되어 있는 것은 아닙니다.


다음 섹션들에서는 뷰를 위해 커맨드 메서드나 커맨드 오브젝트로서 커맨드를 구현하는 방법을 기술하며, 뷰에서 그것들을 컨트롤들과 연관시키는 방법들에 대해 기술합니다.



커맨드 오브젝트들을 구현하기


커맨드 오브젝트는 ICommand 인터페이스를 구현하는 오브젝트입니다. 이 인터페이스는 Execute 메서드를 정의하는데, 이는 연산 자체를 캡슐화합니다. 그리고 CanExecute 메서드를 제공하는데, 이는 커맨드가 특정 시점에 실행될 수 있는지 여부를 가리킵니다. 이 두 메서드는 모두 파라미터로 커맨드를 받습니다. 커맨드 오브젝트 내에서 연산을 위한 구현 로직을 캡슐화한다는 것은 그것이 좀 더 쉽게 단위 테스트를 위해 사용되거나 유지보수될 수 있다는 것을 의미합니다.


ICommand 인터페이스를 구현하는 것은 직관적입니다. 그러나, 응용프로그램에서 쉽게 사용할 수 있도록 인터페이스를 구현하는 방법은 여러 가지가 있습니다. 예를 들어 Expression Blend SDK 의 ActionCommand 클래스를 사용하거나 프리즘이 제공하는 DelegateCommand 클래스를 사용할 수 있습니다.


프리즘의 DelegateCommand 클래스는 뷰 모델 클래스 내에서 구현된 메서드를 각각 참조하는 두 개의 대리자( delegate ) 를 캡슐화합니다. 이는 DelegateCommandBase 클래스를 상속하고, DelegateCommandBase ICommand 인터페이스의 Execute CanExecute 메서드를 구현하는데, 여기에서는 앞의 대리자들을 호출하도록 하고 있습니다. 당신은 DelegateCommand 클래스 생성자에서 뷰 모델 메서드들에 대한 대리자들을 지정하면 됩니다. 그 예가 아래에 나와 있습니다.



예를 들어, 다음 코드 예제는 Submit 커맨드를 표현하는 DelegateCommand 인스턴스가 OnSubmit 와 CanSubmit 뷰 모델 메서드에 대한 대리자를 지정함으로써 생성되는 방식에 대해서 보여 줍니다. 그리고 나서 그 커맨드는 ICommand 에 대한 참조를 반환하는 읽기 전용 프라퍼티를 통해서 뷰에 노출됩니다.



Execute 메서드가 DelegateCommand 오브젝트에서 호출되면, 그것은 당신이 생성자에서 지정했던 대리자를 통해서 당신의 뷰 모델 클래스의 메서드에 대한 호출을 포워딩할 뿐입니다. 이와 유사하게, CanExecute 메서드가 호출되면, 당신의 뷰 모델 클래스의 관련 메서드가 호출됩니다. 생성자에서 CanExecute 메서드에 대한 대리자는 선택적입니다. 만약 대리자가 지정되지 않으면, DelegateCommand CanExecute 에 대해 항상 true 를 반환할 것입니다.


DelegateCommand 클래스는 저네릭( generic ) 타입입니다. 이 타입 파라미터는 Execute CanExecute 메서드에 넘겨질 커맨드 파라미터의 타입을 지정합니다. 앞의 예제에서, 커맨드 파라미터는 object 타입입니다. 프리즘은 커맨드 파라미터가 요구되지 않을 때 사용할 DelegateCommand 클래스의 비-제너릭 버전도 제공합니다.


뷰 모델은 RaiseCanExecuteChanged 메서드를 DelegateCommand 오브젝트 상에서 호출함으로써 커맨드의 CanExecute 상태의 변경을 지정할 수 있습니다. 이는 CanExecuteChanged 이벤트가 발생하도록 만듭니다. 커맨드에 바인딩된 모든 UI 컨트롤들은 바인딩된 커맨드의 이용 가능 상태를 반영하기 위해서 활성화 상태를 갱신할 것입니다.


ICommand 인터페이스에 대한 다른 구현도 가능합니다. Expression Blend SDK 가 제공하는 ActionCommand 클래스는 프리즘의 DelegateCommand 클래스와 유사합니다. 그러나 그것은 단지 Execute 메서드 대리자만을 지원합니다. 프리즘은 CompositeCommand 클래스도 제공하는데, 이는 DelegateCommand 들이 실행을 위해 서로 그룹화되는 것을 허용합니다. CompositeCommand 클래스를 사용하는 것과 관련된 더 많은 정보를 원한다면, 챕터 6 "진보된 MVVM 시나리오들" 의 "복합 커맨드들" 항목을 찹조하십시오.



뷰에서 커맨드 오브젝트들을 실행하기


뷰의 컨트롤들이 뷰 모델에 의해 제공되는 커맨드 오브젝트들과 연관되는 여러 가지 방식이 존재합니다. 특정 WPF 및 실버라이트 4 컨트롤들은, 특히 Button 이나 RadioButton 과 같은 ButtonBase 를 상속한 클래스들이나 Hyperlink MenuItem 을 상속하는  클래스들은,  Command 프라퍼티를 통해서 커맨드 오브젝트에 쉽게 데이터 바인딩될 수 있습니다. WPF 는 KeyGesture 에 뷰 모델의 ICommand 를 바인딩하는 것도 지원합니다.



커맨드 파라미터는 CommandParameter 프라퍼티를 사용하여 선택적으로 제공될 수도 있습니다. 기대되는 파라미터의 타입은 Execute CanExecute 타깃 메서드들에서 지정됩니다. 컨트롤은 사용자가 컨트롤과 상호작용할 때 자동으로 타깃 커맨드를 실행할 것이며, 만약 커맨드 파라미터가 제공되어 있다면, 그것이 커맨드의 Execute 메서드에 파라미터로 전달될 것입니다. 앞의 예제에서, 버튼은 그것이 클릭될 때 자동으로 SubmitCommand 를 실행할 것입니다. 부가적으로 CanExecute 핸들러가 지정되어 있다면, CanExecute false 를 반환할 때 버튼은 자동으로 비활성화될 것이고, 그것이 true 를 반환할 때 버튼은 자동으로 활성화될 것입니다.


대안 접근법은 Expression Blend 상호작용 트리거들과 InvokeCommandAction 비헤이비어를 사용하는 것입니다.




이 접근법은 상호작용 트리거를 붙일 수 있는 모든 컴트롤을 위해서 사용될 수 있습니다. 그것은 당신이 ButtonBase 를 상속하지 않은 컨트롤에 커맨드를 붙이고자 할 때나, 클릭 이벤트가 아닌 다른 이벤트에서 커맨드를 실행하고자 할 때 특히 유용합니다. 또한 당신이 커맨드를 위한 파라미터를 제공할 필요가 있다면, CommandParameter 프라퍼티를 사용할 수 있습니다.


커맨드에 직접적으로 바인딩될 수 있는 컨트롤들과는 다르게, InvokeCommandAction 은 커맨드의 CanExecute 값들에 기반해 자동적으로 컨트롤을 활성화하거나 비활성화하지는 않습니다. 이 동작을 구현하기 위해서는, 컨트롤의 IsEnabled 프라퍼티를 직접적으로 뷰 모델의 적절한 프라퍼티에 데이터 바인딩해야 합니다. 그 예는 앞에 나와 있습니다.


노트:

커맨드 이용 가능한 컨트롤들 대 비헤이비어들

커맨드를 지원하는 WPF 및 실버라이트 4 컨트롤들은 당신이 선언적으로 컨트롤에 커맨드를 연결할 수 있도록 해 줍니다. 이러한 컨트롤들은 사용자가 컨트롤과 특정 방식으로 상호작용할 때 특정 커맨드를 실행합니다. 예를 들어, Button 컨트롤에 대해, 커맨드는 사용자가 버튼을 클릭할 때 실행됩니다. 커맨드와 연결된 이 이벤트는 고정되어 있으며 변경될 수 없습니다. 또한 비헤이비어들은 선언적인 형태로 커맨드에 컨트롤을 연결할 수 있도록 해 줍니다. 그러나, 비헤이비어들은 컨트롤에 의해 발생하는 이벤트들의 범주에 연관될 수 있으며, 그것들은 뷰 모델의 관련 커맨드 오브젝트나 커맨드 메서드들을 선택적으로 실행하는데 사용될 수 있습니다. 다시 말해, 비헤이비어들은 커맨드 이용 가능한 컨트롤들과 유사한 많은 시나리오들을 가능하게 할 수 있으며, 그것들은 훨씬 많은 수준의 유연성과 제어를 제공할 것입니다.

당신은 커맨드 이용 가능한 컨트롤들을 사용할 때와 비헤이비어를 사용할 때를 선택해야 할 것입니다. 또한 어떤 종류의 비헤이비어를 사용할지도 선택해야 합니다. 만약 당신이 뷰의 컨트롤들을 뷰 모델의 기능과 연관시키기 위한 단일한 메커니즘을 선호하거나 일관성있는 단일 메커니즘을 원한다면, 컨트롤들이 본질적으로 커맨드를 지원하고 있더라도 비헤이비어를 사용하는 것을 고려해야 합니다.

뷰 모델에서 커맨드를 실행하기 위해서 커맨드 이용 가능한 컨트롤을 사용할 필요가 있을 때만, 그리고 커맨드를 실행하기 위해 기본 이벤트들을 사용하는 것이 행복할 때만, 비헤이비어가 요구되지 않을 것입니다.

이와 유사하게, 만약 당신이 Expression Blend 를 사용하지 않는 개발자나 UI 설계자라면, 당신은 커맨드 이용 가능한 컨트롤들( 혹은 custom attached behaviors )을 사용하는 것을 선호하게 될 것입니다. 왜냐하면 Expression Blend 비헤이비어들을 위해서 부가적인 구문들이 요구되기 때문입니다.



뷰에서 커맨드 메서드들을 실행하기


ICommand 오브젝트로서 커맨드들을 구현하는 것에 대한 대안 접근법은, 그것들을 단순히 뷰 모델의 메서드로서 구현하고, 뷰에서 그 메서드들을 직접적으로 호출하기 위해서 비헤이비어들을 사용하는 것입니다.


이는 이전 섹션에서 보여주었던 것처럼 비헤이비어로부터 커맨드들을 실행하는 것과 유사항 방식으로 수행됩니다. 그러나, InvokeCommandAction 을 사용하는 대신에, CallMethodAction 을 사용하게 됩니다. 다음 예제 코드는 기저 뷰 모델의 ( 파라미터 없는 ) Submit 메서드를 호출하는 것을 보여 줍니다.



TargetObject {Binding} 표현식을 통해 ( 뷰 모델을 의미하는 ) 기저 데이터 칸텍스트에 바인딩됩니다. Method 파리미터는 실행될 메서드를 지정합니다.


노트:

CallMethodAction 은 파라미터가 아닙니다; 만약 타깃 메서드에 파라미터를 넘길 필요가 있다면, 뷰 모델의 프라퍼티들을 값으로서 제공해야만 하며, InvokeCommandAction 으로 커맨드를 바꾸거나 당신만의 파라미터를 넘기는 CallMethodAction 버전을 작성해야 합니다.



데이터 유효화와 에러 리포팅


당신의 뷰 모델이나 모델은 종종, 데이터 유효성 검사를 수행하거나 뷰에 데이터 유효성 에러를 표시해서, 사용자가 그것을 수정하는 동작을 수행할 수 있기를 요구받을 것입니다.


실버라이트와 WPF 는 뷰의 컨트롤에 바인딩된 개별 프라퍼티들을 변경할 때 발생하는 데이터 유효성 에러를 관리하기 위한 지원을 제공합니다. 컨트롤에 데이터 바인딩되어 있는 단일 프라퍼티에 대해, 뷰 모델이나 모델은 프라퍼티 setter 에서 데이터 유효성 에러를 표시할 수 있습니다. 이는 예외를 던지거나 들어 온 잘못된 값을 거부함으로써 수행됩니다. 만약 데이터 바인딩할 때 ValidatesOnExceptions 프라퍼티가 true 로 설정되어 있다면, WPF 와 실버라이트의 데이터 바인딩 엔진은 그 예외를 다루고 사용자에게 데이터 유효성 에러가 있음을 가시적으로 보여줄 것입니다.


그러나, 이러한 방식으로 프라퍼티에서 예외를 던지는 것은 가능하면 피해야 합니다. IDataErrorInfo INotifyDataErrorInfo 인터페이스를 뷰 모델이나 모델에서 구현하는 것이 대안적인 접근법입니다. 이 인터페이스들은 뷰 모델이나 모델이 하나 이상의 프라퍼티 값들에 대해서 데이터 유효성 검사를 수행하도록 해 주며, 뷰에 에러 메시지를 반환하도록 해서 사용자가 에러를 통지받을 수 있게 합니다.



IDataErrorInfo 구현하기


IDataErrorInfo 인터페이스는 프라퍼티 데이터 유효화 및 에러 리포팅을 위한 기본적인 지원을 제공합니다. 그것은 두 개의 읽기 전용 프라퍼티들을 정의합니다; 프라퍼티 이름으로 인덱서 인자를 사용하는 인덱서 프파리티와 Error 프라퍼티입니다. 둘 다 문자열 값을 반환합니다.


인덱서 프라퍼티는 뷰 모델이나 모델이 명명된 프라퍼티에 대한 특정한 에러 메시지를 제공하도록 합니다. 빈 문자열이나 null 반환값은 뷰에 변경된 프라퍼티 값이 유효하다는 것을 알려 줍니다. Error 프라퍼티는 뷰 모델이나 모델이 전체 오브젝트에 대한 에러 메시지를 제공하도록 해 줍니다. 그러나 이 프라퍼티는 현재 실버라이트나 WPF 데이터 바인딩 엔진에서 호출되지 않는다는 것을 기억하기 바랍니다.


IDataerrorInfo 인덱서 프라퍼티는 데이터 바인딩된 프라퍼티가 처음 출력될 때, 그리고 그것이 연속적으로 변경될 때마다 접근됩니다. 이 인덱서 프라퍼티는 변경중인 모든 프라퍼티들을 위해 호출되기 때문에, 데이터 유효성 검사가 가능한 한 빠르고 효율적으로 수행되도록 보장하는데 주의를 기울여야만 합니다.


뷰의 컨트롤들에 IDataErrorInfo 인텊이스를 통해서 유효화를 수행하고자 하는 프라퍼티들을 바인딩할 때, 데이터 바인딩의 ValidatesOnDataErrors 프라퍼티를 true 로 설정해야 합니다. 이는 데이터 바인딩 엔진이 데이터 바인딩된 프라퍼티를 위해 에러 정보를 요구하는 것을 보장할 것입니다.




INotifyDataErrorInfo 구현하기


INofityDataErrorInfo IDaraErrorInfo 보다 더 유연합니다. 그것은 프라퍼티를 위한 다중 에러, 비동기 데이터 유효성 검사, 에러 상태가 오브젝트를 위해서 변경될 때 뷰에 통지하는 기능을 지원합니다. 그러나 INotifyDataErrorInfo 는 현재 실버라이트 4 에서만 지원하고 WPF 4 에서는 지원되지 않습니다.


INotifyDataErrorInfo 인터페이스는 HasError 프라퍼티를 정의하는데, 이는 뷰 모델이 에러( 혹은 다중 에러)가 어느 프라퍼티들에서 발생했는지를 알려 주고, 뷰 모델이 개별 프라퍼티들을 위한 에러 메시지 리스트를 반환할 수 있도록 해 주는 GetErrors 메서드를 알려 줍니다.


INotifyDataErrorInfo 인터페이스는 ErrosChanged 이벤트도 정의하고 있습니다. 이는 비동기적인 데이터 유효성 검사 시나리오를 지원하는데, ErrorsChanged 이벤트를 통해 뷰나 뷰 모델이 개별 프라퍼티들에 대한 에러 상태 변경을 표시할 수 있도록 해 줍니다. 프라퍼티 값들은 데이터 바인딩 이외에도 다양한 방식으로 변경될 수 있습니다 - 예를 들어, 웹 서비스 호출이나 백그라운드 계산의 결과로서 변경될 수 있습니다. ErrorsChanged 이벤트는 데이터 유효성 에러가 식별될 때 뷰 모델이 에러가 난 뷰에 정보를 줄 수 있도록 해 줍니다.


INotifyDataErrorInfo 를 지원하기 위해서는, 각 프라퍼티를 위한 에러 리스트를 유지할 필요가 있습니다. Model-View-ViewModel Reference Implementation( MVVM RI ) 는 오브젝트의 모든 유효성 에러를 트래킹하는 ErrosConatiner 컬렉션 클래스를 사용해 이를 수행하는 방식에 대해 설명합니다. 그것은 또한 에러 리스트가 변경되었을 때 통지 이벤트들을 발생시킵니다. 다음 예제 코드는 DomainObject( 루트 모델 오브젝트 )를 보여 주고, ErrorsContainer 클래스를 사용하는 INotifyDataErrorInfo 에 대한 샘플 구현을 보여 줍니다.



실버라이트에서, 뷰 모델의 프라퍼티들에 바인딩된 모든 컨트롤들은 자동적으로 INotifyDataErrorInfo 이벤트를 구독하며, 프라퍼티가 에러를 포함할 때 컨트롤에 에러 정보를 출력합니다.



생성 및 엮기( wire-up )



MVVM 패턴은 UI 를 프리젠테이션 로직, 비즈니스 로직, 데이터와 명확하게 분리할 수 있도록 도와줍니다. 그래서 올바른 클래스로 올바른 구현을 하는 것이 MVVM 패턴을 효율적으로 사용하기 위해 중요한 첫 번째 단계입니다. 뷰와 뷰 모델간이 데이터 바인딩과 커맨드를 통해서 상호작용하도록 관리하는 것도 고려해야 할 중요한 관점입니다. 다음 단계는 뷰, 뷰 모델, 모델 클래스을 인스턴스화하고 런타임에 서로 연관시키는 방법을 고려하는 것입니다.


노트:

이 단계를 관리하기 위한 적절한 전략을 선택하는 것은 응용프로그램에서 종속성 주입 컨테이너를 사용하고자 한다면 특히 중요합니다. MEF 와 유니티는 모두 뷰, 뷰 모델, 모델 사이의 종속성들을 지정하는 기능을 제공하고 있으며, 컨테이터에 의해서 그것들을 수행합니다. 진보된 시나리오에 대한 더 많은 정보를 원한다면, 챕터 6의 "진보된 MVVM 시나리오들" 을 참조하십시오.


일반적으로, 뷰와 그것의 뷰 모델 간에는 1 대 1 관계가 성립합니다. 뷰와 뷰 모델은 뷰의 데이터 칸텍스트 프라퍼티를 통해 느슨하게 연결됩니다; 이는 뷰에서의 비주얼 엘리먼트들과 비헤이비어들이 뷰 모델의 프라퍼티, 커맨드, 메서드들에 데이터 바인딩되도록 해 줍니다. 당신은 뷰와 뷰 모델 클래스의 인스턴스화를 관리하는 방식과 그것들을 런타임에 DataContext 프라퍼티를 통해 연관시키는 방식을 결정할 필요가 있습니다.


뷰 와 뷰 모델을 생성하고 연결할 때 느슨한 결합이 유지되도록 보장하기 위해서 주의를 기울여야만 합니다. 이전 섹션에서 주의를 줬듯이, 뷰 모델은 이상적으로는 뷰에 대한 특정 구현에 의존성을 가져서는 안 됩니다. 이와 유사하게, 뷰는 이상적으로는 뷰 모델의 특정 구현에 의존성을 가져서는 안 됩니다.


노트:

그러나, 뷰는 묵시적으로 뷰 모델의 특정 프라퍼티, 커맨드, 메서드에 의존할 것이라는 점을 기억해야 합니다. 왜냐하면 그것이 데이터 바인딩을 정의하고 있기 때문입니다. 만약 뷰 모델이 요구되는 프라퍼티, 커맨드, 메서드를 구현하지 않았다면, 런타임 예외가 데이터 바인딩 엔진에 의해서 생성될 것이며, 이는 디버깅 동안에 비주얼 스튜디오의 아웃풋 윈도우에 출력될 것입니다.


뷰와 뷰 모델이 런타임에 생성되고 연관되는 방식은 다양합니다. 가장 적절한 접근법은, 당신이 뷰나 뷰 모델을 생성하느냐, 그리고 이를 프로그램적으로 하느냐 선언적으로 하느냐에 달려있을 것입니다. 다음 섹션들은 뷰와 뷰 모델이 런타임에 생성되고 연관되는 일반적인 방식들에 대해 설명합니다.



XAML 을 사용해서 뷰 모델을 생성하기


아마도 뷰를 위한 가장 간단한 접근법은 XAML 에서 뷰와 연관된 뷰 모델을 XAML 에서 선언적으로 인스턴스화하는 것입니다. 뷰가 생성될 때, 관련 뷰 모델 오브젝트도 생성될 것입니다. 당신은 XAML 에서 뷰 모델이 뷰의 데이터 칸텍스트에 설정되었음을 지정할 수도 있습니다.


XAML 기반 접근법은 Basic MVVM QuickStart 의 QuestionnaireView.xaml 파일에 설명되어 있습니다. 그 예제에서, QuestionnaireViewModel 인스턴스는 QuestionnaireView 의 XXAML 에서 정의됩니다. 그 예는 다음과 같습니다.



QuestionnaireView 가 생성될 때, QuestionnaireViewModel 이 자동적으로 생성되며, 뷰의 데이터 칸텍스트로 설정됩니다. 이 접근법은 당신의 뷰 모델이 기본 ( 파라미터 없는 ) 생성자를 가질 것을 요구합니다.


뷰에 의한 뷰 모델의 선언적인 생성 및 할당의 이점은 그것이 단순하고 Microsoft Expression Blend 나 Microsoft Visual Studio 와 같은 디자인 타임 툴들에서 잘 동작한다는 거입니다. 이 접근법의 단점은 뷰가 관련 뷰 모델 타입에 대한 정보를 가지고 있다는 것입니다.



프로그램적으로 뷰 모델 생성하기


이 접근법은 뷰와 연관된 뷰 모델을 프로그램적으로 뷰의 생성자에서 인스턴스화하는 것입니다. 그리고 나서 뷰의 데이터 칸텍스트에 뷰 모델을 설정할 수 있습니다. 그 예가 아래에 나와 있습니다.



뷰 모델을 뷰의 코드-비하인드에서 프로그램적으로 생성하고 할당하는 것의 이점은 그것이 단순하며 Expression Blend 나 Visual Studio 와 같은 디자인 타임 툴에서도 잘 동작한다는 것입니다. 이 접근법의 단점은 뷰가 연관된 뷰 모델 타입에 대한 정보를 가질 것을 필요로 한다는 것이며, 뷰의 코드-비하인드 내의 코드를 요구한다는 것입니다. 유니티나 MEF 와 같은 종속성 주입 컨테이너를 사용하는 것은 뷰와 뷰 모델 간의 느슨한 결합을 유지하는데 도움을 줍니다. 더 많은 정보를 원한다면, 챕터 3 "컴포넌트 간의 종속성 관리하기" 를 참조하십시오.



데이터 템플릿으로 정의된 뷰 생성하기


뷰는 데이터 템플릿으로 정의될 수 있으며, 뷰 모델 타입과 연관될 수 있습니다. 데이터 템플릿은 리소스로서 정의되거나, 뷰 모델을 출력하게 될 컨트롤 내부에 포함되어 정의될 수 있습니다. 컨트롤의 "내용" 은 뷰 모델 인스턴스이며, 데이터 템플릿은 그것을 가시적으로 표현하는데 사용됩니다. WPF 와 실버라이트는 데이터 템플릿을 런타임에 자동적으로 인스턴스화하며 그것의 데이터 칸텍스트를 뷰 모델의 인스턴스로 설정합니다. 이 기법은 뷰 모델이 먼저 생성되고 뷰가 나중에 생성되는 상황의 예입니다.


데이터 템플릿은 유연하고 가볍습니다. UI 설계자는 그것들을 사용해 복잡한 코드를 요구하지 않고도 뷰 모델에 대한 가시적 표현을 쉽게 정의할 수 있습니다. 데이터 템플릿들은 뷰에 제한되는데, 그것은 어떤 UI 로직( 코드-비하인드 )도 요구하지 않습니다. Microsoft Expression Blend 는 데이터 템플릿을 가시적으로 설계하고 편집하기 위해서 사용될 수 있습니다.


다음 예제는 고객 리스트에 바인딩되는 ItemsControl 을 보여 줍니다. 기저 컬렉션의 각 고객 오브젝트는 뷰 모델 인스턴스입니다. 고객을 위한 뷰는 인라인 데이터 템플릿으로 정듸됩니다. 다음 예제에서, 각 고객 뷰 모델을 위한 뷰는 StackPanel  로 정의되는데, 그것은 레이블과 뷰 모델의 Name 프라퍼티에 바인딩된 텍스트 박스로 구성되어 있습니다.



당신은 데이터 템플릿을 리소스로서 정의할 수도 있습니다. 다음 예제는 리소스로 정의된 데이터 템플릿을 보여 주고, StaticRresource 마크업 확장을 통해서 클라이언트 컨트롤에 적용되는 것을 보여 줍니다.



여기에서, 데이터 템플릿은 concrete 뷰 타입을 래핑합니다. 이는 뷰가 코드-비하인드 비헤이비어를 정의하도록 해 줍니다. 이러한 방식으로, 데이터 템플릿 메커니즘은 외부적으로 뷰와 뷰 모델의 연관을 제공하기 위해서 사용될 수 있습니다. 비록 앞의 예제가 UserControl 리소스 안의 템플릿을 보여 주기는 하지만, 그것은 재사용을 위해서 보통 응용프로그램의 리소스에 배치될 것입니다. 데이터 템플릿을 사용하여 뷰를 인스턴스화하고 그것들을 자신의 뷰 모델에 연관시키는 예제를 MVVM QuickStart 의 QuestionnaireView.xaml 에서 찾아볼 수 있습니다.



핵심 결정들



당신의 응용프로그램을 생성하는데 있어 MVVM 패턴을 사용하기로 했다면, 나중에 변경하기 힘든 특정 설계 결정들을 내려야 할 필요가 있습니다. 보통, 이 결정들은 응용프로그램 전반적인 이슈이며, 그것들을 응용프로그램 전반에서 일관성있게 사용하는 것은 개발자나 디자이너의 제품성을 증대시켜줄 것입니다. 다음은 MVVM 패턴을 구현할 때 가장 중요한 결정들을 요약합니다:

  • 당신이 사용할 뷰 및 뷰 모델의 생성과 관련한 접근법을 결정하십시오. 응용프로그램이 뷰나 뷰 모델을 먼저 생성할지, 유니티나 MEF 와 같은 종속성 주입 컨테이너를 사용할지를 결정할 필요가 있습니다. 당신은 보통 이것이 응용프로그램 전반에서 일관적이기를 원할 것입니다. 더 많은 정보를 원한다면, 이 챕터의 "생성 및 엮기" 섹션을 참조하십시오. 그리고 챕터 6 "진보된 MVVM 시나리오들" 의 "진보된 생성 및 엮기" 를 참조하십시오.
  • 뷰 모델로부터의 커맨드를 커맨드 메서드로 노출할지 커맨드 오브젝트로 노출할지를 결정하십시오. 커맨드 메서드는 노출하기 쉬우며 뷰의 비헤이비어를 통해서 실행될 수 있습니다. 커맨드 오브젝트는 커맨의와 활성화/비활성화 로직을 깔끔하게 캡슐화할 수 있으며, 비헤이비어나 ButtonBase-상속 클래스의 Command 프라퍼티를 통해서 실행될 수 있습니다. 개발자나 디자이너가 사용하기 쉽게 하기 위해서, 이를 응용프로그램 전반에서 사용하기로 하는 선택하는 것은 좋은 생각입니다. 더 많은 정보를 원한다면, 이 챕터의 "커맨드들" 섹션을 참조하십시오.
  • 뷰 모델과 모델이 뷰에 에러를 제출하는 방식을 결정하십시오. 당신의 모델은 IDataErrorInfo 나, 실버라이트를 사용할 경우, INotifyDataErrorInfo 를 지원할 수 이습니다. 모든 모델이 에러 정보를 제출할 필요는 없지만, 그래야 하는 경우에, 당신의 개발자들을 위해 일관적인 접근을 하는 것이 좋습니다. 더 많은 정보를 원한다면, 이 챕터의 "데이터 유효화 및 에러 리포팅' 을 참조하십시오.
  • Microsoft Expression Blend 디자인-타임 데이터 지원이 당신의 팀에 있어 중요한지를 결정하십시오. 만약 Expression Blend 를 UI 를 설계하고 유지보수하는데 사용하고 디자인 타임 데이터를 보고 싶다면, 당신의 뷰와 뷰 모델이 파라미터를 가지지 않는 생성자를 제공하고 뷰가 디자인-타임 데이터 칸텍스트를 제공하도록 보장하십시오. 대안적으로, d:DataContextd:DesignSource 와 같은 디자인-타임 애트리뷰트들을 사용하여 Microsoft Expression Blend 가 제공하는 디자인-타임 기능들을 사용하는 것을 고려하십시오. 더 많은 정보를 원한다면, 챕터 7 "유저 인터페이스 만들기" 의 "디자이너 친화적인 뷰를 생성하는 가이드라인" 을 참조하십시오.


더 많은 정보



For more information about data binding in WPF, see "Data Binding" on MSDN:

For data binding in Silverlight, see "Data Binding" on MSDN:

For more information about binding to collections in WPF, see "Binding to Collections" in "Data Binding Overview" on MSDN:

For more information about binding to collections in Silverlight, see "Binding to Collections" in "Data Binding" on MSDN:

For more information about the Presentation Model pattern, see "Presentation Model" on Martin Fowler's website:

For more information about data templates, see "Data Templating Overview" on MSDN:

For more information about MEF, see "Managed Extensibility Framework Overview" on MSDN:

For more information about Unity, see "Unity Application Block" on MSDN:

For more information about DelegateCommand and CompositeCommand, see Chapter 9, "Communicating Between Loosely Coupled Components."


'Programming > Prism4.1' 카테고리의 다른 글

진보된 MVVM 시나리오들  (0) 2014.09.20
모듈러 응용프로그램 개발  (0) 2014.09.13
컴포넌트 간의 종속성 관리하기  (0) 2014.09.10
프리즘 응용프로그램 초기화하기  (0) 2014.08.24
왜 프리즘을 사용하는가?  (0) 2014.08.19
소개  (0) 2014.08.18
프리즘 4.1 설치  (0) 2014.08.14

+ Recent posts