출처: http://lifeisforu.tistory.com/290
진보된 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 클래스를 참조하십시오 ).
1 2 3 4 | commandProxy.SubmitAllOrdersCommand.RegisterCommand( orderCompositeViewModel.SubmitCommand ); commandProxy.CancelAllOrdersCommand.RegisterCommand( orderCompositeViewModel.CancelCommand ); |
노트:
앞의 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. 모든 활성화된 커맨드들을 실행합니다. 비활성화된 자식 커맨드들은 전혀 고려되지 않습니다.
예를 들어, 다음 그림에서 보여 주는 응용프로그램에서, 뷰는 ListBox 컨트롤에서 아이템 컬렉션을 출력합니다. 그리고 사용자가 컬렉션에서 개별 아이템들을 제거할 수 있도록 하는 Delete 버튼을 정의하는 아이템을 출력하기 위해서 데이터 템플릿을 사용합니다.
컬렉션 내의 커맨드들을 바인딩하기
뷰 모델이 Delete 커맨드를 구현하기 때문에, 각 아이템을 위한 UI 의 Delete 버튼을 뷰 모델에 의해 구현된 Delete 커맨드와 엮는 것이 도전 과제입니다. ListBox 의 각 아이템들에 대한 데이터 칸텍스트가 Delete 커맨드를 구현하는 부모 뷰 모델이 아니라 컬렉션 내의 아이템들을 참조하고 있는 것이 어려운 점입니다.
이 문제를 해결하는 한 가지 접근법은, 데이터 템플릿이 아니라 부모 컨트롤에 상대적인 바인딩을 보장하도록 하기 위해, ElementName 바인딩 프라퍼티를 사용하여 부모 뷰의 커맨드에 데이터 템플릿의 버튼을 바인딩하는 것입니다. 다음 XAML 은 이 기법을 설명하고 있습니다.
1 2 3 4 5 6 7 8 9 10 | < Grid x:Name = "root" > < ListBox ItemsSource = "{Binding Path=Items}" > < ListBox.ItemTemplate > < DataTemplate > < Button Content = "{Binding Path=Name}" Command = "{Binding ElementName=root, Path=DataContext.DeleteCommand}" /> </ DataTemplate > </ ListBox.ItemTemplate > </ ListBox > </ Grid > |
데이터 템플릿의 버튼 컨트롤의 내용은 컬렉션 아이템의 Name 프라퍼티에 바인딩된다. 하지만 버튼을 위한 커맨드는 root 엘리먼트의 데이터 칸텍스트를 통해 Delete 커맨드에 바인딩됩니다. 이는 버튼이 아이템 레벨이 아니라 부모 뷰 레벨에서 커맨드에 바인딩될 수 있도록 허용해 줍니다. 당신은 CommandParameter 프라퍼티를 사용해서 아이템에 적용될 커맨드가 무엇인지를 지정하거나, ( CollectionView 를 통해 ) 현재 선택된 아이템에서 수행될 커맨드를 구현할 수 있습니다.
커맨드 비헤이비어
실버라이트 3 이전 버전에서, 실버라이트는 직접적으로 커맨드들을 지원하는 컨트롤들을 제공하지 않았습니다. ICommand 인터페이스가 이용 가능했지만, 컨트롤들이 ICommand 구현에 직접적으로 연결될 수 있도록 허용해주는 Command 프라퍼티를 구현한 컨트롤이 존재하지 않았습니다. 이 한계를 극복하기 위해서, 그리고 실버라이트 3 에서 MVVM 커맨딩 패턴들을 지원하기 위해서, 프리즘 라이브러리( 버전 2.0 )은 모든 실버라이트 컨트롤들이 연결된 비헤이비어를 사용해 커맨드 오브젝트에 바인딩될 수 있도록 허용하는 메커니즘을 제공했습니다. 이 메커니즘은 WPF 에서도 작동하는데, 이는 뷰 모델 구현들이 실버라이트와 WPF 에서 재사용될 수 있도록 해 주었습니다.
다음 예제는 버튼의 클릭 이벤트에 뷰에서 정의한 커맨드 오브젝트가 바인딩될 수 있도록 하기 위해서 프리즘 커맨드 비헤이비어가 사용되는 방식을 보여 줍니다.
1 2 3 | < Button Content = "Submit All" prism:Click.Command = "{Binding Path=SubmitAllCommand}" prism:Click.CommandParameter = "{Binding Path=TickerSymbol}" /> |
실버라이트 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 클래스의 구현을 보여 줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class ButtonBaseClickCommandBehavior : CommandBehaviorBase<ButtonBase> { public ButtonBaseClickCommandBehavior(ButtonBase clickableObject) : base (clickableObject) { clickableObject.Click += onClick; } private void OnClick( object sender, System.Windows.RoutedEventArgs e) { ExecuteCommand(); } } |
CommandBehaviorBase<T> 클래스를 사용하면, 자신만의 커스텀 비헤이비어 클래스를 정의할 수 있습니다; 이는 타깃 컨트롤이나 뷰 모델에 의해 제공되는 커맨드들과 비헤이비어가 상호작용하는 방식을 커스터마이즈 할 수 있도록 해 줍니다. 예를 들어, 당신은 다양한 컨트롤 이벤트에 기반해 바인딩된 커맨드를 실행하거나 바인딩된 커맨드의 CanExecute 상태에 기반해 컨트롤의 가시적 상태를 변경하는 비헤이비어를 정의할 수 있습니다.
타깃 컨트롤에 커맨드 비헤이비어를 선언적으로 어태치하는 것을 지원하기 위해서, 어태치드 프라퍼티가 사용됩니다. 어태치드 프라퍼티는 비헤이비어가 XAML 에서 컨트롤에 어태치되는 것을 허용하며, 타깃 컨트롤과 비헤이비어 구현의 연관 및 생성을 관리합니다. 어태치드 프라퍼티는 정적 클래스에서 정의됩니다. 프리즘 커맨드 비헤이비어는 어떤 규약에 기반하는데, 그것은 커맨드를 실행하기 위해서 사용되는 이벤트를 가리키는 정적 클래스의 이름입니다. 어태치드 프라퍼티의 이름은 데이터 바인딩되고 있는 오브젝트의 타입을 가리킵니다. 그러므로 앞서 설명한 프리즘 커맨드 비헤이비어는 Click 이라 명명된 정적 클래스를 사용합니다. 이 클래스는 Command 라고 명명된 어태치드 프라퍼티를 정의합니다. 이는 앞에서 보여 준 Click.Command 라는 구문을 사용하는 것을 허용합니다.
커맨드 비헤이비어 자체가 어태치드 프라퍼티를 통해 타깃 컨트롤과 실제로 연관되기도 합니다. 그러나 이 어태치드 프라퍼티는 정적 클래스에 대해 private 이며, 개발자는 볼 수 없습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached( "Command" , typeof (ICommand), typeof (Click), new PropertyMetadata(OnSetCommandCallback)); private static readonly DependencyProperty ClickCommandBehaviorProperty = DependencyProperty.RegisterAttached( "ClickCommandBehavior" , typeof (ButtonBaseClickCommandBehavior), typeof (Click), null ); |
Command 어태치드 프라퍼티의 구현은 ButtonBaseCommandBehavior 클래스의 인스턴스를 생성합니다. 이는 onSetCommandCallback 콜백 메서드를 통해서 이루어 집니다. 그것이 아래 코드 예제에 나와 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | private static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { ButtonBase buttonBase = dependencyObject as ButtonBase; if (buttonBase != null ) { ButtonBaseClickCommandBehavior behavior = GetOrCreateBehavior(buttonBase); behavior.Command = e.NewValue as ICommand; } } private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { ButtonBase buttonBase = dependencyObject as ButtonBase; if (buttonBase != null ) { ButtonBaseClickCommandBehavior behavior = GetOrCreateBehavior(buttonBase); behavior.CommandParameter = e.NewValue; } } private static ButtonBaseClickCommandBehavior GetOrCreateBehavior( ButtonBase buttonBase ) { ButtonBaseClickCommandBehavior behavior = buttonBase.GetValue(ClickCommandBehaviorProperty) as ButtonBaseClickCommandBehavior; if ( behavior == null ) { behavior = new ButtonBaseClickCommandBehavior(buttonBase); buttonBase.SetValue(ClickCommandBehaviorProperty, behavior); } return behavior; } |
어태치드 프라퍼티에 대한 더 많은 정보를 원한다면 MSDN 의 "Attached Properites Overview" 를 참조하십시오.
비동기 상호작용들을 다루기
보통 뷰 모델들을 비동기적으로 통신하는 서비스들이나 컴포넌트들과 상호작용할 필요가 있습니다.이는 당신이 실버라이트 응용프로그램을 다루거나, 웹서비스타 네트워크를 통한 다른 리소스들과 상호작용하거나, 응용프로그램이 계산이나 I/O 를 수행하기 위해서 백그라운드 작업을 사용할 때 특히 그렇습니다. 이러한 연산을 비동기적으로 수행하는 것은 응용프로그램이 좋은 사용자 경험을 배달하는데 필수적인 응답성을 유지하고 있음을 보장해 줍니다.
사용자가 비동기적인 요청이나 백그라운드 작업을 초기화할 때, 그 응답이 언제 도착할지와 스레드가 언제 반환될지를 예측하는 것은 어렵습니다. UI 는 UI 스레드에서만 갱신될 수 있기 때문에, 당신은 UI 스레드 상에서 요청을 디스패치함으로써 UI 를 갱신해야 할 것입니다.
웹 서비스들에서 데이터를 획득하고 상호작용하기
웹 서비스나 원격 접근 기법들과 상호작용할 때, 당신은 보통 IAsyncResult 패턴을 사용할 것입니다. 이 패턴에서는 GetQuestionnaire 같은 메서드를 호출하는 대신, BeginGetQuestionnaire 와 EndGetQuestionnaire 와 같은 메서드 쌍을 사용합니다. 비동기 호출을 초기화하기 위해서 BeginGetQuestionnaire 를 호출합니다. 결과를 획득하거나 타깃 메서드를 실행하는 도중에 예외가 발생하는지를 확인하기 위해서, 호출이 완료되었을 때 EndGetQuestionnaire 를 호출합니다.
EndGetQuestionnaire 의 호출 시점을 결정하려면, BeginGetQuestionnaire 를 호출하는 동안에 사용할 콜백을 지정하거나 완료되었는지 상황을 조사할 수 있습니다. 콜백 접근법을 사용하면, 당신의 콜백 메서드가 타깃 메서드 실행이 완료되었을 때 호출될 것입니다. 그러면 거기에서 EndGetQuestionnaire 를 호출합니다. 그 예가 아래에 나와 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | IAsyncResult asyncResult = this .service.BeginGetQuestionnaire(GetQuestionnaireCompleted, null // object state, not used in this example); private void GetQuestionnaireCompleted(IAsyncResult result) { try { questionnaire = this .service.EndGetQuestionnaire(ar); } catch (Exception ex) { // Do something to report the error. } } |
End 메서드( 이 경우 EndGetQuestionnaire )에 대한 호출에서 주의할 점이 있습니다. 요청을 실행하는 동안에 발생한 예외가 실행될 수도 있다는 것입니다. 당신의 응용프로그램은 반드시 이를 처리해야만 하며, UI 를 통해 스레드 안전한 방법으로 그들에게 보고해 줘야할 필요가 있을 것입니다. 만약 이것들을 다루지 않는다면, 스레드는 종료될 것이고, 당신은 결과를 처리할 수 없게 될 것입니다.
응답이 항상 UI 스레드에서 발생하는 것은 아니기 때문에, 당신이 UI 상태에 영향을 줄 수 있는 무엇인가를 변경하려고 하는 계획득 세웠다면, 스레드 Dispatcher 오브젝트나 SynchronizationContext 오브젝트를 사용해서 UI 스레드에 그 응답을 디스패치해야 할 것입니다. WPF 와 실버라이트에서는 보통 디스패처를 사용할 것입니다.
다음 코드 에제에서, Questionnaire 오브젝트는 비동기적으로 획득되며, 그것은 QuestionnaireView 를 위한 데이터 컨텍스트로서 설정됩니다. 실버라이트에서 당신은 디스패처의 CheckAccess 메서드를 사용해서 당신이 지금 UI 스레드 상에서 작업하고 있는지 여부를 확인할 수 있습니다. 만약 UI 스레드가 아니라면, BeginInvoke 메서드를 사용해서 UI 스레드로 그 요청이 운반되도록 할 필요가 있습니다.
1 2 3 4 5 6 7 8 9 10 | var dispatcher = System.Windows.Deployment.Current.Dispatcher; if (dispatcher.CheckAccess()) { QuestionnaireView.DataContext = questionnaire; } else { dispatcher.BeginInvoke( () => { Questionnaire.DataContext = questionnaire; }); } |
Model-View-ViewModel Reference Implementation( MVVM RI ) 는 IAsyncResult-상속 서비스 인터페이스를 이전 예제와 유사한 방식으로 사용하는 방식의 예를 보여 줍니다. 이는 고객을 위해 더 단순한 콜백 메커니즘을 제공하기 위해서 서비스를 래핑하고, 호출자의 스레드에 콜백을 디스패치하기 하는 것을 다룹니다. 예를 들어, 다음 코드 예제는 설문지( questionnaire )를 획득하는 것을 보여 줍니다.
1 2 3 4 5 | this .questionnaireRepository.GetQuestionnaireAsync( (result) => { this .Questionnaire = result.Result; }); |
반환되는 result 오브젝트는 발생할 수 있는 에러들과 함께 획득한 결과를 래핑합니다. 다음 코드 예제는 에러가 평가되는 방법을 보여 줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 | this .questionnaireRepository.GetQuestionnaireAsync( (result) => { if (result.Error == null ) { this .Questionnaire = result.Result; ... } else { // Handle error. } }) |
유저 인터페이스 패턴들
응용프로그램들에서는 이벤트의 발생을 사용자에게 통지하거나 작업을 진행하기 전에 확인을 요청할 필요성이 빈번하게 발생합니다. 이러한 상호작용들은 보통 응용프로그램에서의 변화를 사용자들에게 단순하게 전달하기 위해서 설계된 짧은 상호작용이거나 사용자들로부터 간단한 응답을 획득합니다. 이러한 상호작용들 중의 일부는 사용에게 다이얼로그 박스나 메시지 박스를 출력할 때와 같이 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.
1 2 3 4 5 6 7 8 9 | var result = interactionService.ShowMessageBox( "Are you sure you want to cancel this operation?" , "Confirm" , MessageBoxButton.OK ); if (result == MessageBoxResult.Yes) { CancelRequest(); } |
그러나 이접근법의 단점은 동기적인 프로그래밍 모델을 강제한다는 것인데, 이는 상호작용 서비스를 구현할 때 실버라이트에서 다른 상호작용 메커니즘들과는 공유되지 않고, 매우 많은 어려움을 겪게 만듭니다. 대안적인 비동기적 구현은 상호작용이 완료되었을 때 실행될 콜백을 뷰 모델이 제공하도록 허용하는 것입니다. 다음 코드는 이러한 접근법을 설명합니다.
1 2 3 4 5 6 7 8 9 10 11 | interactionService.ShowMessageBox( "Are you sure you want to cancel this operation?" , "Confirm" , MessageBoxButton.OK, result => { if (result == MessageBoxResult.Yes) { CancelRequest(); } }); |
비동기 접근법은 modal 및 non-modal 상호작용이 구현될 수 있도록 허용함으로써 상호작용 서비스를 구현할 때 유연성을 증대시켜 줍니다. 예를 들어, WPF 에서, MessageBox 클래스는 진짜로 사용자와의 modal 상호작용 구현하기 위해서 사용될 수 있습니다; 반면에, 실버라이트에서, 팝업 윈도우는 사용자와의 pseudo-modal 상호작용을 구현하기 위해서 사용될 수 있습니다.
상호작용 요청 오브젝트들을 사용하기
MVVM 에서 단순한 사용자 상호작용을 구현하기 위한 다른 접근법은, 뷰의 비헤이비어와 쌍을 이루는 상호작용 오브젝트를 통해서, 뷰 모델이 뷰 자체에 대한 상호작용 요청을 직접적으로 만드는 것을 허용하는 것입니다. 이 상호작용 요청 오브젝트는 이벤트들을 통해 상호작용 요청, 그것의 응답을 캡슐화하며, 뷰와 통신합니다. 뷰는 상호작용에 대한 사용자 경험 부분을 초기화하기 위해서 이러한 이벤트들을 구독합니다. 뷰는 일반적으로 뷰 모델에 의해서 제공되는 상호작용 요청 오브젝트에 바인딩되는 비헤이비어에 상호작용데 대한 사용자 경험을 캡슐화합니다. 다음은 이를 설명합니다.
사용자와의 상호작용을 위해서 상호작용 요청 오브젝트 사용하기
이 접근법은 단순한, 그리고 여전히 유연한 메커니즘을 제공하는데, 이는 뷰 모델과 뷰 사이의 명확한 관심사 분리를 보존합니다 - 이는 뷰 모델이 응용프로그램의 프리젠테이션 로직과 요청되는 사용자 상호작용들을 캡슐화하도록 해 주는 반면, 뷰가 상호작용의 가시적 관점을 완전하게 캡슐화할 수 있도록 해 줍니다. 뷰를 통해 기대한 사용자와의 상호작용을 포함하는, 뷰 모델의 구현이 쉽게 테스트될 수 있습니다. 그리고 UI 디자이너는, 상호작용을 위해 다양한 사용자 경험을 캡슐화하는 다양한 비헤이비어의 사용을 통해, 상호작용을 구현하는 방식을 선택하는데 있어 많은 유연성을 가지게 됩니다.
이 접근법은 MVVM 패턴을 사용해서 일관성을 가지는데, 뷰가 뷰 모델상에서 관찰하고 있는 상태 변경들을 반영할 수 있게 해 주며, 둘 사이의 데이터 통신을 위해서 양방향 데이터 바인딩을 사용합니다. 상호작용 요청 오브젝트 내의 상호작용에 대한 비가시적 요소들에 대한 캡슐화, 그리고 상호작용의 가시적 요소들을 관리하기 위해 관련 비헤비어를 사용하는 것은, 커맨드 오브젝트와 커맨드 비헤이비어가 사용되는 방식과 매우 유사합니다.
이 접근법은 프리즘에 의해 가깝게 채택됩니다. 프리즘 라이브러리는 IInteractionRequest 인터페이스와 InteractionRequest<T> 클래스를 통해서 이 패턴을 직접적으로 지원합니다. 뷰의 비헤이비어들은 이 인터페이스에 바인딩되며, 그것이 노출하는 이벤트를 구독합니다.InteractionRequeset<T> 클래스는 IInteractionRequest 인터페이스를 구현하고 뷰 모델이 상호작용을 초기화하고 요청을 위한 칸텍스트를 지정하고, 선택적으로 콜백 대리자를 지정하는 것을 허용하기 위해서 두 개의 Raise 메서드를 정의합니다.
뷰 모델에서 상호작용 요청들을 초기화하기
InteractionRequest<T> 클래스는 상호작용 요청 동안에 뷰와 뷰 모델의 상호작용을 조직합니다. Raise 메서드는 뷰 모델이 상호작용을 초기화하고, ( T 타입의 ) 칸텍스트 오브젝트를 지정하고, 상호작용이 완료되었을 때 호출될 콜백 메서드를 지정하는 것을 허용합니다. 칸텍스트 오브젝트는 뷰 모델이 사용자와의 상호작용 동안에 사용될 수 있는 데이터와 상태를 뷰에 넘기는 것을 허용합니다. 만약 콜백 메서드가 정의되어 있다면, 칸텍스트 오브젝트는 뷰 모델에 다시 넘겨질 것입니다; 이는 상호작용 동안에 사용자가 만든 어떤 변경이 뷰 모델로 다시 넘어 가는 것을 허용합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public interface IInteractionRequest { event EventHandler<InteractionRequestedEventArgs> Raised; } public class InteractionRequest<T> : IInteractionRequest { public event EventHandler<InteractionRequestedEventArgs> Raised; public void Raise(T context, Action<T> callback) { var handler = this .Raised; if (handler != null ) { handler( this , new InteractionRequestedEventArgs( context, () => callback(context))); } } } |
프리즘은 미리 정의된 칸텍스트 클래스를 제공하는데, 이는 일반적인 상호작용 요청 시나리오들을 지원합니다. Notification 클래스는 모든 칸텍스트 오브젝트들을 위한 기저 클래스입니다. 이는 응용프로그램 내의 중요한 이벤트를 사용자에게 통지하기 위해서 상호작용 요청이 사용될 때 사용됩니다. 이는 두 개의 프라퍼티들을 제공합니다 - Title 과 Content - 이것은 사용자에게 출력될 것입니다. 일반적으로 통지들은 단방향이며, 그래서 사용자가 이 값들을 상호작용하는 도중에 변경하는 것은 기대되지 않습니다.
Confirmation 클래스는 Notification 클래스를 상속하고, 세 번째 프라퍼티를 추가합니다 - Confirmed - 이것은 사용자가 확인을 누르거나 작업을 거부했음을 표시하기 위해서 사용됩니다. Confirmation 클래스는 사용자로부터 yes/no 응답을 획득하기 원할 때 MessageBox 스타일 상호작용을 구현하기 위해서 사용됩니다. 당신은 Notification 클래스를 상속하는 커스텀 칸텍스트 클래스를 정의해서 당신이 상호작용을 지원하는데 필요로 하는 데이터와 상태를 캡슐화할 수 있습니다.
InteractionRequest<T> 클래스를 사용하기 위해, 뷰 모델 클래스는 InteractionRequest<T> 클래스의 인스턴스를 생성하고, 뷰가 그것에 데이터 바인딩되는 것을 허용하기 위한 읽기 전용 프라퍼티를 정의할 것입니다. 뷰 모델이 요청을 초기화하고자 할 때, 그것은 Raise 메서드를 호출하는데, 칸텍스트 오브젝트를 넘기고, 선택적으로 콜백 대리자를 넘길 것입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public IInteractionRequest ConfirmCancelInteractionRequest { get { return this .confirmCancelInteractionRequest; } } this .confirmCancelInteractionRequest.Raise( new Confirmation( "Are you sure you wish to cancel?" ), confirmation => { if (confirmation.Confirmed) { this .NavigateToQuestionnaireList(); } }); } |
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 에서 사용자를 위한 확인용 팝업 윈도우를 출력하는 방법을 보여 줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | < i:Interaction.Triggers > < prism:InteractionRequestTrigger SourceObject = "{Binding ConfirmCancelInteractionRequest}" > < prism:PopupChildWindowAction ContentTemplate = "{StaticResource ConfirmWindowTemplate}" /> </ prism:InteractionRequestTrigger > </ i:Interaction.Triggers > < UserControl.Resources > < DataTemplate x:Key = "ConfirmWindowTemplate" > < Grid MinWidth = "250" MinHeight = "100" > < TextBlock TextWrapping = "Wrap" Grid.Row = "0" Text = "{Binding}" /> </ Grid > </ DataTemplate > </ UserControl.Resources > |
노트:
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 는 익스포트된 적절한 뷰 모델의 인스턴스를 생성하고, 프라퍼티 값을 설정합니다. 이 프러퍼티 세터는 뷰 모델을 뷰의 데이터 칸텍스트로 할당합니다. 아래에 그 예가 나와 있습니다.
1 2 3 4 5 | [Import] public QuestionnaireViewModel ViewModel { set { this .DataContext = value; } } |
뷰 모델은 아래와 같이 정의되고 익스포트됩니다.
1 2 3 4 5 6 7 8 9 10 | public QuestionnaireView() { InitializeComponent(); } [ImportingConstructor] public QuestionnaireView(QuestionnaireViewModel viewModel) : this () { this .DataContext = viewModel; } |
노트:
MEF 와 유니티에서는 모두 프라퍼티 주입과 생성자 주입을 사용할 수 있습니다; 그러나 당신은 프라퍼티 주입이 더 단순하다는 것을 알게 될 것입니다. 왜냐하면 두 개의 생성자를 유지할 필요가 없기 때문입니다. Visual Studio 나 expression Blend 와 같은 디자인-타임 툴은 컨틀롤들을 디자이너에 출력하기 위해서 컨트롤들이 파라미터 없는 기본 생성자를 가질 것을 요구합니다. 당신이 정의하는 부가적인 생성자들은 기본 생성자가 호출되는 것을 보장해야만 합니다. 그래야지 뷰가 InitializeComponent 메서드를 통해 적절하게 초기화될 수 있습니다.
유니티를 사용하여 뷰와 뷰 모델을 생성하기
종속성 주입 컨테이너로 유니티를 사용하는 것은 MEF 를 사용하는 것과 유사하며, 프라퍼티-기반 주입과 생성자-기반 주입이 모두 지원됩니다. 기본적인 차이는 보통 타입들이 런타임에 묵시적으로 검색되지 않는다는 것입니다; 그 대신에, 그것들은 컨테이너에 등록되어야만 합니다.
일반적으로 당신은 뷰 모델의 인터페이스를 정의하므로, 뷰 모델의 특정 concrete 타입이 뷰와 디커플링될 수 있습니다. 예를 들어, 뷰는 뷰 모델에 대한 자신의 의존성을 아래와 같이 생성자 인자를 통해서 정의할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 | public QuestionnaireView() { InitializeComponent(); } public QuestionnaireView(QuestionnaireViewModel viewModel) : this () { this .DataContext = viewModel; } |
노트:
파라미터 없는 기본 생성자는 뷰가 Visual Studio 나 expression Blend 와 같은 디자인 타임 툴에서 작동할 수 있도록 허용하기 위해 필요합니다.
대안적으로, 당신은 뷰의 쓰기 전용 뷰 모델 프라퍼티를 아래와 같이 정의할수 있습니다. 유니티는 요청된 뷰를 인스턴스화할 것이고, 뷰가 인스턴스화된 이후에 프라퍼티 세터를 호출할 것입니다.
1 2 3 4 5 6 7 8 9 10 | public QuestionnaireView() { InitializeComponent(); } [Dependency] public QuestionnaireViewModel ViewModel { set { this .DataContext = value; } } |
뷰 모델 타입은 유니티 컨테이너에 아래와 같이 등록됩니다.
1 2 | IUnityContainer container; container.RegisterType<QuestionnaireViewModel>(); |
그리고 나서 뷰는 컨테이너를 통해서 아래와 같이 초기화됩니다.
1 2 | IUnityContainer container; var view = container.Resolve<QuestionnaireView>(); |
외부( external ) 클래스를 사용하여 뷰와 뷰 모델을 생성하기
보통, 당신은 뷰 및 뷰 모델의 인스턴스화를 조직하기 위해서 컨트롤러나 서비스를 정의하는 것이 유용합을 발견하게 될 것입니다. 이 접근법은 MEF 나 유니티와 같은 의존성 주입 컨테이너와 함께 사용될 수 있거나, 뷰가 명시적으로 자신이 요구하는 뷰 모델을 생성할 때 사용될 수 있습니다.
이 접근법은 응용프로그램에서 네비게이션을 구현할 때 특히 유용합니다. 이 경우에, 컨트롤러는 placeholder 컨트롤이나 UI 의 리전( region )과 연관되며, 그것은 뷰의 생성과 배치를 그 placeholder 나 리전으로 조직합니다.
예를 들어, MVVM RI 는 컨테이너를 사용하여 뷰를 생성하는 서비스 클래스를 사용하고, 그것들을 메인 페이지에서 보여 줍니다. 이 예제에서, 뷰는 뷰 이름에 의해 지정됩니다. 네비게이션은 아래와 같이 UI 서비스의 ShowView 메서드에 대한 호출을 통해 인스턴스화됩니다.
1 2 3 4 5 | private void NavigateToQuestionnaireList() { // UI 서비스에 "questionnaire list" 뷰로 이동할 것을 요청한다. this .uiService.ShowView(ViewNames.QuestionnaireTemplatesList); } |
UI 서비스는 응용프로그램 UI 에 있는 placeholder 컨트롤에 연관됩니다; 그것은 요청된 뷰의 생성을 캡슐화하고, UI 에서의 외형을 조직화합니다. UIService 의 ShowView 는 아래와 같이 컨테이너를 통해서 뷰의 인스턴스를 생성하고( 그래서 그것의 뷰 모델과 다른 의존성들이 수행될 수 있다 ) 그것을 적절한 위치에 출력합니다.
1 2 3 4 5 | public void ShowView( string viewName) { var view = this .ViewFactory.GetView(viewName); this .MainWindow.CurrentView = view; } |
노트:
프리즘은 리전을 사용하여 네비게이션을 위한 확장적인 지원을 제공하니다. 리전 네비게이션은 앞의 접근법과 매우 유사한 메커니즘을 사용하는데, 리전 관리자가 지정된 영역 내에 뷰의 인스턴스화와 배치를 조직화할 책임이 있다는 차이가 있습니다. 더 많은 정보를 원한다면, 챕터 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
'IT_Architecture > Architecture' 카테고리의 다른 글
[Prism 4.1] MVVM 패턴 구현하기 (0) | 2015.08.28 |
---|---|
페이스북의 결정: MVC는 확장에 용이하지 않다. 그렇다면 Flux다. (0) | 2015.08.25 |
SOA - JAVA로 SOAP 구축하기 (1) ~ (3) (0) | 2011.07.12 |
REST Architecture (0) | 2007.08.09 |
일반 웹 아키텍처 (0) | 2007.07.04 |