다음을 통해 공유


Model-View-ViewModel 패턴

참고 항목

이 전자책은 2017년 봄에 게시되었으며 그 이후로 업데이트되지 않았습니다. 이 책에는 귀중한 기본 많이 있지만 일부 자료는 구식입니다.

개발자 환경에는 Xamarin.Forms 일반적으로 XAML에서 사용자 인터페이스를 만든 다음 사용자 인터페이스에서 작동하는 코드 숨김을 추가하는 작업이 포함됩니다. 앱이 수정되고 크기 및 범위가 커짐에 따라 복잡한 기본 테넌트 문제가 발생할 수 있습니다. 이러한 문제에는 UI 컨트롤과 비즈니스 논리 간의 긴밀한 결합이 포함되며, 이로 인해 UI 수정 비용이 증가하고 이러한 코드를 단위 테스트하기 어려워집니다.

MVVM(Model-View-ViewModel) 패턴을 사용하면 애플리케이션의 비즈니스 및 프레젠테이션 논리를 UI(사용자 인터페이스)와 클린 구분할 수 있습니다. 애플리케이션 논리와 UI 간의 클린 분리를 유지 관리하면 수많은 개발 문제를 해결하는 데 도움이 되며 애플리케이션을 더 쉽게 테스트, 기본, 발전시킬 수 있습니다. 또한 코드 재사용 기회를 크게 향상시킬 수 있으며 개발자와 UI 디자이너가 앱의 각 부분을 개발할 때 보다 쉽게 공동 작업할 수 있습니다.

MVVM 패턴

MVVM 패턴에는 모델, 뷰 및 뷰 모델의 세 가지 핵심 구성 요소가 있습니다. 각 구성 요소는 다른 용도로 사용됩니다. 그림 2-1은 세 구성 요소 간의 관계를 보여 줍니다.

The MVVM pattern

그림 2-1: MVVM 패턴

각 구성 요소의 책임을 이해하는 것 외에도 서로 상호 작용하는 방법을 이해하는 것도 중요합니다. 개략적으로 뷰는 뷰 모델을 "인식"하고, 뷰 모델은 모델을 "인식"하지만 모델은 뷰 모델을 인식하지 못하고 뷰 모델은 뷰를 인식하지 못합니다. 따라서 뷰 모델은 뷰를 모델에서 격리하고 모델이 뷰와 독립적으로 발전할 수 있도록 합니다.

MVVM 패턴을 사용하면 다음과 같은 이점이 있습니다.

  • 기존 비즈니스 논리를 캡슐화하는 기존 모델 구현이 있는 경우 변경하기가 어렵거나 위험할 수 있습니다. 이 시나리오에서 뷰 모델은 모델 클래스의 어댑터 역할을 하며 모델 코드를 크게 변경하지 않도록 할 수 있습니다.
  • 개발자는 뷰를 사용하지 않고 뷰 모델 및 모델에 대한 단위 테스트를 만들 수 있습니다. 뷰 모델에 대한 단위 테스트는 뷰에서 사용하는 것과 정확히 동일한 기능을 실행할 수 있습니다.
  • 보기가 XAML에서 완전히 구현되는 경우 코드를 건드리지 않고 앱 UI를 다시 디자인할 수 있습니다. 따라서 새 버전의 뷰가 기존 뷰 모델에서도 작동합니다.
  • 디자이너와 개발자는 개발 프로세스 중에 해당 구성 요소에서 독립적으로 동시에 작업할 수 있습니다. 디자이너는 뷰에 집중할 수 있고 개발자는 뷰 모델 및 모델 구성 요소에서 작업할 수 있습니다.

MVVM을 효과적으로 사용하는 핵심은 앱 코드를 올바른 클래스로 팩터하는 방법을 이해하고 클래스가 상호 작용하는 방식을 이해하는 것입니다. 다음 섹션에서는 MVVM 패턴에서 각 클래스의 책임을 설명합니다.

보기

뷰는 사용자가 화면에 표시되는 구조, 레이아웃 및 모양을 정의합니다. 이상적으로 각 뷰는 비즈니스 논리를 포함하지 않는 제한된 코드 숨김을 사용하여 XAML에서 정의됩니다. 그러나 경우에 따라 코드 숨김에는 애니메이션과 같이 XAML에서 표현하기 어려운 시각적 동작을 구현하는 UI 논리가 포함될 수 있습니다.

Xamarin.Forms 애플리케이션에서 뷰는 일반적으로 Page-derived 또는 ContentView-derived 클래스입니다. 그러나 뷰는 표시되는 개체를 시각적으로 표현하는 데 사용할 UI 요소를 지정하는 데이터 템플릿으로도 나타낼 수 있습니다. 뷰로서의 데이터 템플릿에는 코드 숨김이 없으며 특정 뷰 모델 형식에 바인딩되도록 설계되었습니다.

코드 숨김에서 UI 요소를 사용하도록 설정하거나 사용하지 않도록 설정하지 마세요. 뷰 모델이 명령을 사용할 수 있는지 여부 또는 작업이 보류 중임을 나타내는 등 보기 표시의 일부 측면에 영향을 주는 논리적 상태 변경을 정의해야 합니다. 따라서 코드 숨김에서 UI 요소를 사용하도록 설정하거나 사용하지 않도록 설정하는 대신 뷰 모델 속성에 바인딩하여 UI 요소를 사용하거나 사용하지 않도록 설정합니다.

뷰 모델에서 뷰 상호 작용에 대한 응답으로 코드를 실행하는 몇 가지 옵션이 있습니다(예: 단추 클릭 또는 항목 선택). 컨트롤이 명령을 지원하는 경우 컨트롤의 Command 속성은 뷰 모델의 속성에 데이터 바인딩 ICommand 될 수 있습니다. 컨트롤의 명령이 호출되면 뷰 모델의 코드가 실행됩니다. 명령 외에도 동작을 뷰의 개체에 연결할 수 있으며 호출할 명령이나 발생할 이벤트를 수신 대기할 수 있습니다. 이에 대한 응답으로 동작은 뷰 모델 또는 뷰 모델의 메서드를 호출 ICommand 할 수 있습니다.

ViewModel

뷰 모델은 뷰가 데이터 바인딩될 수 있는 속성 및 명령을 구현하고 변경 알림 이벤트를 통해 뷰에 상태 변경을 알릴 수 있습니다. 뷰 모델에서 제공하는 속성 및 명령은 UI에서 제공할 기능을 정의하지만 뷰는 해당 기능을 표시하는 방법을 결정합니다.

비동기 작업을 사용하여 UI의 응답성을 유지합니다. 모바일 앱은 사용자의 성능 인식을 개선하기 위해 UI 스레드의 차단을 해제해야 합니다. 따라서 뷰 모델에서 I/O 작업에 비동기 메서드를 사용하고 이벤트를 발생시켜 뷰에 속성 변경 내용을 비동기적으로 알립니다.

또한 뷰 모델은 필요한 모든 모델 클래스와 뷰의 상호 작용을 조정해야 합니다. 일반적으로 뷰 모델과 모델 클래스 간에 일대다 관계가 있습니다. 뷰 모델은 뷰의 컨트롤이 직접 데이터에 바인딩될 수 있도록 모델 클래스를 뷰에 직접 노출하도록 선택할 수 있습니다. 이 경우 모델 클래스는 데이터 바인딩 및 변경 알림 이벤트를 지원하도록 설계되어야 합니다.

각 뷰 모델은 뷰에서 쉽게 사용할 수 있는 형식으로 모델의 데이터를 제공합니다. 이를 위해 뷰 모델은 때때로 데이터 변환을 수행합니다. 뷰 모델에 이 데이터 변환을 배치하는 것은 뷰가 바인딩될 수 있는 속성을 제공하기 때문에 좋은 생각입니다. 예를 들어 보기 모델은 두 속성의 값을 결합하여 보기에서 더 쉽게 표시할 수 있습니다.

변환 계층에서 데이터 변환을 중앙 집중화합니다. 변환기를 뷰 모델과 뷰 사이에 있는 별도의 데이터 변환 계층으로 사용할 수도 있습니다. 예를 들어 데이터에 뷰 모델이 제공하지 않는 특별한 서식이 필요한 경우 이 작업이 필요할 수 있습니다.

뷰 모델이 뷰를 사용하여 양방향 데이터 바인딩에 참여하려면 해당 속성이 PropertyChanged 이벤트를 발생시켜야 합니다. 뷰 모델은 INotifyPropertyChanged 인터페이스를 구현하고 속성이 변경되면 PropertyChanged 이벤트를 발생시켜 이 요구 사항을 충족합니다.

컬렉션의 경우 뷰 친화적인 ObservableCollection<T>이 제공됩니다. 이 컬렉션은 컬렉션 변경 알림을 구현하여 개발자가 컬렉션에서 INotifyCollectionChanged 인터페이스를 구현할 필요가 없습니다.

모델

모델 클래스는 앱의 데이터를 캡슐화하는 비 시각적 클래스입니다. 따라서 모델은 일반적으로 비즈니스 및 유효성 검사 논리와 함께 데이터 모델을 포함하는 앱의 도메인 모델을 나타내는 것으로 생각할 수 있습니다. 모델 개체의 예로는 DTO(데이터 전송 개체), POCO(Plain Old CLR Objects), 생성된 엔터티 및 프록시 개체가 있습니다.

모델 클래스는 일반적으로 데이터 액세스 및 캐싱을 캡슐화하는 서비스 또는 리포지토리와 함께 사용됩니다.

뷰에 모델 보기 커넥트

뷰 모델은 의 데이터 바인딩 기능을 사용하여 뷰에 연결할 수 있습니다 Xamarin.Forms. 런타임에 뷰 및 뷰 모델을 생성하고 서로 연결하는 데 사용할 수 있는 여러 가지 방법이 있습니다. 이러한 방법은 뷰 우선 구성과 뷰 모델 우선 구성이라고 하는 두 가지 범주로 분류됩니다. 뷰 우선 구성과 뷰 모델 우선 구성 중에서 선택하는 것은 기본 설정 및 복잡성의 문제입니다. 그러나 모든 방법은 뷰가 BindingContext 속성에 뷰 모델을 할당한다는 동일한 목표가 있습니다.

뷰 우선 구성을 사용하면 앱은 개념적으로 사용하는 뷰 모델에 연결하는 뷰로 구성됩니다. 이 방법의 주요 이점은 뷰 모델이 뷰 자체에 의존하지 않으므로 느슨하게 결합된 단위 테스트 가능한 앱을 쉽게 생성할 수 있다는 것입니다. 또한 클래스가 만들어지고 연결되는 방법을 이해하기 위해 코드 실행을 추적할 필요 없이 시각적 구조를 따라 앱의 구조를 쉽게 이해할 수 있습니다. 또한 뷰 첫 번째 생성은 Xamarin.Forms 탐색이 발생할 때 페이지 생성을 담당하는 탐색 시스템과 일치하므로 뷰 모델이 첫 번째 컴퍼지션을 복잡하게 만들고 플랫폼과 잘못 정렬됩니다.

뷰 모델 우선 컴퍼지션을 사용하면 앱이 개념적으로 보기 모델로 구성되며, 서비스는 뷰 모델에 대한 뷰를 찾는 역할을 담당합니다. 뷰 모델 우선 구성은 뷰 만들기를 추상화할 수 있어 앱의 논리적 비 UI 구조에 집중할 수 있으므로 일부 개발자에게 더 자연스럽게 느껴집니다. 또한 다른 뷰 모델이 뷰 모델을 만들도록 허용합니다. 그러나 이 방법은 종종 복잡하며 앱의 다양한 부분이 만들어지고 연결되는 방식을 이해하기 어려울 수 있습니다.

뷰 모델과 뷰를 독립적으로 유지합니다. 뷰의 데이터 원본 속성에 대한 바인딩은 해당 뷰 모델에 대한 뷰의 보안 주체 종속성이어야 합니다. 특히 뷰 모델과 같은 Button 뷰 형식을 ListView참조하지 마세요. 여기에 설명된 원칙에 따라 뷰 모델을 격리하여 테스트할 수 있으므로 범위를 제한하여 소프트웨어 결함의 가능성을 줄일 수 있습니다.

다음 섹션에서는 뷰 모델을 뷰에 연결하는 주요 방법을 설명합니다.

선언적으로 뷰 모델 만들기

가장 간단한 방법은 뷰가 XAML에서 해당 뷰 모델을 선언적으로 인스턴스화하는 것입니다. 뷰가 생성되면 해당 뷰 모델 개체도 생성됩니다. 다음 코드 예제는 이 방법을 보여 줍니다.

<ContentPage ... xmlns:local="clr-namespace:eShop">  
    <ContentPage.BindingContext>  
        <local:LoginViewModel />  
    </ContentPage.BindingContext>  
    ...  
</ContentPage>

ContentPage가 만들어지면 LoginViewModel 인스턴스가 자동으로 생성되고 뷰의 BindingContext로 설정됩니다.

뷰에 의한 이 선언적 뷰 모델 생성 및 할당은 간단하다는 장점이 있지만 뷰 모델에서 기본(매개 변수가 없는) 생성자가 필요하다는 단점이 있습니다.

프로그래밍 방식으로 보기 모델 만들기

뷰에는 코드 숨김 파일에 뷰 모델이 해당 BindingContext 속성에 할당되는 코드가 있을 수 있습니다. 다음 코드 예제와 같이 이 작업은 뷰의 생성자에서 수행되는 경우가 많습니다.

public LoginView()  
{  
    InitializeComponent();  
    BindingContext = new LoginViewModel(navigationService);  
}

뷰의 코드 숨김 내에서 프로그래밍 방식 뷰 모델 생성 및 할당은 간단하다는 장점이 있습니다. 그러나 이 방법의 주요 단점은 뷰가 뷰 모델에 필요한 종속성을 제공해야 한다는 것입니다. 종속성 주입 컨테이너를 사용하면 뷰와 뷰 모델 간의 느슨한 결합을 유지하는 데 도움이 될 수 있습니다. 자세한 내용은 종속성 주입을 참조하세요.

데이터 템플릿으로 정의된 뷰 만들기

뷰는 데이터 템플릿으로 정의되고 뷰 모델 형식과 연결할 수 있습니다. 데이터 템플릿은 리소스로 정의되거나 뷰 모델을 표시할 컨트롤 내에서 인라인으로 정의할 수 있습니다. 컨트롤의 콘텐츠는 뷰 모델 인스턴스이며 데이터 템플릿은 시각적으로 나타내는 데 사용됩니다. 이 기술은 뷰 모델을 먼저 인스턴스화한 다음 뷰를 만드는 상황의 예입니다.

뷰 모델 로케이터를 사용하여 뷰 모델 자동 만들기

뷰 모델 로케이터는 뷰 모델의 인스턴스화와 뷰에 대한 연결을 관리하는 사용자 지정 클래스입니다. eShopOnContainers 모바일 앱 ViewModelLocator 에서 클래스에는 뷰 모델과 뷰를 연결하는 데 사용되는 연결된 속성 AutoWireViewModel이 있습니다. 보기의 XAML에서 이 연결된 속성은 다음 코드 예제와 같이 뷰 모델이 뷰에 자동으로 연결되어야 함을 나타내기 위해 true로 설정됩니다.

viewModelBase:ViewModelLocator.AutoWireViewModel="true"

속성은 AutoWireViewModel false로 초기화된 바인딩 가능한 속성이며 값이 변경 OnAutoWireViewModelChanged 되면 이벤트 처리기가 호출됩니다. 이 메서드는 뷰에 대한 뷰 모델을 확인합니다. 다음 코드 예제에서는 이를 달성하는 방법을 보여줍니다.

private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)  
{  
    var view = bindable as Element;  
    if (view == null)  
    {  
        return;  
    }  

    var viewType = view.GetType();  
    var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");  
    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;  
    var viewModelName = string.Format(  
        CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);  

    var viewModelType = Type.GetType(viewModelName);  
    if (viewModelType == null)  
    {  
        return;  
    }  
    var viewModel = _container.Resolve(viewModelType);  
    view.BindingContext = viewModel;  
}

이 메서드는 OnAutoWireViewModelChanged 규칙 기반 접근 방식을 사용하여 뷰 모델을 확인하려고 시도합니다. 이 규칙은 다음을 가정합니다.

  • 보기 모델은 뷰 형식과 동일한 어셈블리에 있습니다.
  • 보기는 .에 있습니다. 자식 네임스페이스를 표시합니다.
  • 보기 모델은 에 있습니다. ViewModels 자식 네임스페이스입니다.
  • 보기 모델 이름은 뷰 이름과 일치하며 "ViewModel"로 끝납니다.

마지막으로, 메서드는 OnAutoWireViewModelChanged 뷰 형식을 BindingContext 확인된 뷰 모델 형식으로 설정합니다. 보기 모델 유형을 확인하는 방법에 대한 자세한 내용은 해결을 참조 하세요.

이 방법은 앱에 보기 모델의 인스턴스화 및 뷰에 대한 연결을 담당하는 단일 클래스가 있다는 장점이 있습니다.

대체의 용이성을 위해 뷰 모델 로케이터를 사용합니다. 뷰 모델 로케이터는 단위 테스트 또는 디자인 타임 데이터와 같은 종속성의 대체 구현을 위한 대체 지점으로 사용할 수도 있습니다.

기본 뷰 모델 또는 모델의 변경 내용에 대한 응답으로 보기 업데이트

보기에 액세스할 수 있는 모든 뷰 모델 및 모델 클래스는 인터페이스를 INotifyPropertyChanged 구현해야 합니다. 뷰 모델 또는 모델 클래스에서 이 인터페이스를 구현하면 기본 속성 값이 변경될 때 클래스가 뷰의 데이터 바인딩된 컨트롤에 변경 알림을 제공할 수 있습니다.

앱은 다음 요구 사항을 충족하여 속성 변경 알림의 올바른 사용을 위해 설계되어야 합니다.

  • 공용 속성의 값이 변경되면 항상 PropertyChanged 이벤트를 발생시킵니다. XAML 바인딩이 발생하는 방식에 대한 지식 때문에 PropertyChanged 이벤트 발생을 무시할 수 있다고 가정하지 마세요.
  • 뷰 모델 또는 모델의 다른 속성에서 해당 값을 사용하는 계산 속성에 대해 항상 PropertyChanged 이벤트를 발생시킵니다.
  • 속성을 변경하는 메서드의 끝에서 또는 개체가 안전한 상태인 것으로 알려진 경우 항상 PropertyChanged 이벤트를 발생시킵니다. 이벤트가 발생하면 이벤트의 처리기를 동기적으로 호출하여 작업이 중단됩니다. 작업 중간에 발생하는 경우 부분적으로 업데이트된 안전하지 않은 상태일 때 개체가 콜백 함수에 노출될 수 있습니다. 또한 PropertyChanged 이벤트에 의해 계단식 변경이 트리거될 수 있습니다. 계단식 변경은 일반적으로 업데이트가 완료되어야 안전하게 실행될 수 있습니다.
  • 속성이 변경되지 않으면 PropertyChanged 이벤트를 발생시키지 않습니다. 즉, PropertyChanged 이벤트를 발생시키기 전에 이전 값과 새 값을 비교해야 합니다.
  • 속성을 초기화하는 경우 뷰 모델의 생성자 중에 PropertyChanged 이벤트를 발생시키지 않습니다. 뷰의 데이터 바인딩된 컨트롤은 이 시점에서 변경 알림을 수신하도록 구독하지 않습니다.
  • 클래스의 public 메서드에 대한 단일 동기 호출 내에서 속성 이름 인수가 같은 PropertyChanged 이벤트를 두 개 이상 발생시키지 않습니다. 예를 들어 NumberOfItems 속성의 백업 저장소가 _numberOfItems 필드인 경우 메서드가 루프 실행 동안 _numberOfItems를 50번 증가시키면 모든 작업이 완료된 후 NumberOfItems 속성에 대한 속성 변경 알림을 한 번만 발생시켜야 합니다. 비동기 메서드의 경우 비동기 연속 체인의 각 동기 세그먼트에서 지정된 속성 이름에 대해 PropertyChanged 이벤트를 발생시킵니다.

eShopOnContainers 모바일 앱은 클래스를 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)  
    {  
        ...  
    }  
}

Xamarin.Form의 BindableObject 클래스는 인터페이스를 INotifyPropertyChanged 구현하고 메서드를 OnPropertyChanged 제공합니다. ExtendedBindableObject 클래스는 속성 변경 알림을 호출하는 RaisePropertyChanged 메서드를 제공하며, 이 경우 BindableObject 클래스에서 제공하는 기능을 사용합니다.

eShopOnContainers 모바일 앱의 각 뷰 모델 클래스는 클래스에서 ViewModelBase 파생되며, 클래스에서 ExtendedBindableObject 파생됩니다. 따라서 각 뷰 모델 클래스는 ExtendedBindableObject 클래스의 RaisePropertyChanged 메서드를 사용하여 속성 변경 알림을 제공합니다. 다음 코드 예제에서는 eShopOnContainers 모바일 앱이 람다 식을 사용하여 속성 변경 알림을 호출하는 방법을 보여줍니다.

public bool IsLogin  
{  
    get  
    {  
        return _isLogin;  
    }  
    set  
    {  
        _isLogin = value;  
        RaisePropertyChanged(() => IsLogin);  
    }  
}

이러한 방식으로 람다 식을 사용하면 각 호출에 대해 람다 식을 평가해야 하므로 성능 비용이 적습니다. 성능 비용이 적고 일반적으로 앱에 영향을 주지는 않지만 많은 변경 알림이 있을 때 비용이 발생할 수 있습니다. 그러나 이 방법의 이점은 속성 이름을 바꾸는 경우 컴파일 시간 형식 안전성 및 리팩터링 지원을 제공한다는 것입니다.

명령 및 동작을 사용하는 UI 상호 작용

모바일 앱에서 작업은 일반적으로 코드 숨김 파일에서 이벤트 처리기를 만들어 구현할 수 있는 단추 클릭과 같은 사용자 작업에 대한 응답으로 호출됩니다. 그러나 MVVM 패턴에서 작업을 구현하는 책임은 뷰 모델에 있으며 코드를 코드 숨김에 배치하는 것은 피해야 합니다.

명령은 UI의 컨트롤에 바인딩될 수 있는 작업을 나타내는 편리한 방법을 제공합니다. 작업을 구현하는 코드를 캡슐화하고 보기의 시각적 표현과 분리된 상태를 유지하는 데 도움이 됩니다. Xamarin.Forms 에는 명령에 선언적으로 연결할 수 있는 컨트롤이 포함되어 있으며, 이러한 컨트롤은 사용자가 컨트롤과 상호 작용할 때 명령을 호출합니다.

또한 동작을 사용해도 컨트롤을 명령에 선언적으로 연결할 수 있습니다. 그러나 동작은 컨트롤에 의해 발생되는 이벤트 범위와 연결된 작업을 호출하는 데 사용할 수 있습니다. 따라서 동작은 명령 사용 컨트롤과 동일한 많은 시나리오를 처리하면서 더 높은 수준의 유연성과 제어를 제공합니다. 또한 동작은 명령 개체 또는 메서드를 명령과 상호 작용하도록 특별히 설계되지 않은 컨트롤과 연결하는 데에 사용할 수도 있습니다.

명령 구현

뷰 모델은 일반적으로 인터페이스를 구현 ICommand 하는 개체 인스턴스인 뷰에서 바인딩하기 위한 명령 속성을 노출합니다. 여러 Xamarin.Forms 컨트롤은 뷰 모델에서 Command 제공하는 개체에 바인딩된 데이터일 수 있는 ICommand 속성을 제공합니다. ICommand 인터페이스는 작업 자체를 캡슐화하는 Execute 메서드, 명령을 호출할 수 있는지 여부를 나타내는 CanExecute 메서드, 명령 실행 여부에 영향을 주는 변경이 발생할 때 발생하는 CanExecuteChanged 이벤트를 정의합니다. 제공된 클래스 및 클래스는 Command 인터페이스를 ICommand 구현합니다. 여기서 T 인수의 형식은 다음과 같습니다 ExecuteCanExecute.Command<T>Xamarin.Forms

뷰 모델 내에서 형식의 개체 또는 Command<T> 형식 Command 의 뷰 모델에 ICommand있는 각 public 속성에 대한 개체가 있어야 합니다. Command 또는 Command<T> 생성자에는 메서드가 Action 호출될 때 호출되는 콜백 개체가 ICommand.Execute 필요합니다. 메서드는 CanExecute 선택적 생성자 매개 변수이며 Funcbool.

다음 코드에서는 레지스터 명령을 나타내는 인스턴스가 뷰 모델 메서드에 대리 Register 자를 지정하여 생성되는 방법을 Command 보여 줍니다.

public ICommand RegisterCommand => new Command(Register);

명령은 ICommand에 대한 참조를 반환하는 속성을 통해 뷰에 노출됩니다. Execute 메서드는 Command 개체에 호출되면 Command 생성자에 지정된 대리자를 통해 뷰 모델의 메서드에 호출을 전달하기만 합니다.

명령의 대리자를 지정할 때 키워드(keyword) 사용하여 asyncawait 명령에 Execute 의해 비동기 메서드를 호출할 수 있습니다. 이는 콜백이 Task이며 대기해야 함을 나타냅니다. 예를 들어 다음 코드에서는 뷰 모델 메서드에 대리 SignInAsync 자를 지정하여 로그인 명령을 나타내는 인스턴스를 생성하는 방법을 Command 보여 줍니다.

public ICommand SignInCommand => new Command(async () => await SignInAsync());

매개 변수는 Command<T> 클래스를 사용하여 명령을 인스턴스화하는 방법으로 ExecuteCanExecute작업에 전달할 수 있습니다. 예를 들어 다음 코드는 메서드에 형식 string인수 Command<T> 가 필요함을 NavigateAsync 나타내는 데 인스턴스를 사용하는 방법을 보여 줍니다.

public ICommand NavigateCommand => new Command<string>(NavigateAsync);

CommandCommand<T> 클래스 모두에서 각 생성자의 CanExecute 메서드에 대한 대리자는 선택 사항입니다. 대리자를 지정하지 않으면 대리자가 Command 반환 trueCanExecute됩니다. 그러나 뷰 모델은 Command 개체에 ChangeCanExecute 메서드를 호출하여 명령의 CanExecute 상태 변경을 나타낼 수 있습니다. 이로 인해 CanExecuteChanged 이벤트가 발생합니다. 그러면 명령에 바인딩된 UI의 모든 컨트롤은 데이터 바인딩된 명령의 가용성을 반영하도록 활성화된 상태 업데이트합니다.

보기에서 명령 호출

다음 코드 예제에서는 LoginViewGridTapGestureRecognizer 인스턴스를 사용하여 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 속성을 사용하여 명령 매개 변수를 정의할 수도 있습니다. 예상 인수의 형식은 ExecuteCanExecute 대상 메서드에 지정됩니다. TapGestureRecognizer는 사용자가 연결된 컨트롤과 상호 작용할 때 대상 명령을 자동으로 호출합니다. 명령 매개 변수가 제공되면 명령의 Execute 대리자로 인수로 전달됩니다.

동작 구현

동작을 사용하면 기능을 하위 클래스 없이도 UI 컨트롤에 추가할 수 있습니다. 대신 기능은 동작 클래스에서 구현되고 컨트롤 자체의 일부였던 것처럼 컨트롤에 연결됩니다. 동작을 사용하면 컨트롤에 간결하게 연결되고 둘 이상의 보기 또는 앱에서 다시 사용할 수 있도록 패키지될 수 있는 방식으로 컨트롤의 API와 직접 상호 작용하기 때문에 일반적으로 코드 숨김으로 작성해야 하는 코드를 구현할 수 있습니다. MVVM 컨텍스트에서 동작은 명령에 컨트롤을 연결하는 데 유용한 접근 방식입니다.

연결된 속성을 통해 컨트롤에 연결된 동작을 연결된 동작이라고 합니다. 그런 다음 동작은 연결된 요소의 노출된 API를 사용하여 뷰의 시각적 트리에서 해당 컨트롤 또는 기타 컨트롤에 기능을 추가할 수 있습니다. eShopOnContainers 모바일 앱에는 LineColorBehavior 연결된 동작인 클래스가 포함되어 있습니다. 이 동작에 대한 자세한 내용은 유효성 검사 오류 표시를 참조 하세요.

Xamarin.Forms 동작은 동작이 적용되어야 하는 컨트롤의 형식인 또는 Behavior<T> 클래스에서 Behavior 파생되는 클래스 T 입니다. 이러한 클래스는 OnAttachedToOnDetachingFrom 메서드를 제공하며, 이들 메서드는 동작이 컨트롤에 연결 및 분리될 때 실행될 논리를 제공하기 위해 재정의되어야 합니다.

eShopOnContainers 모바일 앱에서 클래스는 BindableBehavior<T> 클래스에서 Behavior<T> 파생됩니다. 클래스의 BindableBehavior<T> 목적은 연결된 컨트롤로 동작을 설정해야 BindingContext 하는 동작에 대한 Xamarin.Forms 기본 클래스를 제공하는 것입니다.

BindableBehavior<T> 클래스는 동작의 BindingContext를 설정하는 재정의 가능한 OnAttachedTo 메서드와 BindingContext를 정리하는 재정의 가능한 OnDetachingFrom 메서드를 제공합니다. 또한 클래스는 연결된 컨트롤에 대한 참조를 AssociatedObject 속성에 저장합니다.

eShopOnContainers 모바일 앱에는 발생하는 이벤트에 대한 응답으로 명령을 실행하는 클래스가 포함되어 EventToCommandBehavior 있습니다. 이 클래스는 동작이 사용될 때 Command 속성에 의해 지정된 ICommand에 바인딩되고 실행할 수 있도록 BindableBehavior<T> 클래스에서 파생됩니다. 다음 코드 예제는 EventToCommandBehavior 클래스를 보여줍니다.

public class EventToCommandBehavior : BindableBehavior<View>  
{  
    ...  
    protected override void OnAttachedTo(View visualElement)  
    {  
        base.OnAttachedTo(visualElement);  

        var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();  
        if (events.Any())  
        {  
            _eventInfo = events.FirstOrDefault(e => e.Name == EventName);  
            if (_eventInfo == null)  
                throw new ArgumentException(string.Format(  
                        "EventToCommand: Can't find any event named '{0}' on attached type",   
                        EventName));  

            AddEventHandler(_eventInfo, AssociatedObject, OnFired);  
        }  
    }  

    protected override void OnDetachingFrom(View view)  
    {  
        if (_handler != null)  
            _eventInfo.RemoveEventHandler(AssociatedObject, _handler);  

        base.OnDetachingFrom(view);  
    }  

    private void AddEventHandler(  
            EventInfo eventInfo, object item, Action<object, EventArgs> action)  
    {  
        ...  
    }  

    private void OnFired(object sender, EventArgs eventArgs)  
    {  
        ...  
    }  
}

OnAttachedToOnDetachingFrom 메서드는 EventName 속성에 정의된 이벤트에 대한 이벤트 처리기를 등록 및 등록 취소하는 데 사용됩니다. 그런 다음 이벤트가 발생하면 OnFired 메서드가 호출되어 명령을 실행합니다.

이벤트가 발생할 때 EventToCommandBehavior를 사용하여 명령을 실행하는 이점은 명령과 상호 작용하도록 설계되지 않은 컨트롤과 명령을 연결할 수 있다는 점입니다. 또한 이벤트 처리 코드를 뷰 모델로 이동하므로 단위 테스트를 수행할 수 있습니다.

뷰에서 동작 호출

명령을 EventToCommandBehavior 지원하지 않는 컨트롤에 명령을 연결하는 데 특히 유용합니다. 예를 들어 ProfileView 다음 코드와 같이 사용자의 주문을 나열하는 이벤트가 발생할 ListViewItemTapped 를 실행하는 OrderDetailCommand 데 사용됩니다EventToCommandBehavior.

<ListView>  
    <ListView.Behaviors>  
        <behaviors:EventToCommandBehavior             
            EventName="ItemTapped"  
            Command="{Binding OrderDetailCommand}"  
            EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />  
    </ListView.Behaviors>  
    ...  
</ListView>

런타임 시 EventToCommandBehaviorListView와의 상호 작용에 응답합니다. 항목이 선택되면 ListViewItemTapped 이벤트가 발생합니다. 이 이벤트는 에서 ProfileViewModel실행됩니다OrderDetailCommand. 기본적으로 이벤트에 대한 이벤트 인수는 명령에 전달됩니다. 이 데이터는 원본과 대상 간에 전달될 때 속성에 EventArgsConverter 지정된 변환기를 통해 변환되며, 이 변환기는 원본ItemTappedEventArgsListView 데이터를 반환합니다Item. 따라서 실행될 때 OrderDetailCommand 선택한 Order 작업이 등록된 작업에 매개 변수로 전달됩니다.

동작에 대한 자세한 내용은 동작을 참조 하세요.

요약

MVVM(Model-View-ViewModel) 패턴을 사용하면 애플리케이션의 비즈니스 및 프레젠테이션 논리를 UI(사용자 인터페이스)와 클린 구분할 수 있습니다. 애플리케이션 논리와 UI 간의 클린 분리를 유지 관리하면 수많은 개발 문제를 해결하는 데 도움이 되며 애플리케이션을 더 쉽게 테스트, 기본, 발전시킬 수 있습니다. 또한 코드 재사용 기회를 크게 향상시킬 수 있으며 개발자와 UI 디자이너가 앱의 각 부분을 개발할 때 보다 쉽게 공동 작업할 수 있습니다.

MVVM 패턴을 사용하여 앱의 UI와 기본 프레젠테이션 및 비즈니스 논리는 UI 및 UI 논리를 캡슐화하는 뷰의 세 가지 개별 클래스로 구분됩니다. 프레젠테이션 논리 및 상태를 캡슐화하는 뷰 모델 및 앱의 비즈니스 논리 및 데이터를 캡슐화하는 모델입니다.