MVVM-퀵 가이드
코드를 구성하는 가장 잘 정리되고 재사용 가능한 방법은 'MVVM'패턴을 사용하는 것입니다. 그만큼Model, View, ViewModel (MVVM pattern) 유지 관리, 테스트 및 확장 가능한 응용 프로그램을 작성하기 위해 코드를 구성하고 구성하는 방법을 안내합니다.
Model − 단순히 데이터를 보유하고 비즈니스 로직과 아무 관련이 없습니다.
ViewModel − Model과 View 사이의 연결 / 연결 역할을하며 물건을 예쁘게 만듭니다.
View − 단순히 형식화 된 데이터를 보유하고 본질적으로 모든 것을 모델에 위임합니다.
분리 된 프레젠테이션
응용 프로그램 논리를 코드 숨김 또는 XAML에 배치하여 발생하는 문제를 방지하려면 분리 된 프레젠테이션이라는 기술을 사용하는 것이 가장 좋습니다. 사용자 인터페이스 개체로 직접 작업하는 데 필요한 최소한의 XAML 및 코드 숨김이있는 경우이를 방지하려고합니다. 사용자 인터페이스 클래스에는 또한 왼쪽의 다음 그림과 같이 복잡한 상호 작용 동작, 응용 프로그램 논리 및 기타 모든 것에 대한 코드가 포함되어 있습니다.
분리 된 프리젠 테이션을 사용하면 사용자 인터페이스 클래스가 훨씬 간단 해집니다. 물론 XAML이 있지만 그 뒤에있는 코드는 실제적인 작업을 거의 수행하지 않습니다.
애플리케이션 로직은 종종 모델이라고하는 별도의 클래스에 속합니다.
그러나 이것은 전체 이야기가 아닙니다. 여기서 멈 추면 데이터 바인딩 광기의 길로 이끄는 매우 일반적인 실수를 반복 할 수 있습니다.
많은 개발자가 데이터 바인딩을 사용하여 XAML의 요소를 모델의 속성에 직접 연결하려고합니다.
이제 때때로 이것은 괜찮을 수 있지만 종종 그렇지 않습니다. 문제는 모델이 사용자가 응용 프로그램과 상호 작용하는 방식이 아니라 응용 프로그램이 수행하는 문제와 전적으로 관련되어 있다는 것입니다.
데이터를 제시하는 방식은 내부적으로 구조화 된 방식과 다소 다릅니다.
또한 대부분의 사용자 인터페이스에는 애플리케이션 모델에 속하지 않는 일부 상태가 있습니다.
예를 들어, 사용자 인터페이스에서 드래그 앤 드롭을 사용하는 경우 항목이 지금 드래그되는 위치, 가능한 드롭 대상 위로 이동할 때 모양이 어떻게 변경되어야하는지, 드롭 대상이 어떻게 될 수 있는지 등을 추적해야합니다. 항목을 드래그하면 변경됩니다.
이러한 종류의 상태는 놀라 울 정도로 복잡해질 수 있으며 철저한 테스트가 필요합니다.
실제로는 일반적으로 사용자 인터페이스와 모델 사이에 다른 클래스가 있어야합니다. 여기에는 두 가지 중요한 역할이 있습니다.
첫째, 특정 사용자 인터페이스보기에 맞게 애플리케이션 모델을 조정합니다.
둘째, 사소한 상호 작용 논리가 존재하는 곳입니다. 즉, 사용자 인터페이스가 원하는 방식으로 작동하도록하는 데 필요한 코드를 의미합니다.
MVVM 패턴은 궁극적으로 MVC 패턴의 현대적인 구조이므로 도메인 로직과 프리젠 테이션 레이어를 명확하게 분리하는 것이 주요 목표는 동일합니다. 다음은 MVVM 패턴의 장점과 단점 중 일부입니다.
주요 이점은 분리와이를 통해 얻는 효율성을 넘어서 뷰와 모델 간의 진정한 분리를 허용한다는 것입니다. 이것이 실제로 의미하는 바는 모델을 변경해야 할 때 뷰없이 쉽게 변경할 수 있으며 그 반대의 경우도 마찬가지입니다.
MVVM 적용에서 흘러 나오는 세 가지 중요한 핵심 사항은 다음과 같습니다.
유지 보수성
서로 다른 종류의 코드를 깔끔하게 분리하면 더 세분화되고 집중된 부분 중 하나 또는 여러 부분으로 쉽게 이동하여 걱정없이 변경할 수 있습니다.
즉, 민첩성을 유지하고 새 릴리스로 빠르게 이동할 수 있습니다.
테스트 가능성
MVVM을 사용하면 각 코드 조각이 더 세분화되어 제대로 구현되면 외부 및 내부 종속성이 테스트하려는 핵심 논리가있는 부분과 별도의 코드 조각에 있습니다.
따라서 핵심 로직에 대한 단위 테스트를 훨씬 쉽게 작성할 수 있습니다.
작성시 올바르게 작동하는지 확인하고 유지 보수 작업이 변경 되어도 계속 작동하는지 확인하십시오.
확장 성
깨끗한 분리 경계와 더 세분화 된 코드 조각으로 인해 유지 관리 가능성과 겹치는 경우가 있습니다.
이러한 부품을 더 재사용 가능하게 만들 가능성이 더 높습니다.
또한 아키텍처의 올바른 위치에서 유사한 작업을 수행하는 새로운 코드 조각을 대체하거나 추가하는 기능도 있습니다.
MVVM 패턴의 명백한 목적은 코드 숨김에서 비즈니스 로직의 양을 줄이는 View의 추상화입니다. 그러나 다음은 다른 확실한 장점입니다.
- ViewModel은 코드 숨김 또는 이벤트 기반 코드보다 단위 테스트가 더 쉽습니다.
- 어색한 UI 자동화 및 상호 작용없이 테스트 할 수 있습니다.
- 프레젠테이션 레이어와 로직은 느슨하게 결합됩니다.
단점
- 어떤 사람들은 단순한 UI의 경우 MVVM이 과도 할 수 있다고 생각합니다.
- 마찬가지로 더 큰 경우에는 ViewModel을 디자인하기가 어려울 수 있습니다.
- 복잡한 데이터 바인딩이있는 경우 디버깅이 약간 어려울 수 있습니다.
MVVM 패턴은 Model, View, ViewModel의 세 부분으로 구성됩니다. 처음에 대부분의 개발자는 Model, View 및 ViewModel이 포함해야하거나 포함하지 않아야하는 내용과 각 부분의 책임에 대해 거의 혼동하지 않습니다.
이 장에서는 MVVM 패턴의 각 부분에 대한 책임을 배워 어떤 종류의 코드가 어디에 있는지 명확하게 이해할 수 있습니다. MVVM은 실제로 다음 그림과 같이 클라이언트 측을위한 계층 구조입니다.
프리젠 테이션 레이어는 뷰로 구성됩니다.
논리 계층은 뷰 모델입니다.
프리젠 테이션 레이어는 모델 개체의 조합입니다.
이를 생성하고 유지하는 클라이언트 서비스는 2 계층 애플리케이션에서 또는 서비스 호출을 통해 애플리케이션으로 직접 액세스합니다.
클라이언트 서비스는 공식적으로 MVVM 패턴의 일부는 아니지만 추가 분리를 달성하고 중복 코드를 방지하기 위해 MVVM과 함께 자주 사용됩니다.
모델 책임
일반적으로 모델은 가장 이해하기 쉬운 모델입니다. 애플리케이션에서보기를 지원하는 것은 클라이언트 측 데이터 모델입니다.
메모리에 데이터를 포함하기 위해 속성과 일부 변수가있는 개체로 구성됩니다.
이러한 속성 중 일부는 다른 모델 개체를 참조하고 전체적으로 모델 개체 인 개체 그래프를 생성 할 수 있습니다.
모델 개체는 WPF에서 데이터 바인딩을 의미하는 속성 변경 알림을 발생시켜야합니다.
마지막 책임은 선택 사항 인 유효성 검사이지만 INotifyDataErrorInfo / IDataErrorInfo와 같은 인터페이스를 통해 WPF 데이터 바인딩 유효성 검사 기능을 사용하여 모델 개체에 유효성 검사 정보를 포함 할 수 있습니다.
책임보기
뷰의 주요 목적과 책임은 사용자가 화면에서 보는 구조를 정의하는 것입니다. 구조는 정적 및 동적 부품을 포함 할 수 있습니다.
정적 부분은 뷰가 구성되는 컨트롤의 레이아웃과 컨트롤을 정의하는 XAML 계층 구조입니다.
동적 부분은보기의 일부로 정의 된 애니메이션 또는 상태 변경과 같습니다.
MVVM의 주요 목표는 뷰 뒤에 코드가 없어야한다는 것입니다.
보기에 뒤에 코드가 없다는 것은 불가능합니다. 보기에서 최소한 생성자와 구성 요소를 초기화하는 호출이 필요합니다.
아이디어는 이벤트 처리, 작업 및 데이터 조작 논리 코드가 View의 코드 뒤에 있어서는 안된다는 것입니다.
UI 요소에 대한 참조를 갖는 데 필요한 코드 뒤에 있어야하는 다른 종류의 코드는 본질적으로 뷰 코드입니다.
ViewModel 책임
ViewModel은 MVVM 애플리케이션의 주요 포인트입니다. ViewModel의 주요 책임은 뷰에 데이터를 제공하여 뷰가 해당 데이터를 화면에 표시 할 수 있도록하는 것입니다.
또한 사용자가 데이터와 상호 작용하고 데이터를 변경할 수 있습니다.
ViewModel의 또 다른 주요 책임은 뷰에 대한 상호 작용 논리를 캡슐화하는 것이지만 응용 프로그램의 모든 논리가 ViewModel로 이동해야한다는 의미는 아닙니다.
사용자 또는 뷰의 변경 사항에 따라 올바른 일이 발생하도록 호출의 적절한 순서를 처리 할 수 있어야합니다.
ViewModel은 다른보기로 이동할시기를 결정하는 것과 같은 탐색 논리도 관리해야합니다.
이 장에서는 간단한 입력 화면과 이미 익숙한 WPF 응용 프로그램에 MVVM 패턴을 사용하는 방법을 배웁니다.
MVVM 접근 방식을 사용할 간단한 예를 살펴 보겠습니다.
Step 1 − 새로운 WPF 애플리케이션 프로젝트 MVVMDemo를 생성합니다.
Step 2 − 프로젝트에 세 개의 폴더 (Model, ViewModel 및 Views)를 추가합니다.
Step 3 − Model 폴더에 StudentModel 클래스를 추가하고 해당 클래스에 아래 코드를 붙여 넣습니다.
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get {
return firstName;
}
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get {return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Step 4 − ViewModel 폴더에 다른 StudentViewModel 클래스를 추가하고 다음 코드를 붙여 넣습니다.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
Step 5 −보기 폴더를 마우스 오른쪽 버튼으로 클릭하고 추가> 새 항목…을 선택하여 새 사용자 컨트롤 (WPF)을 추가합니다.
Step 6− 추가 버튼을 클릭합니다. 이제 XAML 파일이 표시됩니다. 다른 UI 요소가 포함 된 StudentView.xaml 파일에 다음 코드를 추가합니다.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Step 7 − 이제 다음 코드를 사용하여 StudentView를 MainPage.xaml 파일에 추가합니다.
<Window x:Class = "MVVMDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl" Loaded = "StudentViewControl_Loaded"/>
</Grid>
</Window>
Step 8 − 다음은 MainPage.xaml.cs 파일의 Loaded 이벤트에 대한 구현으로, ViewModel에서 View를 업데이트합니다.
using System.Windows;
namespace MVVMDemo {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void StudentViewControl_Loaded(object sender, RoutedEventArgs e) {
MVVMDemo.ViewModel.StudentViewModel studentViewModelObject =
new MVVMDemo.ViewModel.StudentViewModel();
studentViewModelObject.LoadStudents();
StudentViewControl.DataContext = studentViewModelObject;
}
}
}
Step 9 − 위 코드가 컴파일되고 실행되면 메인 창에 다음과 같은 출력이 나타납니다.
더 나은 이해를 위해 위의 예를 단계별로 실행하는 것이 좋습니다.
이 장에서는 뷰를 ViewModel에 연결할 수있는 다양한 방법을 다룹니다. 먼저 XAML에서 선언 할 수있는 View first 생성을 살펴 보겠습니다. 메인 창에서 뷰를 연결 한 마지막 장의 예제를 보았습니다. 이제 뷰를 연결하는 다른 방법을 살펴 보겠습니다.
이 장에서도 동일한 예제를 사용합니다. 다음은 동일한 모델 클래스 구현입니다.
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get { return firstName; }
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get { return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
다음은 ViewModel 클래스 구현입니다. 이번에는 LoadStudents 메서드가 기본 생성자에서 호출됩니다.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel{
public class StudentViewModel {
public StudentViewModel() {
LoadStudents();
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
보기가 창, 사용자 제어 또는 페이지이든 관계없이 파서는 일반적으로 위에서 아래로, 왼쪽에서 오른쪽으로 작동합니다. 각 요소가 발견 될 때마다 기본 생성자를 호출합니다. 뷰를 구성하는 방법에는 두 가지가 있습니다. 당신은 그들에 무엇이든 사용할 수 있습니다.
- XAML에서 첫 번째 생성보기
- 코드 숨김에서 첫 번째 구성보기
XAML에서 첫 번째 생성보기
한 가지 방법은 다음 코드와 같이 DataContext 속성에 대한 setter에서 ViewModel을 중첩 요소로 추가하는 것입니다.
<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>
다음은 전체 View XAML 파일입니다.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
코드 숨김에서 첫 번째 구성보기
또 다른 방법은 인스턴스와 함께 DataContext 속성을 설정하여 뷰 뒤에있는 코드에서 뷰 모델을 직접 생성하는 것입니다.
일반적으로 DataContext 속성은 뷰의 생성자 메서드에서 설정되지만 뷰의 Load 이벤트가 발생할 때까지 생성을 연기 할 수도 있습니다.
using System.Windows.Controls;
namespace MVVMDemo.Views {
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl {
public StudentView() {
InitializeComponent();
this.DataContext = new MVVMDemo.ViewModel.StudentViewModel();
}
}
}
XAML 대신 코드 숨김에서 뷰 모델을 생성하는 한 가지 이유는 View 모델 생성자가 매개 변수를 사용하지만 XAML 구문 분석은 기본 생성자에 정의 된 경우에만 요소를 생성 할 수 있기 때문입니다.
이제이 경우 View의 XAML 파일은 다음 코드와 같이 표시됩니다.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300"
d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal"<
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
MainWindow.XAML 파일에 표시된대로 MainWindow에서이 뷰를 선언 할 수 있습니다.
<Window x:Class = "MVVMDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl"/>
</Grid>
</Window>
위의 코드가 컴파일되고 실행되면 기본 창에 다음 출력이 표시됩니다.
더 나은 이해를 위해 위의 예를 단계별로 실행하는 것이 좋습니다.
이 장에서는 ViewModel을 연결하는 방법을 다룰 것입니다. View first 구성에 대해 논의한 마지막 장의 연속입니다. 자, 첫 번째 건설의 다음 형태는meta-pattern 로 알려진 ViewModelLocator. 의사 패턴이며 MVVM 패턴 위에 계층화됩니다.
MVVM에서 각 View는 ViewModel에 연결되어야합니다.
ViewModelLocator는 코드를 중앙 집중화하고 뷰를 더 분리하는 간단한 접근 방식입니다.
이는 ViewModel 유형과 구성 방법에 대해 명시 적으로 알 필요가 없음을 의미합니다.
ViewModelLocator를 사용하는 방법에는 여러 가지가 있지만 여기서는 PRISM 프레임 워크의 일부인 것과 가장 유사한 방법을 사용합니다.
ViewModelLocator는 ViewModel을 View에 연결하는 프로세스를 자동화하는 View First 구성을 수행하는 표준적이고 일관 적이며 선언적이며 느슨하게 결합 된 방법을 제공합니다. 다음 그림은 ViewModelLocator의 상위 수준 프로세스를 나타냅니다.
Step 1 − 어떤 뷰 유형이 구성되고 있는지 파악합니다.
Step 2 − 특정 뷰 유형에 대한 ViewModel을 식별합니다.
Step 3 − 해당 ViewModel을 생성합니다.
Step 4 − Views DataContext를 ViewModel로 설정합니다.
기본 개념을 이해하기 위해 지난 장의 동일한 예제를 계속하여 ViewModelLocator의 간단한 예제를 살펴 보겠습니다. StudentView.xaml 파일을 보면 ViewModel이 정적으로 연결되어 있음을 알 수 있습니다.
이제 다음 프로그램에 표시된대로 이러한 XAML 코드를 주석 처리하여 코드 숨김에서 코드를 제거합니다.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
이제 새 폴더 VML을 만들고 다음 코드와 같이 단일 연결된 속성 (종속성 속성) AutoHookedUpViewModel을 포함 할 새 공용 클래스 ViewModelLocator를 추가해 보겠습니다.
public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
return (bool)obj.GetValue(AutoHookedUpViewModelProperty);
}
public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) {
obj.SetValue(AutoHookedUpViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for AutoHookedUpViewModel.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false,
AutoHookedUpViewModelChanged));
이제 기본 연결 속성 정의를 볼 수 있습니다. 속성에 동작을 추가하려면 View에 대한 ViewModel을 연결하는 자동 프로세스를 포함하는이 속성에 대한 변경된 이벤트 처리기를 추가해야합니다. 이를 수행하는 코드는 다음과 같습니다.
private static void AutoHookedUpViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
string str = viewType.FullName;
str = str.Replace(".Views.", ".ViewModel.");
var viewTypeName = str;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
다음은 ViewModelLocator 클래스의 완전한 구현입니다.
using System;
using System.ComponentModel;
using System.Windows;
namespace MVVMDemo.VML {
public static class ViewModelLocator {
public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
return (bool)obj.GetValue(AutoHookedUpViewModelProperty);
}
public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) {
obj.SetValue(AutoHookedUpViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for AutoHookedUpViewModel.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
typeof(bool), typeof(ViewModelLocator), new
PropertyMetadata(false, AutoHookedUpViewModelChanged));
private static void AutoHookedUpViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
string str = viewType.FullName;
str = str.Replace(".Views.", ".ViewModel.");
var viewTypeName = str;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
}
}
가장 먼저 할 일은 프로젝트의 루트에있는 ViewModelLocator 유형에 도달 할 수 있도록 네임 스페이스를 추가하는 것입니다. 그런 다음 뷰 유형 인 경로 요소에서 AutoHookedUpViewModel 속성을 추가하고 true로 설정합니다.
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
다음은 StudentView.xaml 파일의 전체 구현입니다.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
위의 코드가 컴파일되고 실행되면 ViewModelLocator가 해당 특정 뷰에 대한 ViewModel을 연결하는 것을 볼 수 있습니다.
이것에 대해 주목해야 할 중요한 점은 뷰가 더 이상 ViewModel의 유형 또는 구성 방법과 결합되지 않는다는 것입니다. 그게 모두 ViewModelLocator 내부의 중앙 위치로 옮겨졌습니다.
이 장에서는 데이터 바인딩이 MVVM 패턴을 지원하는 방법에 대해 알아 봅니다. 데이터 바인딩은 MVVM을 MVC 및 MVP와 같은 다른 UI 분리 패턴과 차별화하는 핵심 기능입니다.
데이터 바인딩의 경우 뷰 또는 UI 요소 집합이 생성되어야하며 바인딩이 가리킬 다른 개체가 필요합니다.
뷰의 UI 요소는 ViewModel에 의해 노출되는 속성에 바인딩됩니다.
View와 ViewModel이 구성되는 순서는 먼저 View를 다루었으므로 상황에 따라 다릅니다.
View와 ViewModel이 생성되고 View의 DataContext가 ViewModel로 설정됩니다.
바인딩은 OneWay 또는 TwoWay 데이터 바인딩이 될 수 있으며 View와 ViewModel간에 데이터를주고받을 수 있습니다.
동일한 예제에서 데이터 바인딩을 살펴 보겠습니다. 다음은 StudentView의 XAML 코드입니다.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
위의 XAML 코드를 보면 ItemsControl이 ViewModel에 의해 노출 된 Students 컬렉션에 바인딩되어 있음을 알 수 있습니다.
또한 Student 모델의 속성에는 고유 한 개별 바인딩이 있으며 이들은 Textbox 및 TextBlock에 바인딩되어 있습니다.
View에 대한 전체 DataContext가 ViewModel로 설정되어 있기 때문에 ItemsControl의 ItemSource는 Students 속성에 바인딩 할 수 있습니다.
여기에있는 속성의 개별 바인딩도 DataContext 바인딩이지만 ItemSource가 작동하는 방식 때문에 ViewModel 자체에 바인딩되지 않습니다.
항목 소스가 컬렉션에 바인딩되면 렌더링시 각 항목에 대한 컨테이너를 렌더링하고 해당 컨테이너의 DataContext를 항목에 설정합니다. 따라서 행 내의 각 텍스트 상자 및 텍스트 블록에 대한 전체 DataContext는 컬렉션의 개별 Student가 될 것입니다. 또한 TextBox에 대한 이러한 바인딩은 TwoWay 데이터 바인딩이고 TextBlock의 경우 TextBlock을 편집 할 수 없기 때문에 OneWay 데이터 바인딩임을 알 수 있습니다.
이 애플리케이션을 다시 실행하면 다음과 같은 출력이 표시됩니다.
이제 첫 번째 행의 두 번째 텍스트 상자에있는 텍스트를 Allain에서 Upston으로 변경하고 Tab 키를 눌러 초점을 잃도록하겠습니다. TextBlock 텍스트도 업데이트되는 것을 볼 수 있습니다.
이는 TextBox의 바인딩이 TwoWay로 설정되고 모델도 업데이트되고 모델에서 다시 TextBlock이 업데이트되기 때문입니다.
템플릿은 컨트롤의 전체적인 모양과 시각적 모양을 설명합니다. 각 컨트롤에 대해 해당 컨트롤에 모양을 제공하는 연결된 기본 템플릿이 있습니다. WPF 응용 프로그램에서 컨트롤의 시각적 동작과 시각적 모양을 사용자 지정하려는 경우 고유 한 템플릿을 쉽게 만들 수 있습니다. 논리와 템플릿 간의 연결은 데이터 바인딩을 통해 얻을 수 있습니다.
MVVM에는 ViewModel 첫 번째 구성으로 알려진 또 다른 기본 양식이 있습니다.
ViewModel의 첫 번째 생성 접근 방식은 WPF의 암시 적 데이터 템플릿 기능을 활용합니다.
암시 적 데이터 템플릿은 데이터 바인딩을 사용하는 요소에 대해 현재 리소스 사전에서 적절한 템플릿을 자동으로 선택할 수 있습니다. 데이터 바인딩에 의해 렌더링되는 데이터 개체의 유형에 따라이를 수행합니다. 첫째, 데이터 개체에 바인딩되는 요소가 있어야합니다.
먼저 데이터 템플릿, 특히 암시 적 데이터 템플릿을 활용하여 뷰 모델을 수행하는 방법을 이해하는 간단한 예제를 다시 살펴 보겠습니다. 다음은 StudentViewModel 클래스의 구현입니다.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public StudentViewModel() {
LoadStudents();
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
위의 ViewModel이 변경되지 않았 음을 알 수 있습니다. 이전 장과 동일한 예제를 계속 진행합니다. 이 ViewModel 클래스는 Students 컬렉션 속성을 노출하고 생성시 채 웁니다. StudentView.xaml 파일로 이동하여 기존 구현을 제거하고 Resources 섹션에서 데이터 템플릿을 정의하겠습니다.
<UserControl.Resources>
<DataTemplate x:Key = "studentsTemplate">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
이제 다음 코드와 같이 목록 상자를 추가하고 해당 목록 상자를 Students 속성에 데이터 바인딩합니다.
<ListBox ItemsSource = "{Binding Students}" ItemTemplate = "{StaticResource studentsTemplate}"/>
리소스 섹션에서 DataTemplate에는 studentsTemplate의 키가 있으며 실제로 해당 템플릿을 사용하려면 ListBox의 ItemTemplate 속성을 사용해야합니다. 이제 목록 상자에 해당 학생을 렌더링하기 위해 특정 템플릿을 사용하도록 지시하는 것을 볼 수 있습니다. 다음은 StudentView.xaml 파일의 전체 구현입니다.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate x:Key = "studentsTemplate">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox
ItemsSource = "{Binding Students}"
ItemTemplate = "{StaticResource studentsTemplate}"/>
</Grid>
</UserControl>
위의 코드를 컴파일하고 실행하면 ListBox 하나가 포함 된 다음과 같은 창이 나타납니다. 각 ListBoxItem에는 TextBlock 및 텍스트 상자에 표시되는 Student 클래스 개체 데이터가 포함되어 있습니다.
이를 암시 적 템플릿으로 만들려면 목록 상자에서 ItemTemplate 속성을 제거하고 다음 코드와 같이 템플릿 정의에 DataType 속성을 추가해야합니다.
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource = "{Binding Students}"/>
</Grid>
DataTemplate에서 x : Type 태그 확장은 XAML의 연산자 유형과 마찬가지로 매우 중요합니다. 따라서 기본적으로 MVVMDemo.Model 네임 스페이스에있는 Student 데이터 유형을 가리켜 야합니다. 다음은 업데이트 된 완전한 XAML 파일입니다.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource = "{Binding Students}"/>
</Grid>
</UserControl>
이 응용 프로그램을 다시 실행하면 적절한 DataTemplate을 찾아서 렌더링되는 개체의 유형을 자동으로 매핑하므로 데이터 템플릿이있는 Students의 동일한 렌더링을 얻을 수 있습니다.
더 나은 이해를 위해 위의 예제를 단계별 방법으로 실행하는 것이 좋습니다.
이 장에서는 MVVM 응용 프로그램에 상호 작용을 추가하는 방법과 논리를 깔끔하게 호출하는 방법을 배웁니다. 또한 MVVM 패턴의 핵심 인 느슨한 결합과 좋은 구조화를 유지함으로써이 모든 작업이 수행되는 것을 볼 수 있습니다. 이 모든 것을 이해하기 위해 먼저 명령에 대해 알아 보겠습니다.
명령을 통한 View / ViewModel 통신
명령 패턴은 잘 문서화되어 있으며 수십 년 동안 자주 디자인 패턴을 사용합니다. 이 패턴에는 두 가지 주요 행위자 인 호출자와 수신자가 있습니다.
호출자
호출자는 명령 적 논리를 실행할 수있는 코드 조각입니다.
일반적으로 UI 프레임 워크 컨텍스트에서 사용자가 상호 작용하는 UI 요소입니다.
응용 프로그램의 다른 곳에 논리 코드의 또 다른 청크 일 수 있습니다.
리시버
수신자는 호출자가 실행될 때 실행되도록 의도 된 논리입니다.
MVVM의 컨텍스트에서 수신자는 일반적으로 호출해야하는 ViewModel의 메서드입니다.
이 둘 사이에는 방해 레이어가 있으며 이는 호출자와 수신자가 서로에 대해 명시 적으로 알 필요가 없음을 의미합니다. 이것은 일반적으로 호출자에게 노출 된 인터페이스 추상화로 표현되며 해당 인터페이스의 구체적인 구현은 수신자를 호출 할 수 있습니다.
명령을 배우고 명령을 사용하여 View와 ViewModel간에 통신하는 방법을 배우는 간단한 예제를 살펴 보겠습니다. 이 장에서 우리는 지난 장의 동일한 예를 계속할 것입니다.
StudentView.xaml 파일에는 ViewModel에서 학생 데이터를 연결하는 ListBox가 있습니다. 이제 ListBox에서 학생을 삭제하는 버튼을 추가해 보겠습니다.
중요한 것은 ICommand에 연결하는 명령 속성이 있기 때문에 버튼의 명령 작업이 매우 쉽다는 것입니다.
따라서 다음 코드와 같이 ICommand가 있고 버튼의 명령 속성에서 바인딩되는 ViewModel의 속성을 노출 할 수 있습니다.
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
프로젝트에 ICommand 인터페이스를 구현할 새 클래스를 추가해 보겠습니다. 다음은 ICommand 인터페이스의 구현입니다.
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
보시다시피 이것은 ICommand의 간단한 위임 구현입니다. 여기서는 executeMethod에 대한 두 개의 델리게이트와 생성시에 전달 될 수있는 canExecuteMethod에 대한 하나의 델리게이트가 있습니다.
위의 구현에는 두 개의 오버로드 된 생성자가 있습니다. 하나는 executeMethod 전용이고 다른 하나는 executeMethod 및 I canExecuteMethod입니다.
StudentView Model 클래스에 MyICommand 유형의 속성을 추가해 보겠습니다. 이제 StudentViewModel에서 인스턴스를 생성해야합니다. 두 개의 매개 변수를 사용하는 MyICommand의 오버로드 된 생성자를 사용합니다.
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
이제 OnDelete 및 CanDelete 메서드 구현을 추가합니다.
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
또한 사용자가 ListBox에서 선택한 항목을 삭제할 수 있도록 새 SelectedStudent를 추가해야합니다.
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
다음은 ViewModel 클래스의 전체 구현입니다.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
}
}
StudentView.xaml에서 SelectStudent 속성에 바인딩 할 ListBox에 SelectedItem 속성을 추가해야합니다.
<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>
다음은 완전한 xaml 파일입니다.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
</StackPanel>
</Grid>
</UserControl>
위의 코드를 컴파일하고 실행하면 다음과 같은 창이 나타납니다.
삭제 버튼이 비활성화 된 것을 볼 수 있습니다. 항목을 선택하면 활성화됩니다.
항목을 선택하고 삭제를 누르면. 선택한 항목 목록이 삭제되고 삭제 버튼이 다시 비활성화됩니다.
더 나은 이해를 위해 위의 예를 단계별로 실행하는 것이 좋습니다.
MVVM 애플리케이션을 빌드 할 때 일반적으로 복잡한 정보 화면을 상위 및 하위보기 집합으로 분해합니다. 여기서 하위보기는 패널 또는 컨테이너 컨트롤의 상위보기 내에 포함되고 자체 사용 계층을 형성합니다.
복잡한 뷰를 분해 한 후 자체 XAML 파일로 분리하는 모든 자식 콘텐츠가 반드시 MVVM 뷰 여야한다는 의미는 아닙니다.
콘텐츠 청크는 화면에 무언가를 렌더링하기위한 구조를 제공 할 뿐이며 해당 콘텐츠에 대한 사용자의 입력이나 조작을 지원하지 않습니다.
별도의 ViewModel이 필요하지 않을 수 있지만 부모 ViewModel에 의해 노출 된 속성을 기반으로 렌더링되는 청크 XAML 일 수 있습니다.
마지막으로, View와 ViewModel의 계층이있는 경우 부모 ViewModel은 통신의 허브가 될 수 있으므로 각 자식 ViewModel은 다른 자식 ViewModel과 가능한 한 많이 분리 된 상태를 유지할 수 있습니다.
서로 다른 뷰간에 간단한 계층 구조를 정의하는 예를 살펴 보겠습니다. 새 WPF 응용 프로그램 프로젝트 만들기MVVMHierarchiesDemo
Step 1 − 프로젝트에 세 개의 폴더 (Model, ViewModel 및 Views)를 추가합니다.
Step 2 − 다음 그림과 같이 Model 폴더에 Customer와 Order 클래스를 추가하고 Views 폴더에 CustomerListView와 OrderView를 추가하고 ViewModel 폴더에 CustomerListViewModel과 OrderViewModel을 추가합니다.
Step 3− CustomerListView 및 OrderView 모두에 텍스트 블록을 추가합니다. 다음은 CustomerListView.xaml 파일입니다.
<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Customer List View"/>
</Grid>
</UserControl>
다음은 OrderView.xaml 파일입니다.
<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Order View"/>
</Grid>
</UserControl>
이제 우리는 이러한 뷰를 호스팅 할 무언가가 필요합니다. MainWindow는 간단한 애플리케이션이기 때문에이를위한 좋은 장소가 필요합니다. 뷰를 배치하고 탐색 방식으로 전환 할 수있는 컨테이너 컨트롤이 필요합니다. 이를 위해 MainWindow.xaml 파일에 ContentControl을 추가해야하며 해당 콘텐츠 속성을 사용하고이를 ViewModel 참조에 바인딩합니다.
이제 리소스 사전의 각보기에 대한 데이터 템플릿을 정의합니다. 다음은 MainWindow.xaml 파일입니다. 각 데이터 템플릿이 데이터 유형 (ViewModel 유형)을 해당보기에 매핑하는 방법에 유의하십시오.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content = "{Binding CurrentView}"/>
</Grid>
</Window>
현재 뷰 모델이 CustomerListViewModel의 인스턴스로 설정 될 때마다 ViewModel이 연결된 CustomerListView를 렌더링합니다. 주문 ViewModel이며 OrderView 등을 렌더링합니다.
이제 CurrentViewModel 속성과 속성 내에서 ViewModel의 현재 참조를 전환 할 수있는 몇 가지 논리 및 명령이있는 ViewModel이 필요합니다.
MainWindowViewModel이라는이 MainWindow에 대한 ViewModel을 생성 해 보겠습니다. XAML에서 ViewModel의 인스턴스를 만들고이를 사용하여 창의 DataContext 속성을 설정할 수 있습니다. 이를 위해 ViewModel에 대한 INotifyPropertyChanged 구현을 캡슐화하는 기본 클래스를 만들어야합니다.
이 클래스의 기본 개념은 INotifyPropertyChanged 구현을 캡슐화하고 적절한 알림을 쉽게 트리거 할 수 있도록 파생 클래스에 도우미 메서드를 제공하는 것입니다. 다음은 BindableBase 클래스의 구현입니다.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class BindableBase : INotifyPropertyChanged {
protected virtual void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
이제 CurrentViewModel 속성을 사용하여 뷰 전환을 실제로 시작할 때입니다. 이 속성의 설정을 제어 할 방법이 필요합니다. 그리고 최종 사용자가 고객 목록이나 주문보기로 이동하도록 명령 할 수 있도록 만들 것입니다. 먼저 프로젝트에 ICommand 인터페이스를 구현할 새 클래스를 추가하십시오. 다음은 ICommand 인터페이스의 구현입니다.
using System;
using System.Windows.Input;
namespace MVVMHierarchiesDemo {
public class MyICommand<T> : ICommand {
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyICommand(Action<T> executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime is
longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod((T)parameter);
}
}
#endregion
}
}
이제 ViewModel에 대한 최상위 탐색을 설정해야하며 해당 전환에 대한 논리는 MainWindowViewModel 내에 있어야합니다. 이를 위해 문자열 대상을 가져와 CurrentViewModel 속성을 반환하는 탐색시 호출되는 메서드를 사용할 것입니다.
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
이러한 다른 뷰를 탐색하려면 MainWindow.xaml 파일에 두 개의 버튼을 추가해야합니다. 다음은 완전한 XAML 파일 구현입니다.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "*" />
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0" />
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2" />
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}" />
</Grid>
</Grid>
</Window>
다음은 전체 MainWindowViewModel 구현입니다.
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get {return _CurrentViewModel;}
set {SetProperty(ref _CurrentViewModel, value);}
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
BindableBase 클래스에서 모든 ViewModel을 가져옵니다. 위의 코드가 컴파일되고 실행되면 다음과 같은 출력이 표시됩니다.
보시다시피 MainWindow에 두 개의 버튼과 CurrentViewModel 만 추가했습니다. 버튼을 클릭하면 특정보기로 이동합니다. Customers 버튼을 클릭하면 CustomerListView가 표시되는 것을 볼 수 있습니다.
더 나은 이해를 위해 위의 예를 단계별로 실행하는 것이 좋습니다.
이 장에서는 유효성 검사에 대해 알아 봅니다. 또한 WPF 바인딩이 이미 지원하지만 MVVM 구성 요소에 연결하여 유효성 검사를 수행하는 깨끗한 방법을 살펴볼 것입니다.
MVVM의 유효성 검사
애플리케이션이 최종 사용자의 데이터 입력을 받아들이 기 시작하면 해당 입력의 유효성을 검사해야합니다.
전체 요구 사항을 준수하는지 확인하십시오.
WPF에는 입력 유효성 검사를위한 바인딩 시스템에 몇 가지 훌륭한 빌드와 기능이 있으며 MVVM을 수행 할 때 이러한 모든 기능을 계속 활용할 수 있습니다.
유효성 검사를 지원하고 뷰 자체가 아니라 모델 또는 ViewModel의 일부가되어야하는 속성에 대해 존재하는 규칙을 정의하는 논리를 염두에 두십시오.
다음을 포함하여 WPF 데이터 바인딩에서 지원하는 유효성 검사를 표현하는 모든 방법을 계속 사용할 수 있습니다.
- 속성에 예외 발생이 설정되었습니다.
- IDataErrorInfo 인터페이스 구현.
- INotifyDataErrorInfo 구현.
- WPF 유효성 검사 규칙을 사용합니다.
일반적으로 INotifyDataErrorInfo가 권장되며 WPF .net 4.5에 도입되었으며 속성과 관련된 오류에 대한 개체 쿼리를 지원하며 다른 모든 옵션으로 몇 가지 결함을 수정합니다. 특히 비동기 유효성 검사를 허용합니다. 속성에 둘 이상의 오류가 연결될 수 있습니다.
유효성 검사 추가
입력보기에 유효성 검사 지원을 추가하는 예를 살펴 보겠습니다. 대규모 애플리케이션에서는 애플리케이션의 여러 위치에 필요할 것입니다. 때로는 뷰, 때로는 ViewModels, 때로는 이러한 도우미 객체에 모델 객체 주변에 래퍼가 있습니다.
다른 시나리오에서 상속 할 수있는 공통 기본 클래스에 유효성 검사 지원을 배치하는 것이 좋습니다.
기본 클래스는 속성이 변경 될 때 유효성 검사가 트리거되도록 INotifyDataErrorInfo를 지원합니다.
ValidatableBindableBase라는 새 클래스를 추가합니다. 속성 변경 처리를위한 기본 클래스가 이미 있으므로 여기에서 기본 클래스를 파생시키고 INotifyDataErrorInfo 인터페이스도 구현하겠습니다.
다음은 ValidatableBindableBase 클래스의 구현입니다.
using System;
using System.Collections.Generic;
using System.ComponentModel;
//using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace MVVMHierarchiesDemo {
public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo {
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs>
ErrorsChanged = delegate { };
public System.Collections.IEnumerable GetErrors(string propertyName) {
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
else
return null;
}
public bool HasErrors {
get { return _errors.Count > 0; }
}
protected override void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
base.SetProperty<T>(ref member, val, propertyName);
ValidateProperty(propertyName, val);
}
private void ValidateProperty<T>(string propertyName, T value) {
var results = new List<ValidationResult>();
//ValidationContext context = new ValidationContext(this);
//context.MemberName = propertyName;
//Validator.TryValidateProperty(value, context, results);
if (results.Any()) {
//_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
} else {
_errors.Remove(propertyName);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
}
이제 각 폴더에 AddEditCustomerView 및 AddEditCustomerViewModel을 추가하십시오. 다음은 AddEditCustomerView.xaml의 코드입니다.
<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
</Grid.RowDefinitions>
<Grid x:Name = "grid1"
HorizontalAlignment = "Left"
DataContext = "{Binding Customer}"
Margin = "10,10,0,0"
VerticalAlignment = "Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "Auto" />
<ColumnDefinition Width = "Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
</Grid.RowDefinitions>
<Label Content = "First Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "0"
VerticalAlignment = "Center" />
<TextBox x:Name = "firstNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "0"
Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Last Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "1"
VerticalAlignment = "Center" />
<TextBox x:Name = "lastNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "1"
Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Email:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "2"
VerticalAlignment = "Center" />
<TextBox x:Name = "emailTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "2"
Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Phone:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "3"
VerticalAlignment = "Center" />
<TextBox x:Name = "phoneTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "3"
Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
</Grid>
<Grid Grid.Row = "1">
<Button Content = "Save"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
<Button Content = "Add"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
<Button Content = "Cancel"
Command = "{Binding CancelCommand}"
HorizontalAlignment = "Left"
Margin = "150,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
</Grid>
</Grid>
</UserControl>
다음은 AddEditCustomerViewModel 구현입니다.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
public AddEditCustomerViewModel() {
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value);}
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value);}
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
Done();
}
private bool CanSave() {
return !Customer.HasErrors;
}
}
}
다음은 SimpleEditableCustomer 클래스의 구현입니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Model {
public class SimpleEditableCustomer : ValidatableBindableBase {
private Guid _id;
public Guid Id {
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _firstName;
[Required]
public string FirstName {
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
private string _lastName;
[Required]
public string LastName {
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
private string _email;
[EmailAddress]
public string Email {
get { return _email; }
set { SetProperty(ref _email, value); }
}
private string _phone;
[Phone]
public string Phone {
get { return _phone; }
set { SetProperty(ref _phone, value); }
}
}
}
위의 코드를 컴파일하고 실행하면 다음과 같은 창이 나타납니다.
고객 추가 버튼을 누르면 다음과 같은 화면이 표시됩니다. 사용자가 필드를 비워두면 강조 표시되고 저장 버튼이 비활성화됩니다.
이 장에서는 종속성 주입에 대해 간략하게 설명합니다. 데이터 바인딩은 이미 통신의 다른 쪽 끝에서 무슨 일이 벌어지고 있는지 명시 적으로 알지 못해도 통신 할 수 있도록 뷰와 뷰 모델을 서로 분리하는 것을 다루었습니다.
이제 클라이언트 서비스에서 ViewModel을 분리하는 것과 유사한 것이 필요합니다.
객체 지향 프로그래밍의 초기에 개발자는 응용 프로그램에서 클래스 인스턴스를 만들고 검색하는 문제에 직면했습니다. 이 문제에 대한 다양한 해결책이 제안되었습니다.
지난 몇 년 동안 종속성 주입 및 제어 반전 (IoC)은 개발자들 사이에서 인기를 얻었으며 Singleton 패턴과 같은 일부 구형 솔루션보다 우선했습니다.
의존성 주입 / IoC 컨테이너
IoC 및 종속성 주입은 밀접하게 관련된 두 가지 디자인 패턴이며 컨테이너는 기본적으로 이러한 패턴을 모두 수행하는 인프라 코드 청크입니다.
IoC 패턴은 생성에 대한 책임을 위임하는 것이고 의존성 주입 패턴은 이미 생성 된 객체에 대한 의존성을 제공하는 것입니다.
둘 다 구성에 대한 2 단계 접근 방식으로 취급 할 수 있습니다. 컨테이너를 사용할 때 컨테이너는 다음과 같은 몇 가지 책임이 있습니다.
- 요청하면 객체를 생성합니다.
- 컨테이너는 해당 개체가 의존하는 대상을 결정합니다.
- 이러한 종속성을 구성합니다.
- 그것들을 건설중인 물체에 주입합니다.
- 재귀 적으로하는 과정.
종속성 주입을 사용하여 ViewModel과 클라이언트 서비스 간의 분리를 끊는 방법을 살펴 보겠습니다. 이와 관련된 종속성 주입을 사용하여 저장 처리 AddEditCustomerViewModel 양식을 연결합니다.
먼저 프로젝트의 Services 폴더에 새 인터페이스를 만들어야합니다. 프로젝트에 서비스 폴더가없는 경우 먼저 생성하고 Services 폴더에 다음 인터페이스를 추가합니다.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Services {
public interface ICustomersRepository {
Task<List<Customer>> GetCustomersAsync();
Task<Customer> GetCustomerAsync(Guid id);
Task<Customer> AddCustomerAsync(Customer customer);
Task<Customer> UpdateCustomerAsync(Customer customer);
Task DeleteCustomerAsync(Guid customerId);
}
}
다음은 ICustomersRepository의 구현입니다.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq; using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Services {
public class CustomersRepository : ICustomersRepository {
ZzaDbContext _context = new ZzaDbContext();
public Task<List<Customer>> GetCustomersAsync() {
return _context.Customers.ToListAsync();
}
public Task<Customer> GetCustomerAsync(Guid id) {
return _context.Customers.FirstOrDefaultAsync(c => c.Id == id);
}
public async Task<Customer> AddCustomerAsync(Customer customer){
_context.Customers.Add(customer);
await _context.SaveChangesAsync();
return customer;
}
public async Task<Customer> UpdateCustomerAsync(Customer customer) {
if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) {
_context.Customers.Attach(customer);
}
_context.Entry(customer).State = EntityState.Modified;
await _context.SaveChangesAsync();
return customer;
}
public async Task DeleteCustomerAsync(Guid customerId) {
var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId);
if (customer != null) {
_context.Customers.Remove(customer);
}
await _context.SaveChangesAsync();
}
}
}
저장 처리를 수행하는 간단한 방법은 AddEditCustomerViewModel에 ICustomersRepository의 새 인스턴스를 추가하고 AddEditCustomerViewModel 및 CustomerListViewModel 생성자를 오버로드하는 것입니다.
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
다음 코드와 같이 OnSave 메서드를 업데이트합니다.
private async void OnSave() {
UpdateCustomer(Customer, _editingCustomer);
if (EditMode)
await _repo.UpdateCustomerAsync(_editingCustomer);
else
await _repo.AddCustomerAsync(_editingCustomer);
Done();
}
private void UpdateCustomer(SimpleEditableCustomer source, Customer target) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
다음은 완전한 AddEditCustomerViewModel입니다.
using MVVMHierarchiesDemo.Model;
using MVVMHierarchiesDemo.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value); }
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value); }
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
UpdateCustomer(Customer, _editingCustomer);
if (EditMode)
await _repo.UpdateCustomerAsync(_editingCustomer);
else
await _repo.AddCustomerAsync(_editingCustomer);
Done();
}
private void UpdateCustomer(SimpleEditableCustomer source, Customer target) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
private bool CanSave() {
return !Customer.HasErrors;
}
private void CopyCustomer(Customer source, SimpleEditableCustomer target) {
target.Id = source.Id;
if (EditMode) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
}
}
}
위의 코드가 컴파일되고 실행되면 동일한 출력이 표시되지만 이제 ViewModel이 더 느슨하게 분리됩니다.
고객 추가 버튼을 누르면 다음과 같은 화면이 나타납니다. 사용자가 필드를 비워두면 강조 표시되고 저장 버튼이 비활성화됩니다.
이벤트는 상태 변경에 반응하여 알림을 위해 등록 된 모든 엔드 포인트에 알리는 프로그래밍 구조입니다. 주로 이벤트는 마우스와 키보드를 통해 사용자 입력을 알리는 데 사용되지만 그 유용성은 이에 국한되지 않습니다. 상태 변경이 감지 될 때마다, 아마도 객체가로드되거나 초기화 될 때 이벤트가 발생하여 관심있는 제 3 자에게 경고 할 수 있습니다.
MVVM (Model-View-ViewModel) 디자인 패턴을 사용하는 WPF 응용 프로그램에서보기 모델은 응용 프로그램의 프레젠테이션 논리 및 상태를 처리하는 구성 요소입니다.
뷰의 코드 숨김 파일에는 Button 또는 ComboBox와 같은 UI (사용자 인터페이스) 요소에서 발생하는 이벤트를 처리하는 코드가 포함되어서는 안되며 도메인 특정 논리를 포함해서는 안됩니다.
이상적으로, View의 코드 숨김에는 InitializeComponent 메서드를 호출하는 생성자 및 XAML에서 표현하기 어렵거나 비효율적 인보기 계층 (예 : 복잡한 애니메이션)을 제어하거나 상호 작용하는 일부 추가 코드 만 포함됩니다.
애플리케이션에서 버튼 클릭 이벤트의 간단한 예를 살펴 보겠습니다. 다음은 두 개의 버튼이있는 MainWindow.xaml 파일의 XAML 코드입니다.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "*" />
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0" />
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2" />
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}" />
</Grid>
</Grid>
</Window>
위의 XAML 파일에서는 button Click 속성이 사용되지 않지만 Command 및 CommandParameter 속성은 단추를 눌렀을 때 다른 뷰를로드하는 데 사용된다는 것을 알 수 있습니다. 이제 View 파일이 아닌 MainWindowViewModel.cs 파일에 명령 구현을 정의해야합니다. 다음은 전체 MainWindowViewModel 구현입니다.
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get { return _CurrentViewModel; }
set { SetProperty(ref _CurrentViewModel, value); }
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
BindableBase 클래스에서 모든 ViewModel을 가져옵니다. 위의 코드가 컴파일되고 실행되면 다음과 같은 출력이 표시됩니다.
보시다시피 MainWindow에 두 개의 버튼과 CurrentViewModel 만 추가했습니다. 이제 any 버튼을 클릭하면 해당 뷰로 이동합니다. Customers 버튼을 클릭하면 CustomerListView가 표시되는 것을 볼 수 있습니다.
더 나은 이해를 위해 위의 예제를 단계별 방법으로 실행하는 것이 좋습니다.
단위 테스트의 기본 개념은 개별 코드 청크 (단위)를 가져 와서 예상대로 코드를 사용하는 테스트 메서드를 작성한 다음 예상되는 결과를 얻었는지 테스트하는 것입니다.
코드 자체이므로 단위 테스트는 프로젝트의 나머지 부분과 마찬가지로 컴파일됩니다.
또한 테스트 실행 소프트웨어에 의해 실행되며, 각 테스트를 빠르게 진행할 수 있으며, 테스트가 통과했는지 또는 실패했는지를 표시하기 위해 엄지 손가락 또는 엄지 손가락을 효과적으로 제공합니다.
앞서 만든 예제를 살펴 보겠습니다. 다음은 학생 모델의 구현입니다.
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get { return firstName; }
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get { return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
다음은 StudentView의 구현입니다.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
</StackPanel>
</Grid>
</UserControl>
다음은 StudentViewModel 구현입니다.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
public int GetStudentCount() {
return Students.Count;
}
}
}
다음은 MainWindow.xaml 파일입니다.
<Window x:Class = "MVVMDemo.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl"/>
</Grid>
</Window>
다음은 ICommand 인터페이스를 구현하는 MyICommand 구현입니다.
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
위의 코드가 컴파일되고 실행되면 기본 창에 다음 출력이 표시됩니다.
위 예제에 대한 단위 테스트를 작성하기 위해 솔루션에 새 테스트 프로젝트를 추가해 보겠습니다.
참조를 마우스 오른쪽 버튼으로 클릭하여 프로젝트에 참조를 추가하십시오.
기존 프로젝트를 선택하고 확인을 클릭하십시오.
이제 다음 코드와 같이 학생 수를 확인하는 간단한 테스트를 추가하겠습니다.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMDemo.ViewModel;
namespace MVVMTest {
[TestClass]
public class UnitTest1 {
[TestMethod]
public void TestMethod1() {
StudentViewModel sViewModel = new StudentViewModel();
int count = sViewModel.GetStudentCount();
Assert.IsTrue(count == 3);
}
}
}
이 테스트를 실행하려면 테스트 → 실행 → 모든 테스트 메뉴 옵션을 선택합니다.
StudentViewModel에 세 명의 학생이 추가 되었기 때문에 테스트 탐색기에서 테스트가 통과되었음을 확인할 수 있습니다. 다음 코드와 같이 카운트 조건을 3에서 4로 변경합니다.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMDemo.ViewModel;
namespace MVVMTest {
[TestClass]
public class UnitTest1 {
[TestMethod] public void TestMethod1() {
StudentViewModel sViewModel = new StudentViewModel();
int count = sViewModel.GetStudentCount();
Assert.IsTrue(count == 4);
}
}
}
시험 계획을 다시 실행하면 학생 수가 4가 아니기 때문에 시험이 실패했음을 알 수 있습니다.
더 나은 이해를 위해 위의 예제를 단계별 방법으로 실행하는 것이 좋습니다.
이 장에서는 사용 가능한 MVVM 툴킷 또는 프레임 워크에 대해 설명합니다. 또한 이러한 프레임 워크를 사용하여 MVVM 패턴을 직접 구현하기 위해 반복적 인 코드를 작성할 필요가 없습니다. 다음은 가장 인기있는 프레임 워크입니다.
- Prism
- MVVM 라이트
- Caliburn Micro
프리즘
Prism은 풍부하고 유연하며 쉽게 유지 관리되는 WPF (Windows Presentation Foundation) 데스크톱 응용 프로그램을 쉽게 설계하고 빌드하는 데 도움이되는 샘플 및 설명서 형식의 지침을 제공합니다. Microsoft Silverlight 브라우저 플러그인 및 Windows 응용 프로그램으로 구축 된 RIA (Rich Internet Applications).
Prism은 관심사 분리 및 느슨한 결합과 같은 중요한 아키텍처 디자인 원칙을 구현하는 디자인 패턴을 사용합니다.
Prism은 독립적으로 발전 할 수 있지만 전체 애플리케이션에 쉽고 원활하게 통합 될 수있는 느슨하게 결합 된 구성 요소를 사용하여 애플리케이션을 설계하고 구축하는 데 도움이됩니다.
이러한 유형의 애플리케이션을 복합 애플리케이션이라고합니다.
Prism에는 즉시 사용할 수있는 다양한 기능이 있습니다. 다음은 Prism의 몇 가지 중요한 기능입니다.
MVVM 패턴
Prism은 MVVM 패턴을 지원합니다. 이전 장에서 구현 한 것과 유사한 Bindablebase 클래스가 있습니다.
규칙이있는 유연한 ViewModelLocator가 있지만 이러한 규칙을 재정의하고 느슨하게 결합 된 방식으로 뷰와 ViewModel을 선언적으로 연결할 수 있습니다.
모듈성
코드가 완전히 분리 된 상태로 유지되는 동안 코드를 부분적으로 완전히 느슨하게 결합 된 클래스 라이브러리로 분할하고 런타임에 최종 사용자를위한 응집 된 전체로 통합하는 기능입니다.
UI 구성 / 지역
UI 컨테이너 자체에 대한 명시 적 참조가 있어야하는 연결을 수행하는보기없이보기를 컨테이너에 연결하는 기능입니다.
항해
Prism에는 전방 및 후방 탐색과 같이 영역 위에 계층화되는 탐색 기능과보기 모델이 탐색 프로세스에 직접 참여할 수있는 탐색 스택이 있습니다.
명령
Prism에는 명령이 있으므로 이전 장에서 사용한 MyICommand와 매우 유사한 델리게이트 명령이 있습니다. 단, 메모리 누수로부터 사용자를 보호하기위한 추가 견고 함이 있습니다.
Pub / Sub 이벤트
Prism은 또한 Pub / Sub 이벤트를 지원합니다. 이는 게시자와 구독자가 서로 다른 수명을 가질 수 있고 이벤트를 통해 통신하기 위해 서로에 대한 명시 적 참조를 가질 필요가없는 느슨하게 결합 된 이벤트입니다.
MVVM 라이트
MVVM Light는 Laurent Bugnion에서 생산하며보기를 모델에서 분리하는 데 도움이되며, 이는 더 깨끗하고 유지 관리 및 확장이 쉬운 애플리케이션을 생성합니다.
또한 테스트 가능한 애플리케이션을 생성하고 훨씬 더 얇은 사용자 인터페이스 레이어를 가질 수 있습니다 (자동으로 테스트하기가 더 어렵습니다).
이 툴킷은 Blend 사용자가 데이터 컨트롤 작업을 할 때 "뭔가를 볼"수 있도록하는 디자인 타임 데이터 생성을 포함하여 Blend에서 사용자 인터페이스를 열고 편집하는 데 특히 중점을 둡니다.
Caliburn Micro
이것은 MVVM 패턴을 구현하는 데 도움이되는 또 다른 작은 오픈 소스 프레임 워크이며 여러 가지를 즉시 지원합니다.
Caliburn Micro는 모든 XAML 플랫폼에서 응용 프로그램을 빌드하도록 설계된 작지만 강력한 프레임 워크입니다.
MVVM 및 기타 입증 된 UI 패턴에 대한 강력한 지원을 통해 Caliburn Micro를 사용하면 코드 품질이나 테스트 가능성을 희생 할 필요없이 솔루션을 빠르게 구축 할 수 있습니다.