C# - WPF, MVVM 노트 (3)

업데이트:

WPF MVVM 패턴

스파게티 코드

실제로 많은 애플리케이션에서 모든 로직이 심하게 서로 섞여있다. 이런 코딩 방식은 유지 보수가 불가능한 코드를 생성한다.

  • 거대한 파일을 만들어내고 유지보수가 갈수록 어렵게 된다. 한 화면에 약 5000줄 이상의 코드 비하인드를 생성한다.

  • 테스트가 어렵다. 컨트롤 코드와 논리적 코드가 많이 섞여 있기 때문에 테스트를 실행하려면 UI를 인스턴스화해야 한다.

  • 재사용이 어렵다. 컨트롤에 대한 참조는 다른 화면에서 코드 재사용을 어렵게 만든다.

  • 컨트롤의 속성을 깊이 있게 알아야 한다. UI에 대한 모든 변경은 해당 코드에 큰 영향을 미친다.


MVC 모델

MVC가 위와 같은 스파게티 코드의 개선 모델로 등장했다.

  • 뷰: 순수 XAML
  • 모델: INotifyPropertyChanged 및 INotifyCollectionChanged를 구현하는 클래스
  • 컨트롤러: 명령, 트리거, 관련 이벤트, NavigationService


하지만 여기서 다시 설계자가 개발자가 올바르게 코딩했는지를 파악하기 위해 해당 코드를 살펴볼 필요가 있다.

그래서 등장한 것이 MVVM이다.


MVVM 모델

MVVM에는 몇 가지 장점이 있다.

  1. 코드 리뷰를 훨씬 쉽게 해준다.
  2. 깨끗하고 재사용 가능하고 테스트 가능하다.
  3. 유지 보수 가능한 코딩 방법을 제공한다.


MVVM은 View - ViewModel - DataModel로 연관되어 있다.

  • DataModel
    • 비즈니스 클래스로 구성된다.
    • UI에 제공된 데이터를 갖고 있다.
    • 쉰게 단위테스트를 할 수 있다.
  • View:
    • UI이다.
    • 이상적으로 순수 XAML로 구성된다.
    • 자동화된 테스트가 어렵다.
    • View의 DataContext는 ViewModel이다.
  • ViewModel
    • 하나의 뷰에 대한 메소드로 속성 및 액션을 사용해 데이터를 노출한다.
    • 뷰를 참조하지 않아야 하지만 뷰에 크게 의존한다.
    • 다른 DataModel 혼합을 허용하거나 비동기 호출의 복잡성을 숨길 수 있다.
    • 단위 테스트를 쉽게 할 수 있다.
    • INotifyPropertyChanged를 구현한다.


권장 단계(단순)

과정

  1. ViewModel 생성
  2. ViewModel이 노출해야하는 속성을 찾는다.
  3. 알림 속성을 선언한다.
  4. ViewModel을 View의 DataContext로 사용한다.
  5. View를 ViewModel에 데이터 바인딩한다.
  6. 기능적 논리를 코딩한다 (3단계 이후~).


ViewModel 생성

ViewModel은 INotifyPropertyChanged를 구현해야 한다.

대체로 각 화면당 하나의 ViewModel은 좋은 출발점이다.
또한 화면에 따라 클래스의 이름을 명명하면 좋다.


노출 속성 선택

생성을 원하는 뷰를 살펴보았을 때, 모든 사용자 입력이나 출력에 대해 ViewModel에 속성을 추가해야 한다.

ViewModel에 추가하는 속성은 알림 속성이어야 한다.

private double speed;
public double Speed
{
    get { return speed; }
    set
    {
        speed = value;
        OnPropertyChanged("Speed");
        OnSpeedChange();
    }
}

void OnSpeedChange()
{
    // 기능 로직
}


View의 DataContext로 ViewModel 사용

이제 View의 XAML에서 DataContext를 ViewModel로 지정한다.

<Window xmlns:vm:"clr-namespace:ViewModels"
    ...>
    <Window.DataContext>
        <vm:YourScreenViewModel />
    </Window.DataContext>
    ...
</Window>
this.DataContext = new YourScreenViewModel();


위의 코드로 사용하면 ViewModel이 인스턴스화 된다는 단점이 있다.


해결책

클래스를 지정하는 XAML의 d:DataContext 특성을 사용하면 해결할 수 있다.

이러면 뷰를 디자인하는 동안 인스턴스화는 되지않지만 데이터 바인딩 편집기에서 도움을 얻을 수 있다.

<Window xmlns:vm="clr-namespace:ViewModels"
    mc:Ignorable="d"
    xmls:d="httpL//schemas.microsoft.com/expression/blend/2008"
    d:DataContext="{d:DesignInstance vm:YourScreenViewModel}"
    ...>
    ...
</Window>


그리고 다시 코드 비하인드에서 DataContext를 할당한다.

this.DataContext = new YourScreenViewModel();


Binding할 때, **UpdateSourceTrigger=PropertyChanged** 옵션을 주면 속성이 변경될때 바로 업데이트가 된다.

<TextBox Text="{Binding Euros, UpdateSourceTrigger=PropertyChanged}" />


명령과 메소드

명령: ICommand

WPF에는 ICommand 인터페이스가 포함되어 있다.

ICommand 인터페이스를 구현하고 해당 클래스를 인스턴스화하면 Button 및 MenuItem 컨트롤의 Command 속성을 사용해 해당 인스턴스를 참조할 수 있다.

하지만 이는 조금 어려운 방법이다.

  1. 많은 코드가 필요하다. 대부분의 MVVM 프레임워크에서는 DelegateCommand 클래스를 제공하여 간결하게 만들어준다.

  2. 인스턴스화된 명령을 ViewModel 특성에 할당해야 한다.

  3. Click 이벤트만 명령을 트리거한다. MouseOver 같은 다른 이벤트를 처리하려면 메소드를 사용하는 것과 비슷한 모양을 갖는 자세한 XAML을 사용해야 한다.

<Button>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <ei:InvokeCommandAction Command="{Binding LoadMoreCommand}">
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>


메소드 방법

메소드 방법을 사용하면 ViewModel이 쉬워지고 XAML이 더 복잡해진다.

이러한 복잡성에 대한 모든 고려 사항은 MVVM 프레임워크나 몇 가지 확장을 사용해 완화할 수 있다.

프로젝트 참조에 다음과 같은 파일을 추가한다.

  • System.Windows.Interactivity.dll
  • Microsoft.Expression.Interactions.dll

https://blog.naver.com/goldrushing/221316103092


그런다음 루트 요소에 다음과 같은 특성을 추가한다.

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

이제 마지막으로 컨트롤 및 이벤트에 대한 트리거를 추가한다.

<Button>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadMoreAction"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>


위의 코드는 Blend를 사용해서 쉽게 생성할 수 있다.

DataContext로 사용할 ViewModel을 정의한 경우 공용 메소드가 ‘데이터 컨텍스트’ 창에 나타나고 이를 컨트롤로 끌어다 놓기만 하면 된다.
또한 이 트릭은 일반 속성 바인딩에서도 동작한다.

위의 코드는 간단하다. 이벤트 이름과 메소드 이름만 기술하면 된다. TargetObject가 현재 DataContext인 ViewModel 이므로 이벤트와 메소드는 모두 ViewModel에서 호출된다.

권장 단계(완성))

과정

  1. ViewModel 생성
  2. ViewModel이 공개해야 하는 속성과 메소드를 찾는다.
  3. 알림 속성을 선언하고 공용 메소드를 추가한다.
  4. ViewModel을 View의 DataContext로 사용한다.
  5. View를 ViewModel에 데이터 바인딩한다.
  6. View에 ViewModel 메소드를 호출하는 트리거를 추가한다.
  7. 기능적 논리를 코딩한다 (3단계 이후~).


MVVM 프레임워크

MVVM 패턴을 구현하려면 다음과 같은 과정들이 필요하다.

  • 알림 속성 코딩 및 INotifyPropertyChange 구현
  • 메소드를 호출하는 트리거 XAML 작성
  • ViewModel 인스턴스화, View에 할당


다양한 MVVM 프레임워크에서는 다음과 같은 기능들을 제공하고 있다.

MVVM 프레임워크 기능

  • INotifyPropertyChanged를 구현하는 ViewModel을 생성하기 위해 ViewModelBase 클래스 상속
  • 오류를 제한하고 작업자 스레드에 알림이 발생하지 않게 속성 변경 알림
  • 명령을 사용하기로 결정해야 하지만, 명령을 쉽게 작성하기 위한 DelegateCommand 클래스 사용


대표적인 MVVM 프레임워크

  • Prism: Microsoft Patterns and Practices 팀에서 제작했고, 모듈식 애플맄케이션 설계에 대한 자세한 가이드와 로깅, 라우팅 또는 종속성 주입에 유용한 많은 도구를 제공
  • MVVM Light: 비주얼 스튜디오 템플릿을 제공
  • Caliburn.Micro: 대부분의 XAML 플랫폼에서 사용할 수 있으며, 규약을 따르면 코드가 간단해진다.

  • 참고자료: WPF MVVM 일주일 만에 배우기 - XAML, C#, MVVM 패턴 (에이콘)

태그: , ,

카테고리:

업데이트:

댓글남기기