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




진보된 MVVM 시나리오들



이전 챕터에서는, 응용프로그램 유저 인터페이스( UI ), 프리젠테이션 로직, 비즈니스 로직을 세 개의 분리된 클래스( 뷰, 뷰 모델, 모델 )로 분리하고, 그 클래스들 간의 상호작용을( 데이터 바인딩, 커맨드, 데이터 유효화 인터페이스를 통해서 ) 구현하고, 생성과 엮기( wire-up )를 다루기 위한 전략을 구현함으로써 모델-뷰-뷰모델 패턴을 구현하는 방법을 설명했습니다. 


이러한 기본 요소들을 구현하는 MVVM 패턴은 응용프로그램 내의 많은 시나리오들을 지원할 수 있는 가능성이 있을 것입니다. 그러나, 기본 MVVM 패턴을 확장할 것을 요구하거나 더욱 진보된 기법들이 적용될 것을 요구하는 복잡한 시나리오들에 직면할 수도 있습니다. 이것은 응용프로그램이 더 커지거나 복잡해지면 사실이 될 가능성이 있습니다. 그러나 이러한 시나리오는 많은 작은 응용프로그램들에서도 만날 수 있는 문제입니다. 프리즘 라이브러리는 이런 많은 기법들을 구현하는 컴포넌트들을 제공하며, 자신만의 응용프로그램에서 그것들을 더욱 쉽게 사용할 수 있도록 해 줍니다.


이 챕터는 약간 복잡한 시나리오들을 설명하며, 그것들을 MVVM 패턴이 그것들을 지원하는 방법에 대해서 설명합니다. 다음 섹션은 커맨드가 서로 연결되거나 차일드 뷰들과 연관되는 방식과 그것들이 커스텀 요구사항들을 지원하기 위해서 확장될 수 있는 방법에 대해서 설명합니다. 그리고 나서 그 다음 섹션들은 비동기 데이터 요청과 연속되는 UI 상호작용을 다루는 방법과 뷰와 뷰 모델 사이의 상호작용 요청을 다루는 방법에 대해서 설명합니다.


"진보된 생성 및 엮기" 섹션은 유니티나 MEF 와 같은 종속성 주입 컨테이너를 사용할 때 생성과 엮기를 다루는 가이드를 제공합니다. 마지막 섹션은 응용프로그램의 뷰 모델과 모델을 단위 테스트하고 비헤이비어를 테스트하는 가이드를 제공함으로써 MVVM 응용프로그램을 테스트하는 방법을 설명합니다.



커맨드들



커맨드는 커맨드의 구현 로직을 UI 표현과 분리하는 방법을 제공합니다. 데이터 바인딩이나 비헤이비어는 뷰의 엘리먼트들을 뷰 모델에 의해 제공되는 커맨드들과 선언적으로 연관시켜주는 방법을 제공합니다. 챕터 5 "MVVM 패턴 구현하기" 의 "커맨드들" 섹션은 커맨드가 뷰 모델에서 커맨드 오브젝트나 커맨드 메서드로 구현될 수 있는 방법과 그것들이 뷰의 컨트롤에서 혹은 비헤이비어를 사용해서 혹은 특정 컨트롤에 의해 제공되는 내장 Command 프라퍼티를 사용해서 호출될 수 있는 방법에 대해서 설명합니다.


노트:

WPF 라우티드( routed ) 커맨드들: MVVM 패턴에서 커맨드 오브젝트나 커멘드 메서드로서 구현되는 커맨드들은 라우티드 커맨드라는 이름을 가진 WPF 의 내장 커맨드 구현과는 다소 다르다는 것을 기억해야 합니다( 실버라이트는 라우티드 커맨드 구현을 가지지 않습니다 ). WPF 라우티드 커맨드는 UI 트리(  특히 논리 트리 )의 엘리먼트들을 통해 커맨드들을 라우팅함으로써 커맨드 메시지를 받습니다. 그러므로, 커맨드 메시지들은 해당 요소로부터 혹은 명시적으로 지정된 타깃 엘리먼트들까지 위 아래로 라우팅됩니다; 기본적으로, 그것들은 뷰와 연관된 뷰 모델과 같은 UI 트리 외부의 컴포넌트까지 라우팅되지는 않습니다. 그러나 WPF 라우티드 커맨드들은 뷰의 코드-비하인드에 정의된 커맨드 핸들러를 사용해서뷰 모델 클래스로 커맨드 호출을 전달할 수 있습니다.



복합 명령들


많은 경우에, 뷰 모델에 의해 정의된 커맨드는 연관된 뷰의 컨트롤에 바인딩 될 것이며, 그래서 사용자가 직접적으로 뷰 내부에서 커맨드를 실행할 수 있습니다. 그러나 어떤 경우에는, 응용프로그램 UI 의 부모 뷰에 있는 컨트롤로부터 하나 이상의 뷰 모델의 커맨드들을 실행할 수 있기를 원할 수도 있습니다.


예를 들어, 만약 응용프로그램이 사용자로 하여금 동시에 다수의 아이템들을 편집할 수 있도록 허용했다고 한다면, 당신은 응용프로그램의 툴바나 리본에 있는 버튼에 의해 표현되는 단일 커맨드를 사용해 사용자가 모든 아이템들을 저장할 수 있도록 허용하기를 원할 것입니다. 이 경우, Save All 커맨드는 각 아이템을 위한 뷰 모델 인스턴스에 의해 구현된 Save 커맨드들을 각각 실행하게 될 것입니다. 그 예가 아래 그림에 나와 있습니다.


SaveAll 복합 커맨드를 구현하기




프리즘은 CompositeCommand 클래스를 통해 이 시나리오를 지원합니다.


CompositeCommand 클래스는 다중 자식 커맨드들로 구성되는 커맨드를 표현합니다. 복합 커맨드가 실행되면, 그것의 자식 커맨드들이 순서대로 실행됩니다. 이는 UI 에서 단일 커맨드로서 커맨드 그룹을 표현할 필요가 있거나 다중 커맨드들을 하나의 논리 커맨드로 구현하기를 원할 때 유용합니다.


예를 들어, CompositeCommand 클래스는 buy/sell view 의 Submit Alll 버튼으로 표현되는 SubmitAllOrders 를 구현하기 위해서 Stock Trader Reference Implementation( Stock Trader RI ) 에서 사용되는 클래스입니다. 사용자가 Submit All 버튼을 클릭하면, 개별 buy/sell 트랜잭션에 의해 정의된 개별 SubmitCommand 가 실행됩니다.


CompositeCommand 클래스는 자식 커맨드들의 리스트를 유지합니다( 그것들은 DelegateCommand 인스턴스들입니다 ). CompositeCommand 의 Execute 메서드는 단지 자식 커맨드들의 Execute 메서드를 순서대로 호출할 뿐입니다. CanExecute 메서드는 이와 유사하게 개별 자식 커맨드들의 CanExecute 메서드에 대한 호출을 하지만, 하나의 자식 커맨드라도 실행될 수 없는 상태라면 CanExecute false 를 반환하게 될 것입니다. 다시 말해, 기본적으로, CompositeCommand 는 모든 자식 커맨드들이 실행될 수 있을 때만 실행됩니다.



자식 커맨드들을 등록하고 등록해제하기


자식 커맨드들은 RegisterCommand UnregisterCommand 메서드를 통해서 등록되고 등록해제됩니다. 예를 들면 Stock Trader RI 에서, 각 buy/sell 주문에 대한 Submit 과 Cancel 커맨드는 SubmitAllOrders 와 CancelAllOrders 복합 커맨드에 등록됩니다. 아래 코드는 그 예를 보여 줍니다( OrderController 클래스를 참조하십시오 ).



노트:

앞의 commandProxy 오브젝트는 Submit 와 Cancel 복합 커맨드에 접근하는 인스턴스를 제공하는데, 그것은 정적으로 정의되어 있습니다. 더 많은 정보를 원한다면, StockTraderRICommands.cs 파일의 클래스를 참조하십시오.



활성화된 자식 뷰들에서 커맨드 실행하기


보통, 응용프로그램은 응용프로그램 UI 내의 자식 뷰 컬렉션을 출력할 필요가 있습니다. 여기에서 각 자식 뷰는 하나 이상의 커맨드를 구현하는 연관된 뷰 모델을 가지고 있을 것입니다. 복합 커맨드들은 응용프로그램 UI 내의 자식 뷰들에 의해 구현된 커맨드들을 표현하기 위해 사용될 수 있으며, 이는 그것들이 부모 뷰에서 실행되는 방법을 조직화하는데 도움을 줍니다. 이 시나리오들을 지원하기 위해서, 프리즘 CompositeCommand DelegateCommand 클래스는 프리즘 리전( region )과 함께 동작하도록 설계되어 왔습니다.


프리즘 리전( 챕터 7 "유저 인터페이스 만들기" 의 "리전들" 에 설명되어 있습니다 )은 자식 뷰들이 응용프로그램 UI 의 논리적 플레이스홀더( placeholder ) 와 연관되는 방법을 제공합니다. 그것들은 보통 자식 뷰들의 특정 레이아웃을 UI 에서의 위치 및 논리적 플레이스홀더와 디커플링하기 위해서 사용됩니다. 다음 그림은 각 자식 뷰들이 EditRegion 이라 명명된 리전에 추가되고 UI 디자이너가 그 리전에 뷰를 배치하기 위해서 Tab 컨트롤을 사용하는 것을 보여 줍니다.


Tab 컨트롤을 사용해 EditRegion 정의하기




부모 뷰 레벨에서 복합 커맨드는 보통 자식 뷰 수준에서 커맨드가 실행되는 방법을 조직화하기 위해 사용될 것입니다. 어떤 경우에는, 이전에 설명된 Save All 커맨드 예제에서 처럼, 당신은 보여지는 모든 뷰를 위해 커맨드가 실행되기를 원할 것입니다. 다른 경우에는, 당신은 커맨드들이 활성화된 뷰 상에서만 실행되기를 원할 것입니다. 이 경우, 복합 커맨드는 활성화되었다고 여겨지는 뷰 상에서만 자식 커맨드들을 실행할 것입니다. 예를 들어, 당신은 응용프로그램의 툴바나 리본의 Zoom 커맨드를 구현하는데, 그것이 현재 활성화된 아이템에서만 적용되도록 하기를 원할 것입니다. 다음 다이어그램은 그것을 보여 줍니다.


Tab 컨트롤을 사용하여 EditRegion 을 정의하기




이 시나리오를 지원하기 위해서, 프리즘은 IActiveAware 인터페이스를 제공합니다. IActiveAware 인터페이스는 구현자가 활성화되어 있을 때 true 를 반환하는 IsActive 프라퍼티를 정의하며, 그 활성화 상태가 변경될 때마다 발생하는 IsActiveChanged 이벤트를 정의합니다.


자식 뷰나 뷰 모델에서 IActiveAware 인터페이스를 구현할 수 있습니다. 그것은 주로 리전 안의 자식 뷰의 활성화 상태를 트래킹하는데 사용됩니다. 뷰가 활성화되어 있는지 여부는 특정 리전 컨트롤 내의 뷰를 조직하는 리전 어댑터에 의해 결정됩니다. 예를 들어 이전에 보여 준 Tab 컨트롤에 대해, 현재 선택된 탭에서 서 뷰가 활성화되어 있다고 설정하는 리전 어댑터가 존재합니다.


DelegateCommand 클래스도 IActiveAware 인터페이스를 구현합니다. CompositeCommand 는 ( CanExecute 와는 별개로 ) 생성자에서 monitorCommandActivity 파라미터를 true 로 설정함으로써 자식 DelegateCommands 의 활성화 상태를 평가하기 위해서 구성될 수 있습니다. 이 파라미터가 true 로 설정되면, CompositeCommand 클래스는 CanExecute 메서드를 위해 반환값을 결정할 때와 Execute 메서드 내에서 자식 커맨드들이 실행될 때 각 자식 DelegateCommand 의 활성화 상태를 고려할 것입니다.


monitorCommandActivity 파라미터가 true 이면, CompositeCommand 클래스는 다음과 같은 동작들을 보여 줍니다:

  • CanExecute. 모든 활성화된 커맨드들이 실행될 수 있을 때만 true 를 반환합니다. 비활성화된 자식 커맨드들은 전혀 고려되지 않습니다.
  • Execute. 모든 활성화된 커맨드들을 실행합니다. 비활성화된 자식 커맨드들은 전혀 고려되지 않습니다.

이 기능을 사용해서 앞서 설명한 예제를 구현할 수 있습니다. IActiveAware 인터페이스를 자식 뷰나 모델에 구현함으로써, 당신은 자식 뷰가 영역에서 활성화되거나 비활성화되는 때를 통지받을 수 있습니다. 자식 뷰의 활성화 상태가 변경되면, 자식 커맨드의 활성화 상태를 갱신할 수 있습니다. 그리고 나서 사용자가 Zoom 복합 커맨드를 실행하면, 활성화된 자식 뷰의 Zoom 커맨드가 실행될 것입니다.


컬렉션들 내부의 커맨드들

뷰에서 아이템 컬렉션을 출력할 때 당신이 자주 직면하게 되는 다른 일반적인 시나리오는 컬렉션의 각 아이템을 위한 UI 를 ( 아이템 레벨이 아니라 ) 부모 뷰 레벨에서 커맨드와 연관시킬 필요가 있을 때입니다.


예를 들어, 다음 그림에서 보여 주는 응용프로그램에서, 뷰는 ListBox 컨트롤에서 아이템 컬렉션을 출력합니다. 그리고 사용자가 컬렉션에서 개별 아이템들을 제거할 수 있도록 하는 Delete 버튼을 정의하는 아이템을 출력하기 위해서 데이터 템플릿을 사용합니다.


컬렉션 내의 커맨드들을 바인딩하기




뷰 모델이 Delete 커맨드를 구현하기 때문에, 각 아이템을 위한 UI 의 Delete 버튼을 뷰 모델에 의해 구현된 Delete 커맨드와 엮는 것이 도전 과제입니다. ListBox 의 각 아이템들에 대한 데이터 칸텍스트가 Delete 커맨드를 구현하는 부모 뷰 모델이 아니라 컬렉션 내의 아이템들을 참조하고 있는 것이 어려운 점입니다.


이 문제를 해결하는 한 가지 접근법은, 데이터 템플릿이 아니라 부모 컨트롤에 상대적인 바인딩을 보장하도록 하기 위해, ElementName 바인딩 프라퍼티를 사용하여 부모 뷰의 커맨드에 데이터 템플릿의 버튼을 바인딩하는 것입니다. 다음 XAML 은 이 기법을 설명하고 있습니다.



데이터 템플릿의 버튼 컨트롤의 내용은 컬렉션 아이템의 Name 프라퍼티에 바인딩된다. 하지만 버튼을 위한 커맨드는 root 엘리먼트의 데이터 칸텍스트를 통해 Delete 커맨드에 바인딩됩니다. 이는 버튼이 아이템 레벨이 아니라 부모 뷰 레벨에서 커맨드에 바인딩될 수 있도록 허용해 줍니다. 당신은 CommandParameter 프라퍼티를 사용해서 아이템에 적용될 커맨드가 무엇인지를 지정하거나, ( CollectionView 를 통해 ) 현재 선택된 아이템에서 수행될 커맨드를 구현할 수 있습니다.



커맨드 비헤이비어


실버라이트 3 이전 버전에서, 실버라이트는 직접적으로 커맨드들을 지원하는 컨트롤들을 제공하지 않았습니다. ICommand 인터페이스가 이용 가능했지만, 컨트롤들이 ICommand 구현에 직접적으로 연결될 수 있도록  허용해주는 Command 프라퍼티를 구현한 컨트롤이 존재하지 않았습니다. 이 한계를 극복하기 위해서, 그리고 실버라이트 3 에서 MVVM 커맨딩 패턴들을 지원하기 위해서, 프리즘 라이브러리( 버전 2.0 )은 모든 실버라이트 컨트롤들이 연결된 비헤이비어를 사용해 커맨드 오브젝트에 바인딩될 수 있도록 허용하는 메커니즘을 제공했습니다. 이 메커니즘은 WPF 에서도 작동하는데, 이는 뷰 모델 구현들이 실버라이트와 WPF 에서 재사용될 수 있도록 해 주었습니다.


다음 예제는 버튼의 클릭 이벤트에 뷰에서 정의한 커맨드 오브젝트가 바인딩될 수 있도록 하기 위해서 프리즘 커맨드 비헤이비어가 사용되는 방식을 보여 줍니다.



실버라이트 4 는 모든 Hyperlink-상속 컨트롤들과 ButtonBase-상속 컨트롤들을 위해 Command 프라퍼티를 위한 지원을 추가했으며, 이는 그것들이 WPF 에서와 같은 방식으로 커맨드 오브젝트들에 바인딩될 수 있도록 해 줬습니다. 이러한 컨트롤들을 위해서 Command 프라퍼티를 사용하는 것은 챕터 5 "MVVM 패턴 구현하기" 의 "커맨드들" 섹션에서 설명되고 있습니다. 그러나 프리즘 커맨드 비헤이비어는 하위 호환성과 커스텀 비헤이비어의 개발을 지원하기 위해서 남아 있으며, 이는 뒤에서 설명됩니다.


이 비헤이비어 접근법은 뷰의 컨트롤에 쉽게 적용될 수 있는 인터랙티브 비헤이비어를 구현하고 캡슐화하기 위해서 범용적으로 이용 가능한 기법입니다. 이전에 보여 준 커맨드를 지원하기 위해서 비헤이비어를 사용하는 것은 비헤이비어가 지원할 수 있는 많은 시나리오들 중의 하나일 뿐입니다. Microsoft Expression Blend 는 이제 챕터 5 "MVVM 패턴 구현하기" 의 "뷰에서 커맨드 메서드 실행하기" 섹션에서 설명했던 InvokeCommandAction CallMethodAction 을 포함하는 다양한 비헤이비어들을 제공합니다. 그리고 커스텀 비헤이비어를 개발하기 위한 소프트웨어 개발 키트( SDK )를 제공합니다. Expression Blend 는 비헤이비어를 위해 드래그-앤-드랍 생성 및 프라퍼티 편집 지원을 제공하는데, 이는 비헤이비어 추가 작업을 쉽게 만들어 줍니다. 커스텀 Expression Blend 비헤이비어를 개발하는 것에 대한 더 많은 정보를 원한다면, MSDN 의 "Creating Custom Behavior" 를 참조하십시오.


실버라이트 4 에서 커맨드-이용 가능한 컨트롤들에 대한 지원을 소개하고, Expression Blend Behaviors SDK 를 소개하기는 했지만, 프리즘 커맨드 비헤이비어에 대한 대단한 요구들이 있는 것은 아닙니다. 당신은 아마도 그것들의 치밀한 구문과 구현들, 그리고 쉽게 확장될 수 있는 능력과 유용성을 발견하게 될 것입니다.



프리즘 커맨드 비헤이비어들을 확장하기


프리즘 커맨드 비헤이비어는 어태치드( attached ) 비헤이비어 패턴에 기반하고 있습니다. 이 패턴은 컨트롤에 의해 발생되는 이벤트들을 뷰 모델에 의해 제공되는 커맨드 오브젝트에 연결합니다. 프리즘 커멘드 비헤이비어는 두 개의 부분으로 구성됩니다; 어태치드 프라퍼티와 비헤이비어 오브젝트. 어태치드 프라퍼티는 타깃 컨트롤과 비헤이비어 오브젝트 간의 관계를 수립합니다. 비헤이비어 오브젝트는 타깃 컨트롤을 관찰하며, 컨트롤이나 뷰 모델 내의 이벤트들이나 상태 변경들에 기반한 동작을 수행합니다.


프리즘 커맨드는 ButtonBase-상속 컨트롤들의 Click 이벤트에 기반합니다. 이는 ButtonBaseClickCommandBehavior 클래스와 타깃 컨트롤의 클릭 이벤트에 그것을 연결하는 어태치드 프라퍼티를 제공함으로써 가능해 집니다. 아래 그림은 ButtonBase, ButtonBaseClickCommandBehavior, 뷰 모델에 의해 제공되는 ICommand 오브젝트의 관계를 보여 줍니다.


ButtonClick 이벤트를 ICommand 에 전달하기





당신의 응용프로그램은 ButtonBase 로부터의 Click 이벤트가 아니라 컨트롤이나 이벤트로부터의 커맨드들을 실행할 필요가 있습니다. 혹은 비헤이비어가 타깃 컨트롤과 상호작용하는 방식이나 뷰 모델이 그것에 바인딩되는 방식을 커스터마이즈할 필요가 있습니다. 이런 경우에, 당신은 자신만의 어태치드 프라퍼티나 비헤이비어 구현을 정의할 필요가 있습니다.


프리즘 라이브러리는 CommandBehaviorBase<T> 클래스를 제공해서 ICommand 오브젝트들과 상호작용하는 비헤이비어들을 생성하기 쉽게 만들어 줍니다. 이 클래스는 커맨드를 실행하고 커맨드의 CanExecuteChanged 이벤트의 변경을 관찰하고, 실버라이트와 WPF 에서 커맨드 지원을 확장하는데 사용될 수 있습니다.


커스텀 비헤이비어를 생성하려면, CommandBehaviorBase<T> 를 상속하는 클래스를 생성하고 당신이 관찰하고 싶은 컨트롤을 타깃으로 설정하십시오. 이 클래스의 타입 파라미터는 비헤이비어가 연결된 컨트롤의 타입을 식별합니다. 클래스의 생성자에서, 당신은 컨트롤로부터 관찰하고자 하는 이벤트들을 구독할 수 있습니다. 다음 코드 예제는 ButtonBaseClickCommandBehavior 클래스의 구현을 보여 줍니다.



CommandBehaviorBase<T> 클래스를 사용하면, 자신만의 커스텀 비헤이비어 클래스를 정의할 수 있습니다; 이는 타깃 컨트롤이나 뷰 모델에 의해 제공되는 커맨드들과 비헤이비어가 상호작용하는 방식을 커스터마이즈 할 수 있도록 해 줍니다. 예를 들어, 당신은 다양한 컨트롤 이벤트에 기반해 바인딩된 커맨드를 실행하거나 바인딩된 커맨드의 CanExecute 상태에 기반해 컨트롤의 가시적 상태를 변경하는 비헤이비어를 정의할 수 있습니다.


타깃 컨트롤에 커맨드 비헤이비어를 선언적으로 어태치하는 것을 지원하기 위해서, 어태치드 프라퍼티가 사용됩니다. 어태치드 프라퍼티는 비헤이비어가 XAML 에서 컨트롤에 어태치되는 것을 허용하며, 타깃 컨트롤과 비헤이비어 구현의 연관 및 생성을 관리합니다. 어태치드 프라퍼티는 정적 클래스에서 정의됩니다. 프리즘 커맨드 비헤이비어는 어떤 규약에 기반하는데, 그것은 커맨드를 실행하기 위해서 사용되는 이벤트를 가리키는 정적 클래스의 이름입니다. 어태치드 프라퍼티의 이름은 데이터 바인딩되고 있는 오브젝트의 타입을 가리킵니다. 그러므로 앞서 설명한 프리즘 커맨드 비헤이비어는 Click 이라 명명된 정적 클래스를 사용합니다. 이 클래스는 Command 라고 명명된 어태치드 프라퍼티를 정의합니다. 이는 앞에서 보여 준 Click.Command 라는 구문을 사용하는 것을 허용합니다.


커맨드 비헤이비어 자체가 어태치드 프라퍼티를 통해 타깃 컨트롤과 실제로 연관되기도 합니다. 그러나 이 어태치드 프라퍼티는 정적 클래스에 대해 private 이며, 개발자는 볼 수 없습니다.



Command 어태치드 프라퍼티의 구현은 ButtonBaseCommandBehavior 클래스의 인스턴스를 생성합니다. 이는 OnSetCommandCallback 콜백 메서드를 통해서 이루어 집니다. 그것이 아래 코드 예제에 나와 있습니다.



어태치드 프라퍼티에 대한 더 많은 정보를 원한다면 MSDN 의 "Attached Properites Overview" 를 참조하십시오.



비동기 상호작용들을 다루기



보통 뷰 모델들을 비동기적으로 통신하는 서비스들이나 컴포넌트들과 상호작용할 필요가 있습니다.이는 당신이 실버라이트 응용프로그램을 다루거나, 웹서비스타 네트워크를 통한 다른 리소스들과 상호작용하거나, 응용프로그램이 계산이나 I/O 를 수행하기 위해서 백그라운드 작업을 사용할 때 특히 그렇습니다. 이러한 연산을 비동기적으로 수행하는 것은 응용프로그램이 좋은 사용자 경험을 배달하는데 필수적인 응답성을 유지하고 있음을 보장해 줍니다.


사용자가 비동기적인 요청이나 백그라운드 작업을 초기화할 때, 그 응답이 언제 도착할지와 스레드가 언제 반환될지를 예측하는 것은 어렵습니다. UI 는 UI 스레드에서만 갱신될 수 있기 때문에, 당신은 UI 스레드 상에서 요청을 디스패치함으로써 UI 를 갱신해야 할 것입니다.



웹 서비스들에서 데이터를 획득하고 상호작용하기


웹 서비스나 원격 접근 기법들과 상호작용할 때, 당신은 보통 IAsyncResult 패턴을 사용할 것입니다. 이 패턴에서는 GetQuestionnaire 같은 메서드를 호출하는 대신, BeginGetQuestionnaire EndGetQuestionnaire 와 같은 메서드 쌍을 사용합니다. 비동기 호출을 초기화하기 위해서 BeginGetQuestionnaire 를 호출합니다. 결과를 획득하거나 타깃 메서드를 실행하는 도중에 예외가 발생하는지를 확인하기 위해서, 호출이 완료되었을 때 EndGetQuestionnaire 를 호출합니다.


EndGetQuestionnaire 의 호출 시점을 결정하려면, BeginGetQuestionnaire 를 호출하는 동안에 사용할 콜백을 지정하거나 완료되었는지 상황을 조사할 수 있습니다. 콜백 접근법을 사용하면, 당신의 콜백 메서드가 타깃 메서드 실행이 완료되었을 때 호출될 것입니다. 그러면 거기에서 EndGetQuestionnaire 를 호출합니다. 그 예가 아래에 나와 있습니다.



End 메서드( 이 경우 EndGetQuestionnaire )에 대한 호출에서 주의할 점이 있습니다. 요청을 실행하는 동안에 발생한 예외가 실행될 수도 있다는 것입니다. 당신의 응용프로그램은 반드시 이를 처리해야만 하며, UI 를 통해 스레드 안전한 방법으로 그들에게 보고해 줘야할 필요가 있을 것입니다. 만약 이것들을 다루지 않는다면, 스레드는 종료될 것이고, 당신은 결과를 처리할 수 없게 될 것입니다.


응답이 항상 UI 스레드에서 발생하는 것은 아니기 때문에, 당신이 UI 상태에 영향을 줄 수 있는 무엇인가를 변경하려고 하는 계획득 세웠다면, 스레드 Dispatcher 오브젝트나 SynchronizationContext 오브젝트를 사용해서 UI 스레드에 그 응답을 디스패치해야 할 것입니다. WPF 와 실버라이트에서는 보통 디스패처를 사용할 것입니다.


다음 코드 에제에서, Questionnaire 오브젝트는 비동기적으로 획득되며, 그것은 QuestionnaireView 를 위한 데이터 컨텍스트로서 설정됩니다. 실버라이트에서 당신은 디스패처의  CheckAccess 메서드를 사용해서 당신이 지금 UI 스레드 상에서 작업하고 있는지 여부를 확인할 수 있습니다. 만약 UI 스레드가 아니라면, BeginInvoke 메서드를 사용해서 UI 스레드로 그 요청이 운반되도록 할 필요가 있습니다.



Model-View-ViewModel Reference Implementation( MVVM RI ) 는 IAsyncResult-상속 서비스 인터페이스를 이전 예제와 유사한 방식으로 사용하는 방식의 예를 보여 줍니다. 이는 고객을 위해 더 단순한 콜백 메커니즘을 제공하기 위해서 서비스를 래핑하고, 호출자의 스레드에 콜백을 디스패치하기 하는 것을 다룹니다. 예를 들어, 다음 코드 예제는 설문지( questionnaire )를 획득하는 것을 보여 줍니다.



반환되는 result 오브젝트는 발생할 수 있는 에러들과 함께 획득한 결과를 래핑합니다. 다음 코드 예제는 에러가 평가되는 방법을 보여 줍니다.




유저 인터페이스 패턴들



응용프로그램들에서는 이벤트의 발생을 사용자에게 통지하거나 작업을 진행하기 전에 확인을 요청할 필요성이 빈번하게 발생합니다. 이러한 상호작용들은 보통 응용프로그램에서의 변화를 사용자들에게 단순하게 전달하기 위해서 설계된 짧은 상호작용이거나 사용자들로부터 간단한 응답을 획득합니다. 이러한 상호작용들 중의 일부는 사용에게 다이얼로그 박스나 메시지 박스를 출력할 때와 같이 modal 로 나타납니다. 혹은 토스트 통지나 팝업 윈도우처럼 

non-modal 로 나타납니다.


이러한 경우에 사용자와 상호작용하는 여러 가지 방법들이 존재하지만, 그것들을 명확한 관심사 분리를 보존하는 방식으로 MVVM 기반 응용프로그램에서 구현하는 것은 어려운 일이 될 수 있습니다. 예를 들어, non-MVVM 응용프로그램에서, 응답을 위한 프롬프트를 띄우기 위해 코드-비하인드 파일에서 MessageBox 클래스를 사용할 것입니다. MVVM 응용프로그램에서, 이는 적절하지 않습니다. 왜냐하면 그것은 뷰와 뷰 모델 간의 관심사 분리를 깰 것이기 때문입니다.


MVVM 패턴의 관점에서, 뷰 모델은 사용자의 상호작용을 초기화할 책임과 모든 응답을 처리할 책임이 있고, 반면에 뷰는 사용자 경험에 적절한 무엇인가를 사용해 사용자와의 상호작용을 실제로 관리할 책임이 있습니다. 뷰 모델에 구현된 프리젠테이션 로직과 뷰에 의해 구현된 사용자 경허 사이의 관심사 분리를 보존하는 것은 테스트 용이성과 유연성을 증대시킵니다.


MVVM 에는 이러한 종류의 사용자 상호작용을 구현하기 위한 두 가지 일반적인 접근법이 존재합니다. 한 접근법은 사용자와의 상호작용을 초기화하기 위해서 뷰 모델에 의해 사용될 수 있는 서비스를 구현하는 것이며, 그래서 뷰의 구현에 대한 의존성을 서비스의 의존성을 보존하는 것입니다. 다른 접근법은 사용자와의 상호작용에 대한 의도를 표현하기 위해서 뷰 모델에 의해 발생되는 이벤트들을 사용하는 것과 더불어, 이러한 이벤트들에 바인딩되고 상호작용에 대한 가시적 관점을 관리하는 뷰의 컴포넌트를 사용하는 것입니다. 각 접근법은 다음 섹션들에 설명됩니다.



상호작용 서비스 사용하기


이 접근법에서, 뷰 모델은 메시지 박스를 통해 사용자와의 상호작용을 초기화하기 위해서 상호작용 서비스 컴포넌트에 의존합니다. 이 접근법은 관심사의 명확한 분리와 테스트 용이성을 지원하는데, 이는 상호작용의 가시적 구현을 개별 서비스 컴포넌트에 캡슐화함으로써 수행됩니다. 일반적으로, 뷰 모델은 상호작용 서비스 인터페이스에 대한 의존성을 가집니다. 그것은 의존성 주입이나 서비스 로케이터를 통해 상호작용 서비스의 구현에 대한 참조를 빈번하게 요청합니다.


뷰 모델이 상호작용 서비스에 대한 참조를 가지게 되면, 그것은 프로그램적으로 필요할 때마다 사용자와의 상호작용을 여청할 수 있습니다. 상호작용 서비스는 상호작용의 가시적 관점을 구현하는데, 이는 다음 그림에 나와 있습니다. 뷰 모델에서 인터페이스 참조를 사용하는 것은 사용자 인터페이스의 구현 요구사항에 따라 여러 가지 구현이 사용될 수 있도록 허용해 줍니다. 예를 들어, WPF 와 실버라이트를 위한 상호작용 서비스의 구현이 제공될 수도 있는데, 이는 응용프로그램의 로직의 재사용성을 많이 높여 줍니다.


사용자와의 상호작용을 위해서 상호작용 서비스를 사용하기



Modal interactions, such as where the user is presented with MessageBox or modal pop-up window to obtain a specific response before execution can proceed, can be implemented in a synchronous way, using a blocking method call, as shown in the following code example.



그러나 이접근법의 단점은 동기적인 프로그래밍 모델을 강제한다는 것인데, 이는 상호작용 서비스를 구현할 때 실버라이트에서 다른 상호작용 메커니즘들과는 공유되지 않고, 매우 많은 어려움을 겪게 만듭니다. 대안적인 비동기적 구현은 상호작용이 완료되었을 때 실행될 콜백을 뷰 모델이 제공하도록 허용하는 것입니다. 다음 코드는 이러한 접근법을 설명합니다.



비동기 접근법은 modal 및 non-modal 상호작용이 구현될 수 있도록 허용함으로써 상호작용 서비스를 구현할 때 유연성을 증대시켜 줍니다. 예를 들어, WPF 에서, MessageBox 클래스는 진짜로 사용자와의 modal 상호작용 구현하기 위해서 사용될 수 있습니다; 반면에, 실버라이트에서, 팝업 윈도우는 사용자와의 pseudo-modal 상호작용을 구현하기 위해서 사용될 수 있습니다.



상호작용 요청 오브젝트들을 사용하기


MVVM 에서 단순한 사용자 상호작용을 구현하기 위한 다른 접근법은, 뷰의 비헤이비어와 쌍을 이루는 상호작용 오브젝트를 통해서, 뷰 모델이 뷰 자체에 대한 상호작용 요청을 직접적으로 만드는 것을 허용하는 것입니다. 이 상호작용 요청 오브젝트는 이벤트들을 통해 상호작용 요청, 그것의 응답을 캡슐화하며, 뷰와 통신합니다. 뷰는 상호작용에 대한 사용자 경험 부분을 초기화하기 위해서 이러한 이벤트들을 구독합니다. 뷰는 일반적으로 뷰 모델에 의해서 제공되는 상호작용 요청 오브젝트에 바인딩되는 비헤이비어에 상호작용데 대한 사용자 경험을 캡슐화합니다. 다음은 이를 설명합니다.


사용자와의 상호작용을 위해서 상호작용 요청 오브젝트 사용하기



이 접근법은 단순한, 그리고 여전히 유연한 메커니즘을 제공하는데, 이는 뷰 모델과 뷰 사이의 명확한 관심사 분리를 보존합니다 - 이는 뷰 모델이 응용프로그램의 프리젠테이션 로직과 요청되는 사용자 상호작용들을 캡슐화하도록 해 주는 반면, 뷰가 상호작용의 가시적 관점을 완전하게 캡슐화할 수 있도록 해 줍니다. 뷰를 통해 기대한 사용자와의 상호작용을 포함하는, 뷰 모델의 구현이 쉽게 테스트될 수 있습니다. 그리고 UI 디자이너는, 상호작용을 위해 다양한 사용자 경험을 캡슐화하는 다양한 비헤이비어의 사용을 통해, 상호작용을 구현하는 방식을 선택하는데 있어 많은 유연성을 가지게 됩니다.


이 접근법은 MVVM 패턴을 사용해서 일관성을 가지는데, 뷰가 뷰 모델상에서 관찰하고 있는 상태 변경들을 반영할 수 있게 해 주며, 둘 사이의 데이터 통신을 위해서 양방향 데이터 바인딩을 사용합니다. 상호작용 요청 오브젝트 내의 상호작용에 대한 비가시적 요소들에 대한 캡슐화, 그리고 상호작용의 가시적 요소들을 관리하기 위해 관련 비헤비어를 사용하는 것은, 커맨드 오브젝트와 커맨드 비헤이비어가 사용되는 방식과 매우 유사합니다.


이 접근법은 프리즘에 의해 가깝게 채택됩니다. 프리즘 라이브러리는 IInteractionRequest 인터페이스와 InteractionRequest<T> 클래스를 통해서 이 패턴을 직접적으로 지원합니다. 뷰의 비헤이비어들은 이 인터페이스에 바인딩되며, 그것이 노출하는 이벤트를 구독합니다. InteractionRequeset<T> 클래스는 IInteractionRequest 인터페이스를 구현하고 뷰 모델이 상호작용을 초기화하고 요청을 위한 칸텍스트를 지정하고, 선택적으로 콜백 대리자를 지정하는 것을 허용하기 위해서 두 개의 Raise 메서드를 정의합니다.



뷰 모델에서 상호작용 요청들을 초기화하기


InteractionRequest<T> 클래스는 상호작용 요청 동안에 뷰와 뷰 모델의 상호작용을 조직합니다. Raise 메서드는 뷰 모델이 상호작용을 초기화하고, ( T 타입의 ) 칸텍스트 오브젝트를 지정하고, 상호작용이 완료되었을 때 호출될 콜백 메서드를 지정하는 것을 허용합니다. 칸텍스트 오브젝트는 뷰 모델이 사용자와의 상호작용 동안에 사용될 수 있는 데이터와 상태를 뷰에 넘기는 것을 허용합니다. 만약 콜백 메서드가 정의되어 있다면, 칸텍스트 오브젝트는 뷰 모델에 다시 넘겨질 것입니다; 이는 상호작용 동안에 사용자가 만든 어떤 변경이 뷰 모델로 다시 넘어 가는 것을 허용합니다.



프리즘은 미리 정의된 칸텍스트 클래스를 제공하는데, 이는 일반적인 상호작용 요청 시나리오들을 지원합니다. Notification 클래스는 모든 칸텍스트 오브젝트들을 위한 기저 클래스입니다. 이는 응용프로그램 내의 중요한 이벤트를 사용자에게 통지하기 위해서 상호작용 요청이 사용될 때 사용됩니다. 이는 두 개의 프라퍼티들을 제공합니다 - Title Content - 이것은 사용자에게 출력될 것입니다. 일반적으로 통지들은 단방향이며, 그래서 사용자가 이 값들을 상호작용하는 도중에 변경하는 것은 기대되지 않습니다.


Confirmation 클래스는 Notification 클래스를 상속하고, 세 번째 프라퍼티를 추가합니다 - Confirmed - 이것은 사용자가 확인을 누르거나 작업을 거부했음을 표시하기 위해서 사용됩니다. Confirmation 클래스는 사용자로부터 yes/no 응답을 획득하기 원할 때 MessageBox 스타일 상호작용을 구현하기 위해서 사용됩니다. 당신은 Notification 클래스를 상속하는 커스텀 칸텍스트 클래스를 정의해서 당신이 상호작용을 지원하는데 필요로 하는 데이터와 상태를 캡슐화할 수 있습니다.


InteractionRequest<T> 클래스를 사용하기 위해, 뷰 모델 클래스는 InteractionRequest<T> 클래스의 인스턴스를 생성하고, 뷰가 그것에 데이터 바인딩되는 것을 허용하기 위한 읽기 전용 프라퍼티를 정의할 것입니다. 뷰 모델이 요청을 초기화하고자 할 때, 그것은 Raise 메서드를 호출하는데, 칸텍스트 오브젝트를 넘기고, 선택적으로 콜백 대리자를 넘길 것입니다.



MVVM Reference Implementation( MVVM RI ) 는 조사( survey ) 응용프로그램에서 뷰와 뷰 모델 간의 사용자 상호작용을 구현하기 위해서 IInteractionRequest 인터페이스와 InteractionRequest<T> 클래스가 사용되는 방법에 대해 설명합니다( QuestionnaireViewMode.cs 를 참조하십시오 ).



상호작용 사용자 경험을 구현하기 위해서 비헤이비어들을 사용하기


상호작용 요청 오브젝트는 논리적인 상호작용을 표현하기 때문에, 상호작용을 위한 정확한 사용자 경험은 뷰에 정의됩니다. 상호작용을 위한 사용자 경험을 캡슐화하기 위해서 보통 비헤이비어가 사용됩니다; 이는 UI 디자이너가 적절한 비헤이비어를 선택하고 그것을 뷰 모델의 상호작용 요청 오브젝트에 바인딩할 수 있도록 해 줍니다.


뷰는 반드시 상호작용 요청 이벤트를 검출할 수 잇도록 설정되어야만 하고, 그리고 나서 그 요청을 위한 적절한 가시적 출력을 제출할 수 있도록 설정되어야만 합니다. Microsoft Expression Blend Behavior Framework 은 트릭거와 액션의 개념을 지원합니다. 트리거는 특정 이벤트가 발생할 때마다 액션을 초기화하기 위해서 사용됩니다.


 Expression Blend 에 의해서 제공되는 표준 EventTrigger 는, 뷰 모델에 의해서 노출된 상호작용 요청 오브젝트들에 바인딩됨으로써, 상호작용 요청 이벤트를 모니터링합니다. 그러나 프리즘 라이브러리는 커스텀 EventTrigger 를 정의하느데, InteractionRequestTrigger 라는 이름을 가지고 있고, 그것은 IInteractionRequest 인터페이스에 대한 적절한 Raised 이벤트에 자동적으로 연결됩니다. 이는 필요한 XAML 의 양을 줄여 주고, 잘못된 이벤트 이름이 부주의하게 들어가는 경우를 줄여 줍니다.


이벤트가 발생하고 나면, InteractionRequestTrigger 가 특정 액션을 실행합니다. 실버라이트에 대해, 프리즘 라이브러리는 PopupChildWindowAction 클래스를 제공하는데, 이는 사용자에게 팝업 윈도우를 보여 줍니다. 자식 윈도우가 출력될 때, 그것의 데이터 칸텍스트는 상호작용 요청에 대한 칸텍스트 파라미터로 설정됩니다. PopupChildWindowAction ContentTemplate 프라퍼티를 사용하면, 칸텍스트 오브젝트의 Content 프라퍼티를 위해 사용되는 UI 레이아웃을 정의하기 위한 데이터 템플릿을 지정할 수 있습니다.


팝업 윈도우의 타이틀은 칸텍스트 오브젝트의 Title 프라퍼티에 바인딩됩니다.


노트:

기본적으로, PopupChildWindowAction 클래스에 의해 출력되는 특정 타입의 팝업 윈도우는 칸텍스트 오브젝트의 타입에 의존합니다. Notification 칸텍스트 오브젝트에 대해서는 NotificationChildWindow  가 출력되는 반면에, Confirm 칸텍스트 오브젝트에 대해서는 ConfirmationChildWindow 가 출력됩니다. NotificationChildWindow 는 간단한 팝업 윈도우를 출력해 통지를 보여 주는 반면에, ConfirmationChildWindow OK Cancel 버튼을 가지고 있어서 사용자의 응답을 캡쳐합니다. 당신은 이 비헤이비어를 재정의할 수 있는데, 이는 PopupChildWindowAction 클래스의 ChildWindow 프라퍼티를 사용하여 팝업 윈도우를 지정합으로써 수행됩니다.


다음의 예제는 InteractionRequestTrigger PopupChildWindowAction 을 사용해서 MVVM RI 에서 사용자를 위한 확인용 팝업 윈도우를 출력하는 방법을 보여 줍니다.



노트:

ContentTemplate 프라퍼티를 사용해서 지정되는 데이터 템플릿은 칸텍스트 오브젝트의 Content 프라퍼티를 위한 UI 레이아웃을 정의합니다. 앞의 코드에서, Content 프라퍼티는 문자열이며, 그래서 TextBlock Content 프라퍼티 자체에 바인딩됩니다.


사용자가 팝업 윈도우와 상호작용할 때, 칸텍스트 오브젝트는 팝업 윈도우에서 정의된 바인딩이나 칸텍스트 오브젝트의 Context 프라퍼티를 출력하기 위해서 사용된 데이터 템플릿에 따라 갱신됩니다. 사용자가 팝업 윈도우를 닫은 후에는, 콜백 베서드를 통해서 칸텍스트 오브젝트가 갱신된 값과 함께 뷰 모델에 다시 넘겨 집니다. MVVM RI 에서 사용된 확인 예제에서, 기본 확인 뷰는, OK 버튼이 클릭될 때, 제공된 Confirmation 오브젝트의 Confirmed 프라퍼티를 true 로 설정할 책임이 있습니다.


다른 상호작용 메커니즘을 정의하기 위해서 다양한 트리거들과 액션들이 정의될 수 있습니다. 프리즘의 InteractionRequestTrigger PopupChildWindowAction 클래스에 대한 구현은 당신만의 트리거들이나 액션들을 개발하기 위한 기반으로 사용될 수 있습니다.



진보된 생성 및 엮기( wire-up )



MVVM 패턴을 성공적으로 구현하기 위해서는, 뷰, 모델, 뷰 모델의 책임을 완전하게 이해해야 할 필요가 있습니다. 그래야 올바른 클래스들로 응용프로그램 코드를 구현할 수 있습니다. ( 데이터 바인딩, 커맨드, 상호작용 요청 등을 통해 ) 이러한 클래스들이 상호작용할 수 있도록 만들기 위해 올바른 패턴들을 구현하는 것도 중요한 요구사항입니다. 마지막 단계는 뷰, 뷰 모델, 모델을 런타임에 인스턴스화하고 서로 연관시키는 방법을 고려하는 것입니다.


만약 당신이 응용프로그램에서 종속성 주입 컨테이너를 사용하고 있다면, 이 단계를 관리하기 위한 적절한 전략을 선택하는 것이 특히 중요합니다. MEF 와 유니티는 모두 뷰, 뷰 모델, 모델 사이의 종속성을 지정하기 위한 기능과 런타임에 컨테이너에 의해 그것들을 수행하도록 하는 기능을 제공합니다.


일반적으로, 당ㅅ니은 뷰 모델을 뷰에 대한 의존성으로 정의할 수 있습니다. 그래서 ( 컨테이너를 사용해 ) 뷰가 생성될 때, 그것은 자동적으로 요청된 뷰 모델을 인스턴스화합니다. 다음으로, 뷰 모델이 의존하는 모든 컴포넌트들과 서비스들도 컨테이너에 의해 인스턴스화될 것입니다. 뷰 모델이 성공적으로 생성된 후에는, 뷰가 그것을 자신의 데이터 칸텍스트로 설정하게 됩니다.



MEF 를 사용하여 뷰 와 뷰 모델을 생성하기


MEF 를 사용하면, 당신은 import 애트리뷰트를 사용하여 뷰 모델에 대한 뷰의 의존성을 지정할 수 있습니다. 그리고 export 애트리뷰트를 통해서 인스턴스화될 concrete 뷰 모델 타입을 지정할 수 있습니다. 당신은 프라퍼티를 통해서 뷰에 뷰 모델을 임포트하거나, 뷰 모델을 생성자의 인자로 넘길 수 있습니다.


예를 들어, MVVM RI 뷰에서 Questionnaire 는 뷰 모델을 위한 쓰기 전용 프라퍼티를 선언하며, import 애트리뷰트를 지정합니다. 뷰가 인스턴스화될 때, MEF 는 익스포트된 적절한 뷰 모델의 인스턴스를 생성하고, 프라퍼티 값을 설정합니다. 이 프러퍼티 세터는 뷰 모델을 뷰의 데이터 칸텍스트로 할당합니다. 아래에 그 예가 나와 있습니다.



뷰 모델은 아래와 같이 정의되고 익스포트됩니다.



노트:

MEF 와 유니티에서는 모두 프라퍼티 주입과 생성자 주입을 사용할 수 있습니다; 그러나 당신은 프라퍼티 주입이 더 단순하다는 것을 알게 될 것입니다. 왜냐하면 두 개의 생성자를 유지할 필요가 없기 때문입니다. Visual Studio 나 Expression Blend 와 같은 디자인-타임 툴은 컨틀롤들을 디자이너에 출력하기 위해서 컨트롤들이 파라미터 없는 기본 생성자를 가질 것을 요구합니다. 당신이 정의하는 부가적인 생성자들은 기본 생성자가 호출되는 것을 보장해야만 합니다. 그래야지 뷰가 InitializeComponent 메서드를 통해 적절하게 초기화될 수 있습니다.



유니티를 사용하여 뷰와 뷰 모델을 생성하기


종속성 주입 컨테이너로 유니티를 사용하는 것은 MEF 를 사용하는 것과 유사하며, 프라퍼티-기반 주입과 생성자-기반 주입이 모두 지원됩니다. 기본적인 차이는 보통 타입들이 런타임에 묵시적으로 검색되지 않는다는 것입니다; 그 대신에, 그것들은 컨테이너에 등록되어야만 합니다.


일반적으로 당신은 뷰 모델의 인터페이스를 정의하므로, 뷰 모델의 특정 concrete 타입이 뷰와 디커플링될 수 있습니다. 예를 들어, 뷰는 뷰 모델에 대한 자신의 의존성을 아래와 같이 생성자 인자를 통해서 정의할 수 있습니다.



노트:

파라미터 없는 기본 생성자는 뷰가 Visual Studio 나 Expression Blend 와 같은 디자인 타임 툴에서 작동할 수 있도록 허용하기 위해 필요합니다.


대안적으로, 당신은 뷰의 쓰기 전용 뷰 모델 프라퍼티를 아래와 같이 정의할수 있습니다. 유니티는 요청된 뷰를 인스턴스화할 것이고, 뷰가 인스턴스화된 이후에 프라퍼티 세터를 호출할 것입니다.



뷰 모델 타입은 유니티 컨테이너에 아래와 같이 등록됩니다.



그리고 나서 뷰는 컨테이너를 통해서 아래와 같이 초기화됩니다.




외부( external ) 클래스를 사용하여 뷰와 뷰 모델을 생성하기


보통, 당신은 뷰 및 뷰 모델의 인스턴스화를 조직하기 위해서 컨트롤러나 서비스를 정의하는 것이 유용합을 발견하게 될 것입니다. 이 접근법은 MEF 나 유니티와 같은 의존성 주입 컨테이너와 함께 사용될 수 있거나, 뷰가 명시적으로 자신이 요구하는 뷰 모델을 생성할 때 사용될 수 있습니다.


이 접근법은 응용프로그램에서 네비게이션을 구현할 때 특히 유용합니다. 이 경우에, 컨트롤러는 placeholder 컨트롤이나 UI 의 리전( region )과 연관되며, 그것은 뷰의 생성과 배치를 그 placeholder 나 리전으로 조직합니다.


예를 들어, MVVM RI 는 컨테이너를 사용하여 뷰를 생성하는 서비스 클래스를 사용하고, 그것들을 메인 페이지에서 보여 줍니다. 이 예제에서, 뷰는 뷰 이름에 의해 지정됩니다. 네비게이션은 아래와 같이 UI 서비스의 ShowView 메서드에 대한 호출을 통해 인스턴스화됩니다.



UI 서비스는 응용프로그램 UI 에 있는 placeholder 컨트롤에 연관됩니다; 그것은 요청된 뷰의 생성을 캡슐화하고, UI 에서의 외형을 조직화합니다. UIService 의 ShowView 는 아래와 같이 컨테이너를 통해서 뷰의 인스턴스를 생성하고( 그래서 그것의 뷰 모델과 다른 의존성들이 수행될 수 있다 ) 그것을 적절한 위치에 출력합니다.



노트:

프리즘은 리전을 사용하여 네비게이션을 위한 확장적인 지원을 제공하니다. 리전 네비게이션은 앞의 접근법과 매우 유사한 메커니즘을 사용하는데, 리전 관리자가 지정된 영역 내에 뷰의 인스턴스화와 배치를 조직화할 책임이 있다는 차이가 있습니다. 더 많은 정보를 원한다면, 챕터 8 "네비게이션" 의 "뷰-기반 네비게이션" 섹션을 참조하십시오.



MVVM 응용프로그램들을 테스트하기



중략...



More Information



For more information about the logical tree, see "Trees in WPF" on MSDN:
http://msdn.microsoft.com/en-us/library/ms753391.aspx


For more information about attached properties, see "Attached Properties Overview" on MSDN:
http://msdn.microsoft.com/en-us/library/cc265152(VS.95).aspx


For more information about MEF, see "Managed Extensibility Framework Overview" on MSDN:
http://msdn.microsoft.com/en-us/library/dd460648.aspx.


For more information about Unity, see "Unity Application Block" on MSDN:
http://www.msdn.com/unity.


For more information about DelegateCommand, see Chapter 5, "Implementing the MVVM Pattern."


For more information about using Microsoft Expression Blend behaviors, see "Working with built-in behaviors" on MSDN:

http://msdn.microsoft.com/en-us/library/ff724013(v=Expression.40).aspx.


For more information about creating custom behaviors with Microsoft Expression Blend, see "Creating Custom Behaviors" on MSDN:
http://msdn.microsoft.com/en-us/library/ff724708(v=Expression.40).aspx.


For more information about creating custom triggers and actions with Microsoft Expression Blend, see "Creating Custom Triggers and Actions" on MSDN:
http://msdn.microsoft.com/en-us/library/ff724707(v=Expression.40).aspx.


For more information about using the dispatcher in WPF and Silverlight, see "Threading Model" and "The Dispatcher Class" on MSDN:
http://msdn.microsoft.com/en-us/library/ms741870.aspx
http://msdn.microsoft.com/en-us/library/ms615907(v=VS.95).aspx.


For more information about unit testing in Silverlight, see "Unit Testing with Silverlight 2":
http://www.jeff.wilcox.name/2008/03/silverlight2-unit-testing/.


For more information about region navigation, see the section, "View-Based Navigation" in Chapter 8, "Navigation."


For more information about the Event-based Asynchronous pattern, see "Event-based Asynchronous Pattern Overview" on MSDN:
http://msdn.microsoft.com/en-us/library/wewwczdw.aspx


For more information about the IAsyncResult design pattern, see "Asynchronous Programming Overview" on MSDN:
http://msdn.microsoft.com/en-us/library/ms228963.aspx


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

MVVM 패턴 구현하기  (0) 2014.09.16
모듈러 응용프로그램 개발  (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

프리즘 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