학습
모듈
.NET MAUI용 MVVM viewmodel 디자인 - Training
MVVM 디자인 패턴이 비즈니스 논리와 사용자 인터페이스 코드를 분리하는 데 어떻게 도움이 되는지 알아보세요. viewmodel을 디자인하는 방법과 이것이 중요한 이유에 대해 알아봅니다.
팁
이 콘텐츠는 ‘.NET MAUI를 사용하는 엔터프라이즈 애플리케이션 패턴’ eBook에서 발췌한 것으로, .NET Docs에서 제공되거나 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공됩니다.
.NET MAUI 개발자 환경에는 일반적으로 XAML에서 사용자 인터페이스를 만든 다음 사용자 인터페이스에서 작동하는 코드 숨김을 추가하는 작업이 포함됩니다. 앱이 수정되고 크기 및 범위가 증가함에 따라 복잡한 유지 관리 문제가 발생할 수 있습니다. 이러한 문제에는 UI 컨트롤과 비즈니스 논리 간의 긴밀한 결합이 포함되며, 이로 인해 UI 수정 비용이 증가하고 이러한 코드를 단위 테스트하기 어려워집니다.
MVVM 패턴은 애플리케이션의 비즈니스 및 프레젠테이션 논리를 UI(사용자 인터페이스)와 명확하게 구분하는 데 도움이 됩니다. 애플리케이션 논리와 UI 간의 명확한 분리를 유지 관리하면 수많은 개발 문제를 해결하고 애플리케이션을 더 쉽게 테스트, 유지 관리 및 개량할 수 있습니다. 또한 코드 재사용 기회를 크게 개선할 수 있으며 개발자와 UI 디자이너가 앱의 각 부분을 개발할 때 더 쉽게 공동 작업할 수 있습니다.
MVVM 패턴에는 모델, 뷰 및 뷰 모델의 세 가지 핵심 구성 요소가 있습니다. 각 구성 요소는 다른 용도로 사용됩니다. 아래 다이어그램은 세 구성 요소 간의 관계를 보여 줍니다.
각 구성 요소의 책임을 이해하는 것 외에 상호 작용하는 방법을 이해하는 것도 중요합니다. 개략적으로 뷰는 뷰 모델을 "인식"하고, 뷰 모델은 모델을 "인식"하지만 모델은 뷰 모델을 인식하지 못하고 뷰 모델은 뷰를 인식하지 못합니다. 따라서 뷰 모델은 뷰를 모델에서 격리하고 모델이 뷰와 독립적으로 발전할 수 있도록 합니다.
MVVM 패턴을 사용하면 다음과 같은 이점이 있습니다.
MVVM을 효과적으로 사용하는 관건은 앱 코드를 올바른 클래스로 팩터링하는 방법과 클래스가 상호 작용하는 방식을 이해하는 것입니다. 다음 섹션에서는 MVVM 패턴에서 각 클래스의 책임을 설명합니다.
뷰는 사용자가 화면에 표시되는 구조, 레이아웃 및 모양을 정의합니다. 이상적으로 각 뷰는 비즈니스 논리를 포함하지 않는 제한된 코드 숨김을 사용하여 XAML에서 정의됩니다. 그러나 경우에 따라 코드 숨김에는 애니메이션과 같이 XAML에서 표현하기 어려운 시각적 동작을 구현하는 UI 논리가 포함될 수 있습니다.
.NET MAUI 애플리케이션에서 뷰는 일반적으로 ContentPage
파생 또는 ContentView
파생 클래스입니다. 그러나 뷰는 표시되는 개체를 시각적으로 표현하는 데 사용할 UI 요소를 지정하는 데이터 템플릿으로도 나타낼 수 있습니다. 뷰로서의 데이터 템플릿에는 코드 숨김이 없으며 특정 뷰 모델 형식에 바인딩되도록 설계되었습니다.
팁
코드 숨김에서 UI 요소를 사용하도록 설정하거나 사용하지 않도록 설정하지 마세요.
뷰 모델이 명령을 사용할 수 있는지 여부 또는 작업이 보류 중임을 나타내는 표시 등 뷰 표시의 일부 측면에 영향을 주는 논리적 상태 변경을 정의하도록 해야 합니다. 따라서 코드 숨김에서 UI 요소를 사용하도록 설정하거나 사용하지 않도록 설정하는 대신 뷰 모델 속성에 바인딩하여 UI 요소를 사용하거나 사용하지 않도록 설정합니다.
뷰 모델에서 뷰 상호 작용에 대한 응답으로 코드를 실행하는 몇 가지 옵션이 있습니다(예: 단추 클릭 또는 항목 선택). 컨트롤이 명령을 지원하는 경우 컨트롤의 Command 속성은 뷰 모델의 ICommand 속성에 데이터 바인딩될 수 있습니다. 컨트롤의 명령이 호출되면 뷰 모델의 코드가 실행됩니다. 명령 외에도 동작을 뷰의 개체에 연결할 수 있으며 호출할 명령 또는 발생할 이벤트를 수신 대기할 수 있습니다. 이에 대한 응답으로 동작은 뷰 모델에서 ICommand 또는 뷰 모델의 메서드를 호출할 수 있습니다.
뷰 모델은 뷰가 데이터 바인딩될 수 있는 속성 및 명령을 구현하고 변경 알림 이벤트를 통해 뷰에 상태 변경을 알릴 수 있습니다. 뷰 모델에서 제공하는 속성 및 명령은 UI에서 제공할 기능을 정의하지만 뷰는 해당 기능을 표시하는 방법을 결정합니다.
팁
비동기 작업을 사용하여 UI의 응답성을 유지합니다.
다중 플랫폼 앱은 사용자의 성능 인식을 개선하기 위해 UI 스레드를 차단 해제 상태로 유지해야 합니다. 따라서 뷰 모델에서 I/O 작업에 비동기 메서드를 사용하고 이벤트를 발생시켜 뷰에 속성 변경 내용을 비동기적으로 알립니다.
또한 뷰 모델은 필요한 모든 모델 클래스와 뷰의 상호 작용을 조정해야 합니다. 일반적으로 뷰 모델과 모델 클래스 간에 일대다 관계가 있습니다. 뷰 모델은 뷰의 컨트롤이 직접 데이터에 바인딩될 수 있도록 모델 클래스를 뷰에 직접 노출하도록 선택할 수 있습니다. 이 경우 모델 클래스는 데이터 바인딩 및 변경 알림 이벤트를 지원하도록 설계되어야 합니다.
각 뷰 모델은 뷰에서 쉽게 사용할 수 있는 형식으로 모델의 데이터를 제공합니다. 이를 위해 뷰 모델은 때때로 데이터 변환을 수행합니다. 뷰 모델에 이 데이터 변환을 배치하는 것은 뷰가 바인딩될 수 있는 속성을 제공하기 때문에 좋은 생각입니다. 예를 들어 뷰 모델은 뷰에서 더 쉽게 표시할 수 있도록 두 속성의 값을 결합할 수 있습니다.
팁
변환 계층에서 데이터 변환을 중앙 집중화합니다.
변환기를 뷰 모델과 뷰 사이에 있는 별도의 데이터 변환 계층으로 사용할 수도 있습니다. 예를 들어 데이터에 뷰 모델이 제공하지 않는 특별한 서식이 필요한 경우 이 작업이 필요할 수 있습니다.
뷰 모델이 뷰를 사용하여 양방향 데이터 바인딩에 참여하려면 해당 속성이 PropertyChanged
이벤트를 발생시켜야 합니다. 뷰 모델은 INotifyPropertyChanged
인터페이스를 구현하고 속성이 변경되면 PropertyChanged
이벤트를 발생시켜 이 요구 사항을 충족합니다.
컬렉션의 경우 뷰 친화적인 ObservableCollection<T>
이 제공됩니다. 이 컬렉션은 컬렉션 변경 알림을 구현하여 개발자가 컬렉션에서 INotifyCollectionChanged
인터페이스를 구현할 필요가 없습니다.
모델 클래스는 앱의 데이터를 캡슐화하는 비 시각적 클래스입니다. 따라서 모델은 일반적으로 비즈니스 및 유효성 검사 논리와 함께 데이터 모델을 포함하는 앱의 도메인 모델을 나타내는 것으로 생각할 수 있습니다. 모델 개체의 예로는 DTO(데이터 전송 개체), POCO(Plain Old CLR Objects), 생성된 엔터티 및 프록시 개체가 있습니다.
모델 클래스는 일반적으로 데이터 액세스 및 캐싱을 캡슐화하는 서비스 또는 리포지토리와 함께 사용됩니다.
뷰 모델은 .NET MAUI의 데이터 바인딩 기능을 사용하여 뷰에 연결할 수 있습니다. 런타임에 뷰 및 뷰 모델을 생성하고 서로 연결하는 데 사용할 수 있는 여러 가지 방법이 있습니다. 이러한 방법은 뷰 우선 구성과 뷰 모델 우선 구성이라고 하는 두 가지 범주로 분류됩니다. 뷰 우선 구성과 뷰 모델 우선 구성 중에서 선택하는 것은 기본 설정 및 복잡성의 문제입니다. 그러나 모든 방법은 뷰가 BindingContext 속성에 뷰 모델을 할당한다는 동일한 목표가 있습니다.
뷰 우선 구성을 사용하면 앱은 개념적으로 사용하는 뷰 모델에 연결하는 뷰로 구성됩니다. 이 방법의 주요 이점은 뷰 모델이 뷰 자체에 의존하지 않으므로 느슨하게 결합된 단위 테스트 가능한 앱을 쉽게 생성할 수 있다는 것입니다. 또한 클래스가 만들어지고 연결되는 방법을 이해하기 위해 코드 실행을 추적할 필요 없이 시각적 구조를 따라 앱의 구조를 쉽게 이해할 수 있습니다. 또한 뷰 우선 구성은 탐색이 발생할 때 페이지 생성을 담당하는 Microsoft Maui의 탐색 시스템과 정렬되므로 뷰 모델 우선 구성은 복잡하고 플랫폼과 잘못 정렬됩니다.
뷰 모델 우선 구성을 사용하면 앱은 개념적으로 뷰 모델에 대한 뷰를 찾는 서비스를 사용하는 뷰 모델로 구성됩니다. 뷰 모델 우선 구성은 뷰 만들기를 추상화할 수 있어 앱의 논리적 비 UI 구조에 집중할 수 있으므로 일부 개발자에게 더 자연스럽게 느껴집니다. 또한 다른 뷰 모델이 뷰 모델을 만들도록 허용합니다. 그러나 이 방법은 종종 복잡하며 앱의 다양한 부분이 만들어지고 연결되는 방식을 이해하기 어려울 수 있습니다.
팁
뷰 모델과 뷰를 독립적으로 유지합니다.
뷰의 데이터 원본 속성에 대한 바인딩은 해당 뷰 모델에 대한 뷰의 보안 주체 종속성이어야 합니다. 특히 뷰 모델에서 단추 및 ListView와 같은 뷰 형식을 참조하지 마세요. 여기에 설명된 원칙에 따라 뷰 모델을 격리하여 테스트할 수 있으므로 범위를 제한하여 소프트웨어 결함의 가능성을 줄일 수 있습니다.
다음 섹션에서는 뷰 모델을 뷰에 연결하는 주요 방법을 설명합니다.
가장 간단한 방법은 뷰가 XAML에서 해당 뷰 모델을 선언적으로 인스턴스화하는 것입니다. 뷰가 생성되면 해당 뷰 모델 개체도 생성됩니다. 다음 코드 예제는 이 방법을 보여 줍니다.
<ContentPage xmlns:local="clr-namespace:eShop">
<ContentPage.BindingContext>
<local:LoginViewModel />
</ContentPage.BindingContext>
<!-- Omitted for brevity... -->
</ContentPage>
ContentPage
가 만들어지면 LoginViewModel
인스턴스가 자동으로 생성되고 뷰의 BindingContext
로 설정됩니다.
뷰에 의한 이 선언적 뷰 모델 생성 및 할당은 간단하다는 장점이 있지만 뷰 모델에서 기본(매개 변수가 없는) 생성자가 필요하다는 단점이 있습니다.
뷰에서는 코드 숨김 파일에 코드가 있을 수 있으므로 뷰 모델이 해당 BindingContext
속성에 할당됩니다. 다음 코드 예제와 같이 이 작업은 뷰의 생성자에서 수행되는 경우가 많습니다.
public LoginView()
{
InitializeComponent();
BindingContext = new LoginViewModel(navigationService);
}
뷰의 코드 숨김 내에서 프로그래밍 방식 뷰 모델 생성 및 할당은 간단하다는 장점이 있습니다. 그러나 이 방법의 주요 단점은 뷰가 뷰 모델에 필요한 종속성을 제공해야 한다는 것입니다. 종속성 주입 컨테이너를 사용하면 뷰와 뷰 모델 간의 느슨한 결합을 유지하는 데 도움이 될 수 있습니다. 자세한 내용은 종속성 주입을 참조하세요.
뷰에 액세스할 수 있는 모든 뷰 모델 및 모델 클래스는 INotifyPropertyChanged 인터페이스를 구현해야 합니다. 뷰 모델 또는 모델 클래스에서 이 인터페이스를 구현하면 기본 속성 값이 변경될 때 클래스가 뷰의 데이터 바인딩된 컨트롤에 변경 알림을 제공할 수 있습니다.
앱은 다음 요구 사항을 충족하여 속성 변경 알림을 올바로 사용하도록 설계되어야 합니다.
PropertyChanged
이벤트를 발생시킵니다. XAML 바인딩이 발생하는 방식에 대한 지식 때문에 PropertyChanged
이벤트 발생을 무시할 수 있다고 가정하지 마세요.PropertyChanged
이벤트를 발생시킵니다.PropertyChanged
이벤트를 발생시킵니다. 이벤트가 발생하면 이벤트의 처리기를 동기적으로 호출하여 작업이 중단됩니다. 작업 중간에 발생하는 경우 부분적으로 업데이트된 안전하지 않은 상태일 때 개체가 콜백 함수에 노출될 수 있습니다. 또한 PropertyChanged
이벤트에 의해 계단식 변경이 트리거될 수 있습니다. 계단식 변경은 일반적으로 업데이트가 완료되어야 안전하게 실행될 수 있습니다.PropertyChanged
이벤트를 발생시키지 않습니다. 즉, PropertyChanged
이벤트를 발생시키기 전에 이전 값과 새 값을 비교해야 합니다.PropertyChanged
이벤트를 발생시키지 않습니다. 뷰의 데이터 바인딩된 컨트롤은 이 시점에서 변경 알림을 수신하도록 구독하지 않습니다.PropertyChanged
이벤트를 두 개 이상 발생시키지 않습니다. 예를 들어 NumberOfItems
속성의 백업 저장소가 _numberOfItems
필드인 경우 메서드가 루프 실행 동안 _numberOfItems
를 50번 증가시키면 모든 작업이 완료된 후 NumberOfItems
속성에 대한 속성 변경 알림을 한 번만 발생시켜야 합니다. 비동기 메서드의 경우 비동기 연속 체인의 각 동기 세그먼트에서 지정된 속성 이름에 대해 PropertyChanged
이벤트를 발생시킵니다.이 기능을 제공하는 간단한 방법은 BindableObject
클래스의 확장을 만드는 것입니다. 이 예제에서 ExtendedBindableObject
클래스는 다음 코드 예제에 표시된 변경 알림을 제공합니다.
public abstract class ExtendedBindableObject : BindableObject
{
public void RaisePropertyChanged<T>(Expression<Func<T>> property)
{
var name = GetMemberInfo(property).Name;
OnPropertyChanged(name);
}
private MemberInfo GetMemberInfo(Expression expression)
{
// Omitted for brevity ...
}
}
.NET MAUI의 BindableObject
클래스는 INotifyPropertyChanged
인터페이스를 구현하고 OnPropertyChanged
메서드를 제공합니다. ExtendedBindableObject
클래스는 속성 변경 알림을 호출하는 RaisePropertyChanged
메서드를 제공하며, 이 경우 BindableObject
클래스에서 제공하는 기능을 사용합니다.
그러면 뷰 모델 클래스가 ExtendedBindableObject
클래스에서 파생될 수 있습니다. 따라서 각 뷰 모델 클래스는 ExtendedBindableObject
클래스의 RaisePropertyChanged
메서드를 사용하여 속성 변경 알림을 제공합니다. 다음 코드 예에서는 eShop 다중 플랫폼 앱이 람다 식을 사용하여 속성 변경 알림을 호출하는 방법을 보여 줍니다.
public bool IsLogin
{
get => _isLogin;
set
{
_isLogin = value;
RaisePropertyChanged(() => IsLogin);
}
}
이러한 방식으로 람다 식을 사용하면 각 호출에 대해 람다 식을 평가해야 하므로 성능 비용이 적습니다. 성능 비용이 적고 일반적으로 앱에 영향을 주지는 않지만 변경 알림이 많을 때는 비용이 발생할 수 있습니다. 그러나 이 방법의 이점은 속성 이름을 바꾸는 경우 컴파일 시간 형식 안전성 및 리팩터링 지원을 제공한다는 것입니다.
MVVM 패턴은 .NET에서 잘 확립되었으며 커뮤니티는 이러한 개발을 용이하게 하는 많은 프레임워크를 만들었습니다. 각 프레임워크마다 제공하는 기능 집합이 다르지만 INotifyPropertyChanged
인터페이스 구현을 통해 공통 뷰 모델을 제공하는 것이 표준입니다. MVVM 프레임워크의 추가 기능에는 사용자 지정 명령, 탐색 도우미, 종속성 주입/서비스 로케이터 구성 요소, UI 플랫폼 통합이 포함됩니다. 이러한 프레임워크를 반드시 사용할 필요는 없지만 개발 속도를 향상시키고 표준화할 수 있습니다. eShop 다중 플랫폼 앱은 .NET 커뮤니티 MVVM 도구 키트를 사용합니다. 프레임워크를 선택할 때 애플리케이션의 요구 사항과 팀의 강점을 고려해야 합니다. 아래 목록에는 .NET MAUI에 대한 보다 일반적인 MVVM 프레임워크가 포함되어 있습니다.
다중 플랫폼 앱에서 작업은 일반적으로 코드 숨김 파일에서 이벤트 처리기를 만들어 구현할 수 있는, 단추 클릭과 같은 사용자 작업에 대한 응답으로 호출됩니다. 그러나 MVVM 패턴에서 작업을 구현하는 책임은 뷰 모델에 있으며 코드를 코드 숨김에 배치하는 것은 피해야 합니다.
명령은 UI의 컨트롤에 바인딩될 수 있는 작업을 나타내는 편리한 방법을 제공합니다. 작업을 구현하는 코드를 캡슐화하고 뷰에서의 시각적 표현과 분리된 상태를 유지하는 데 도움이 됩니다. 이처럼 뷰 모델은 플랫폼의 UI 프레임워크에서 제공하는 이벤트에 대한 직접적인 종속성이 없으므로 새 플랫폼으로의 이식성이 높아집니다. .NET MAUI에는 명령에 선언적으로 연결할 수 있는 컨트롤이 포함되어 있으며, 이러한 컨트롤은 사용자가 컨트롤과 상호 작용할 때 명령을 호출합니다.
또한 동작을 사용해도 컨트롤을 명령에 선언적으로 연결할 수 있습니다. 그러나 동작은 컨트롤에 의해 발생되는 이벤트 범위와 연결된 작업을 호출하는 데 사용할 수 있습니다. 따라서 동작은 명령 사용 컨트롤과 동일한 많은 시나리오를 처리하면서 더 높은 수준의 유연성과 제어를 제공합니다. 또한 동작은 명령 개체 또는 메서드를 명령과 상호 작용하도록 특별히 설계되지 않은 컨트롤과 연결하는 데에 사용할 수도 있습니다.
뷰 모델은 일반적으로 ICommand
인터페이스를 구현하는 뷰에서 바인딩하기 위해 공용 속성을 노출합니다. 많은 .NET MAUI 컨트롤 및 제스처는 뷰 모델에서 제공하는 ICommand
개체에 바인딩된 데이터일 수 있는 Command
속성을 제공합니다. 단추 컨트롤은 가장 일반적으로 사용되는 컨트롤 중 하나로, 단추를 클릭하면 실행되는 명령 속성을 제공합니다.
참고
뷰 모델이 사용하는 ICommand
인터페이스(예: Command<T>
또는 RelayCommand
)의 실제 구현을 노출하는 것이 가능하지만 명령을 ICommand
로 공개적으로 노출하는 것이 좋습니다. 이렇게 하면 나중에 구현을 변경해야 하는 경우 쉽게 교체할 수 있습니다.
ICommand
인터페이스는 작업 자체를 캡슐화하는 Execute
메서드, 명령을 호출할 수 있는지 여부를 나타내는 CanExecute
메서드, 명령 실행 여부에 영향을 주는 변경이 발생할 때 발생하는 CanExecuteChanged
이벤트를 정의합니다. 대부분의 경우 Microsoft 명령에 대한 Execute
메서드만 제공됩니다. ICommand
에 대한 자세한 개요는 .NET 명령어 문서 MAUI를 참조하세요.
NET MAUI에는 ICommand
인터페이스를 구현하는 Command
및 Command<T>
클래스가 제공되며, 여기서 T
는 Execute
및 CanExecute
에 대한 인수의 유형입니다. Command
및 Command<T>
는 ICommand
인터페이스에 필요한 최소한의 기능 집합을 제공하는 기본 구현입니다.
참고
많은 MVVM 프레임워크는 ICommand
인터페이스의 다양한 기능을 구현합니다.
Command
또는 Command<T>
생성자에는 ICommand.Execute
메서드가 호출될 때 호출되는 Action 콜백 개체가 필요합니다. CanExecute
메서드는 선택적 생성자 매개 변수이며 부울을 반환하는 함수입니다.
eShop 다중 플랫폼 앱은 RelayCommand 및 AsyncRelayCommand를 사용합니다. 최신 애플리케이션의 주요 이점은 AsyncRelayCommand
가 비동기 작업에 더 나은 기능을 제공한다는 것입니다.
다음 코드에서는 Register 뷰 모델 메서드에 대리자를 지정하여 register 명령을 나타내는 Command
인스턴스를 생성하는 방법을 보여 줍니다.
public ICommand RegisterCommand { get; }
명령은 ICommand
에 대한 참조를 반환하는 속성을 통해 뷰에 노출됩니다. Execute
메서드는 Command
개체에 호출되면 Command
생성자에 지정된 대리자를 통해 뷰 모델의 메서드에 호출을 전달하기만 합니다. 명령의 Execute
대리자를 지정할 때 비동기 및 await 키워드를 사용하여 명령에서 비동기 메서드를 호출할 수 있습니다. 이는 콜백이 Task
이며 대기해야 함을 나타냅니다. 예를 들어 다음 코드는 로그인 명령을 나타내는 ICommand
인스턴스가 SignInAsync
보기 모델 메서드에 델리게이트를 지정하여 어떻게 구성되는지 보여줍니다:
public ICommand SignInCommand { get; }
...
SignInCommand = new AsyncRelayCommand(async () => await SignInAsync());
매개변수는 AsyncRelayCommand<T>
클래스를 사용하여 명령을 인스턴스화하여 Execute
및 CanExecute
액션에 전달할 수 있습니다. 예를 들어 다음 코드는 AsyncRelayCommand<T>
인스턴스를 사용하여 NavigateAsync
메서드에 문자열 형식의 인수가 필요함을 나타내는 방법을 보여줍니다.
public ICommand NavigateCommand { get; }
...
NavigateCommand = new AsyncRelayCommand<string>(NavigateAsync);
RelayCommand
및 RelayCommand<T>
클래스 모두에서 각 생성자의 CanExecute
메서드에 대한 대리자는 선택 사항입니다. 대리자를 지정하지 않으면 Command
는 CanExecute
에 대해 true를 반환합니다. 그러나 뷰 모델은 Command
개체에서 ChangeCanExecute
메서드를 호출하여 명령의 CanExecute
상태 변경을 나타낼 수 있습니다. 이로 인해 CanExecuteChanged
이벤트가 발생합니다. 명령에 바인딩된 모든 UI 컨트롤은 데이터 바인딩된 명령의 가용성을 반영하도록 사용 상태를 업데이트합니다.
다음 코드 예제는 LoginView
의 Grid
이 TapGestureRecognizer
인스턴스를 사용하여 LoginViewModel
클래스의 RegisterCommand
에 바인딩하는 방법을 보여줍니다:
<Grid Grid.Column="1" HorizontalOptions="Center">
<Label Text="REGISTER" TextColor="Gray"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
</Grid>
필요에 따라 CommandParameter
속성을 사용하여 명령 매개 변수를 정의할 수도 있습니다. 예상 인수의 형식은 Execute
및 CanExecute
대상 메서드에 지정됩니다. TapGestureRecognizer
는 사용자가 연결된 컨트롤과 상호 작용할 때 대상 명령을 자동으로 호출합니다. CommandParameter
가 제공되면 명령의 Execute 대리자에 인수로 전달됩니다.
동작을 사용하면 기능을 하위 클래스 없이도 UI 컨트롤에 추가할 수 있습니다. 대신 기능은 동작 클래스에서 구현되고 컨트롤 자체의 일부였던 것처럼 컨트롤에 연결됩니다. 동작을 사용하면 코드가 컨트롤에 간결하게 첨부되고 둘 이상의 뷰 또는 앱에서 재사용하도록 패키지될 수 있도록 컨트롤의 API와 직접 상호 작용하기 때문에, 일반적으로 코드 숨김으로 작성해야 할 코드를 구현할 수 있습니다. MVVM 컨텍스트에서 동작은 명령에 컨트롤을 연결하는 데 유용한 접근 방식입니다.
연결된 속성을 통해 컨트롤에 연결된 동작을 연결된 동작이라고 합니다. 그런 다음 동작은 연결된 요소의 노출된 API를 사용하여 뷰의 시각적 트리에서 해당 컨트롤 또는 기타 컨트롤에 기능을 추가할 수 있습니다.
.NET MAUI 동작은 Behavior
또는 Behavior<T>
클래스에서 파생된 클래스입니다. 여기서 T는 동작이 적용되어야 하는 컨트롤의 형식입니다. 이러한 클래스는 OnAttachedTo
및OnDetachingFrom
메서드를 제공하며, 이들 메서드는 동작이 컨트롤에 연결 및 분리될 때 실행될 논리를 제공하기 위해 재정의되어야 합니다.
eShop 다중 플랫폼 앱에서 BindableBehavior<T>
클래스는 Behavior<T>
클래스에서 파생됩니다. BindableBehavior<T>
클래스의 목적은 첨부된 컨트롤에 동작의 BindingContext
을 설정해야 하는 .NET MAUI 동작에 대한 기본 클래스를 제공하는 것입니다.
BindableBehavior<T>
클래스는 동작의 BindingContext
을 설정하는 재정의 가능한 OnAttachedTo
메서드와 BindingContext
를 정리하는 재정의 가능한 OnDetachingFrom
메서드를 제공합니다.
eShop 멀티플랫폼 앱에는 MAUI 커뮤니티 툴킷에서 제공하는 이벤트 명령 동작 클래스가 포함되어 있습니다. EventToCommandBehavior
는 발생하는 이벤트에 대한 응답으로 명령을 실행합니다. 이 클래스는 BaseBehavior<View>
클래스에서 파생되어 동작이 소비될 때 동작이 Command
속성으로 지정된 ICommand
에 바인딩하고 실행할 수 있도록 합니다. 다음 코드 예제는 EventToCommandBehavior
클래스를 보여줍니다.
/// <summary>
/// The <see cref="EventToCommandBehavior"/> is a behavior that allows the user to invoke a <see cref="ICommand"/> through an event. It is designed to associate Commands to events exposed by controls that were not designed to support Commands. It allows you to map any arbitrary event on a control to a Command.
/// </summary>
public class EventToCommandBehavior : BaseBehavior<VisualElement>
{
// Omitted for brevity...
/// <inheritdoc/>
protected override void OnAttachedTo(VisualElement bindable)
{
base.OnAttachedTo(bindable);
RegisterEvent();
}
/// <inheritdoc/>
protected override void OnDetachingFrom(VisualElement bindable)
{
UnregisterEvent();
base.OnDetachingFrom(bindable);
}
static void OnEventNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
=> ((EventToCommandBehavior)bindable).RegisterEvent();
void RegisterEvent()
{
UnregisterEvent();
var eventName = EventName;
if (View is null || string.IsNullOrWhiteSpace(eventName))
{
return;
}
eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ??
throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't resolve the event.", nameof(EventName));
ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);
eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, this) ??
throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't create event handler.", nameof(EventName));
eventInfo.AddEventHandler(View, eventHandler);
}
void UnregisterEvent()
{
if (eventInfo is not null && eventHandler is not null)
{
eventInfo.RemoveEventHandler(View, eventHandler);
}
eventInfo = null;
eventHandler = null;
}
/// <summary>
/// Virtual method that executes when a Command is invoked
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
protected virtual void OnTriggerHandled(object? sender = null, object? eventArgs = null)
{
var parameter = CommandParameter
?? EventArgsConverter?.Convert(eventArgs, typeof(object), null, null);
var command = Command;
if (command?.CanExecute(parameter) ?? false)
{
command.Execute(parameter);
}
}
}
OnAttachedTo
및 OnDetachingFrom
메서드는 EventName
속성에 정의된 이벤트에 대한 이벤트 처리기를 등록 및 등록 취소하는 데 사용됩니다. 그런 다음 이벤트가 발생하면 OnTriggerHandled
메서드가 호출되어 명령을 실행합니다.
이벤트가 발생할 때 EventToCommandBehavior
를 사용하여 명령을 실행하는 이점은 명령과 상호 작용하도록 설계되지 않은 컨트롤과 명령을 연결할 수 있다는 점입니다. 또한 이벤트 처리 코드를 뷰 모델로 이동하므로 단위 테스트를 수행할 수 있습니다.
EventToCommandBehavior
은(는) 명령을 지원하지 않는 컨트롤에 명령을 연결하는 데 특히 유용합니다. 예를 들어 LoginView는 다음 코드와 같이 EventToCommandBehavior
를 사용하여 사용자가 암호 값을 변경할 때 ValidateCommand
를 실행합니다.
<Entry
IsPassword="True"
Text="{Binding Password.Value, Mode=TwoWay}">
<!-- Omitted for brevity... -->
<Entry.Behaviors>
<mct:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding ValidateCommand}" />
</Entry.Behaviors>
<!-- Omitted for brevity... -->
</Entry>
런타임 시 EventToCommandBehavior
는 Entry
와의 상호 작용에 응답합니다. 사용자가 Entry
필드에 입력하면 TextChanged
이벤트가 발생하여 LoginViewModel
에서 ValidateCommand
이 실행됩니다. 기본적으로 이벤트에 대한 이벤트 인수는 명령에 전달됩니다. 필요한 경우 EventArgsConverter
속성을 사용하여 이벤트에서 제공하는 EventArgs
를 명령이 입력으로 예상하는 값으로 변환할 수 있습니다.
동작에 대한 자세한 내용은 .NET MAUI 개발자 센터의 동작 를 참조하세요.
MVVM(Model-View-ViewModel) 패턴은 애플리케이션의 비즈니스 및 프레젠테이션 논리를 UI(사용자 인터페이스)와 명확하게 구분하는 데 도움이 됩니다. 애플리케이션 논리와 UI 간의 명확한 분리를 유지 관리하면 수많은 개발 문제를 해결하고 애플리케이션을 더 쉽게 테스트, 유지 관리 및 개량할 수 있습니다. 또한 코드 재사용 기회를 크게 개선할 수 있으며 개발자와 UI 디자이너가 앱의 각 부분을 개발할 때 더 쉽게 공동 작업할 수 있습니다.
MVVM 패턴을 사용할 때 앱의 UI와 기본 프레젠테이션 및 비즈니스 논리는 UI 및 UI 논리를 캡슐화하는 뷰, 프레젠테이션 논리 및 상태를 캡슐화하는 뷰 모델, 앱의 비즈니스 논리 및 데이터를 캡슐화하는 모델의 세 가지 개별 클래스로 구분됩니다.
.NET 피드백
.NET은(는) 오픈 소스 프로젝트입니다. 다음 링크를 선택하여 피드백을 제공해 주세요.
학습
모듈
.NET MAUI용 MVVM viewmodel 디자인 - Training
MVVM 디자인 패턴이 비즈니스 논리와 사용자 인터페이스 코드를 분리하는 데 어떻게 도움이 되는지 알아보세요. viewmodel을 디자인하는 방법과 이것이 중요한 이유에 대해 알아봅니다.