[c#] WPF의 MVVM-ViewModel에 모델의 변경 사항을 알리는 방법…

나는 주로 이것이것같은 MVVM 기사를 살펴볼 것 입니다.

내 구체적인 질문은 : 모델에서 ViewModel로 모델 변경 사항을 어떻게 전달합니까?

Josh의 기사에서 나는 그가 이것을하는 것을 보지 않는다. ViewModel은 항상 모델에 속성을 요청합니다. Rachel의 예에서 그녀는 모델을 구현하고 모델 INotifyPropertyChanged에서 이벤트를 발생 시키지만 뷰 자체에서 소비하기위한 것입니다 (그녀가이 작업을 수행하는 이유에 대한 자세한 내용은 기사 / 코드 참조).

모델이 ViewModel에 모델 속성의 변경 사항을 알리는 예제는 어디에도 없습니다. 이로 인해 어떤 이유로 든 완료되지 않았을까 걱정됩니다. 모델의 변경 사항을 ViewModel에 경고하는 패턴이 있습니까? (1) 각 모델에 대해 하나 이상의 ViewModel이 있고 (2) ViewModel이 하나만 있어도 모델에 대한 일부 작업으로 인해 다른 속성이 변경 될 수 있으므로 필요합니다.

“왜 그렇게 하시겠습니까?”라는 형식의 답변 / 댓글이있을 수 있습니다. 여기에 내 프로그램에 대한 설명이 있습니다. 저는 MVVM을 처음 사용하므로 전체 디자인이 잘못되었을 수 있습니다. 간단히 설명하겠습니다.

나는 “고객”이나 “제품”수업보다 더 흥미로운 것을 (적어도 나에게는!) 프로그래밍하고있다. BlackJack을 프로그래밍하고 있습니다.

뒤에 코드가없고 ViewModel의 속성 및 명령에 대한 바인딩에만 의존하는 View가 있습니다 (Josh Smith의 기사 참조).

좋든 나쁘 든, 나는 모델이 PlayingCard, 같은 클래스 Deck뿐만 아니라 BlackJackGame전체 게임의 상태를 유지 하는 클래스를 포함해야한다는 태도를 취했고, 플레이어가 언제 파산했는지, 딜러가 카드를 뽑아야 하는지를 알고 있습니다. 플레이어와 딜러의 현재 점수는 무엇입니까 (21, 21 미만, 버스트 등).

에서 BlackJackGame나는 “DrawCard”와 같은 방법을 노출하고 나에게 발생한 카드는 다음과 같은 속성을 그려 때 CardScore, 그리고 IsBust업데이트해야하고이 새로운 값은 뷰 모델에 전달. 아마도 잘못된 생각일까요?

ViewModel이 DrawCard()메서드를 호출 한 태도를 취할 수 있으므로 업데이트 된 점수를 요청하고 그가 파산되었는지 여부를 알아 내야합니다. 의견?

내 ViewModel에는 카드 게임의 실제 이미지 (정장, 순위 기반)를 가져 와서보기에 사용할 수 있도록 만드는 논리가 있습니다. 모델은 이것에 관심이 없어야합니다 (아마 다른 ViewModel은 카드 이미지 대신 숫자를 사용합니다). 물론 어떤 사람들은 Model이 BlackJack 게임의 개념을 가져서는 안되며 ViewModel에서 처리해야한다고 말할 것입니다.



답변

모델이 ViewModels에 변경 사항을 알리도록하려면 INotifyPropertyChanged 를 구현 해야하며 ViewModels는 PropertyChange 알림을 수신하도록 구독해야합니다.

코드는 다음과 같습니다.

// Attach EventHandler
PlayerModel.PropertyChanged += PlayerModel_PropertyChanged;

...

// When property gets changed in the Model, raise the PropertyChanged 
// event of the ViewModel copy of the property
PlayerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SomeProperty")
        RaisePropertyChanged("ViewModelCopyOfSomeProperty");
}

그러나 일반적으로 이것은 둘 이상의 객체가 모델의 데이터를 변경하는 경우에만 필요하며 일반적으로 그렇지 않습니다.

PropertyChanged 이벤트를 첨부하기 위해 Model 속성에 대한 참조가 실제로없는 경우가 있다면 Prism EventAggregator또는 MVVM Light 와 같은 메시징 시스템을 사용할 수 있습니다 Messenger.

나는이 메시징 시스템의 간략한 개요 그러나 모든 객체가 메시지를 방송 할 수있다, 그것을 요약하고, 모든 개체는 특정 메시지를 듣고 구독 할 수 있습니다, 내 블로그에 있습니다. 따라서 PlayerScoreHasChangedMessage한 객체에서를 브로드 캐스트 할 수 있고 다른 객체는 이러한 유형의 메시지를 수신하고 수신 PlayerScore할 때 해당 속성을 업데이트하도록 구독 할 수 있습니다 .

그러나 나는 이것이 당신이 설명한 시스템에 필요하다고 생각하지 않습니다.

이상적인 MVVM 세계에서 애플리케이션은 ViewModel로 구성되고 모델은 애플리케이션을 빌드하는 데 사용되는 블록 일뿐입니다. 일반적으로 데이터 만 포함하므로 DrawCard()(ViewModel에 있음) 과 같은 메서드가 없습니다.

따라서 다음과 같은 일반 Model 데이터 개체가있을 수 있습니다.

class CardModel
{
    int Score;
    SuitEnum Suit;
    CardEnum CardValue;
}

class PlayerModel
{
    ObservableCollection<Card> FaceUpCards;
    ObservableCollection<Card> FaceDownCards;
    int CurrentScore;

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}

그리고 다음과 같은 ViewModel 개체가 있습니다.

public class GameViewModel
{
    ObservableCollection<CardModel> Deck;
    PlayerModel Dealer;
    PlayerModel Player;

    ICommand DrawCardCommand;

    void DrawCard(Player currentPlayer)
    {
        var nextCard = Deck.First();
        currentPlayer.FaceUpCards.Add(nextCard);

        if (currentPlayer.IsBust)
            // Process next player turn

        Deck.Remove(nextCard);
    }
}

(위의 객체는 모두를 구현해야 INotifyPropertyChanged하지만 단순성을 위해 생략했습니다)


답변

짧은 대답 : 세부 사항에 따라 다릅니다.

귀하의 예에서 모델은 “자체적으로”업데이트되고 이러한 변경 사항은 물론 어떻게 든 뷰에 전파되어야합니다. 뷰는 뷰 모델에만 직접 액세스 할 수 있으므로 모델은 이러한 변경 사항을 해당 뷰 모델에 전달해야합니다. 그렇게하기위한 확립 된 메커니즘은 물론 INotifyPropertyChanged입니다. 즉, 다음과 같은 워크 플로를 얻게됩니다.

  1. Viewmodel이 생성되고 모델을 래핑합니다.
  2. Viewmodel은 모델의 PropertyChanged이벤트를 구독합니다.
  3. Viewmodel은 뷰의 DataContext, 속성이 바인딩 됨 등으로 설정됩니다.
  4. viewmodel에서보기 트리거 작업
  5. 모델의 Viewmodel 호출 메서드
  6. 모델 자체 업데이트
  7. Viewmodel은 모델을 처리 하고 이에 대한 응답 PropertyChanged으로 자체 PropertyChanged를 올립니다.
  8. 보기는 바인딩의 변경 사항을 반영하여 피드백 루프를 닫습니다.

반면에 모델에 비즈니스 로직이 거의 또는 전혀 포함되지 않았거나 다른 이유로 (트랜잭션 기능 획득과 같은) 각 뷰 모델이 래핑 된 모델을 “소유”하도록 결정한 경우 모델에 대한 모든 수정 사항이 전달됩니다. 그런 배열이 필요하지 않을 것입니다.

여기 에 또 다른 MVVM 질문에서 그러한 디자인을 설명 합니다 .


답변

귀하의 선택 :

  • INotifyPropertyChanged 구현
  • 이벤트
  • 프록시 조작기가있는 POCO

내가보기 INotifyPropertyChanged에 .Net의 기본 부분입니다. 즉 System.dll. “모델”에서 구현하는 것은 이벤트 구조를 구현하는 것과 유사합니다.

순수한 POCO를 원한다면 프록시 / 서비스를 통해 객체를 효과적으로 조작해야하며, 그러면 ViewModel이 프록시를 수신하여 변경 사항을 알립니다.

개인적으로 나는 INotifyPropertyChanged를 느슨하게 구현 한 다음 FODY 를 사용 하여 더러운 작업을 수행합니다. POCO의 외모와 느낌입니다.

예 (FODY를 사용하여 IL Weave the PropertyChanged 레이저) :

public class NearlyPOCO: INotifyPropertyChanged
{
     public string ValueA {get;set;}
     public string ValueB {get;set;}

     public event PropertyChangedEventHandler PropertyChanged;
}

그런 다음 ViewModel이 변경 사항에 대해 PropertyChanged를 수신하도록 할 수 있습니다. 또는 속성 특정 변경.

INotifyPropertyChanged 경로의 장점은 Extended ObservableCollection으로 연결하는 것입니다 . 따라서 가까운 poco 개체를 컬렉션에 버리고 컬렉션을 듣습니다. 변경 사항이 있으면 어디서나 알게됩니다.

솔직히 말해서 “컴파일러에 의해 INotifyPropertyChanged가 자동으로 처리되지 않은 이유”토론에 참여할 수 있습니다.이 토론은 다음과 같습니다. C #의 모든 개체에는 변경된 부분이 있으면이를 알리는 기능이 있어야합니다. 즉, 기본적으로 INotifyPropertyChanged를 구현합니다. 그러나 가장 적은 노력이 필요한 최선의 방법은 IL Weaving (특히 FODY )을 사용하는 것입니다.


답변

상당히 오래된 스레드이지만 많은 검색 끝에 내 자신의 솔루션을 찾았습니다. PropertyChangedProxy

이 클래스를 사용하면 다른 사람의 NotifyPropertyChanged에 쉽게 등록하고 등록 된 속성에 대해 실행되는 경우 적절한 조치를 취할 수 있습니다.

다음은 자체적으로 변경 될 수있는 모델 속성 “Status”가있을 때 이것이 어떻게 보일 수 있는지에 대한 샘플입니다. 그러면 뷰에도 알림이 전송되도록 해당 “Status”속성에서 자체 PropertyChanged를 실행하도록 ViewModel에 자동으로 알려야합니다. )

public class MyModel : INotifyPropertyChanged
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set { _status = value; OnPropertyChanged(); }
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    public string Status
    {
        get { return _model.Status; }
    }

    private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
    private MyModel _model;
    public MyViewModel(MyModel model)
    {
        _model = model;
        _statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
            _model, myModel => myModel.Status, s => OnPropertyChanged("Status")
        );
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

다음은 클래스 자체입니다.

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
    private readonly Func<TSource, TPropType> _getValueFunc;
    private readonly TSource _source;
    private readonly Action<TPropType> _onPropertyChanged;
    private readonly string _modelPropertyname;

    /// <summary>
    /// Constructor for a property changed proxy
    /// </summary>
    /// <param name="source">The source object to listen for property changes</param>
    /// <param name="selectorExpression">Expression to the property of the source</param>
    /// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
    public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
    {
        _source = source;
        _onPropertyChanged = onPropertyChanged;
        // Property "getter" to get the value
        _getValueFunc = selectorExpression.Compile();
        // Name of the property
        var body = (MemberExpression)selectorExpression.Body;
        _modelPropertyname = body.Member.Name;
        // Changed event
        _source.PropertyChanged += SourcePropertyChanged;
    }

    private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _modelPropertyname)
        {
            _onPropertyChanged(_getValueFunc(_source));
        }
    }
}


답변

이 기사가 도움이되었다고 생각합니다.
http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf

내 요약 :

MVVM 조직의 기본 개념은 뷰와 모델을 더 쉽게 재사용하고 분리 된 테스트를 허용하는 것입니다. 뷰 모델은 뷰 엔터티를 나타내는 모델이고 모델은 비즈니스 엔터티를 나타냅니다.

나중에 포커 게임을 만들고 싶다면? 많은 UI를 재사용 할 수 있어야합니다. 게임 로직이 뷰 모델에 묶여 있다면 뷰 모델을 다시 프로그래밍하지 않고도 이러한 요소를 재사용하기가 매우 어려울 것입니다. 사용자 인터페이스를 변경하려면 어떻게해야합니까? 게임 로직이 뷰-모델 로직과 결합 된 경우 게임이 계속 작동하는지 다시 확인해야합니다. 데스크톱과 웹 앱을 만들고 싶다면 어떻게해야합니까? 뷰 모델에 게임 로직이 포함되어있는 경우 애플리케이션 로직이 뷰 모델의 비즈니스 로직과 불가피하게 결합 될 수 있으므로이 두 애플리케이션을 나란히 유지하는 것이 복잡해집니다.

데이터 변경 알림 및 데이터 유효성 검사는 모든 계층 (뷰, 뷰 모델 및 모델)에서 발생합니다.

모델에는 데이터 표현 (엔티티) 및 해당 엔터티와 관련된 비즈니스 논리가 포함됩니다. 카드 한 벌은 고유 한 속성을 가진 논리적 ‘사물’입니다. 좋은 덱은 중복 카드를 넣을 수 없습니다. 상단 카드를 얻을 수있는 방법을 공개해야합니다. 남은 카드보다 더 많은 카드를주지 않는 것을 알아야합니다. 이러한 덱 동작은 카드 덱에 내재되어 있기 때문에 모델의 일부입니다. 딜러 모델, 플레이어 모델, 손 모델 등도 있습니다. 이러한 모델은 상호 작용할 수 있으며 상호 작용할 것입니다.

뷰 모델은 프리젠 테이션과 애플리케이션 로직으로 구성됩니다. 게임 표시와 관련된 모든 작업은 게임의 논리와 별개입니다. 여기에는 손을 이미지로 표시, 딜러 모델에 대한 카드 요청, 사용자 디스플레이 설정 등이 포함될 수 있습니다.

기사의 핵심 :

기본적으로이를 설명하는 방법은 비즈니스 로직과 엔터티가 모델을 구성한다는 것입니다. 이것은 특정 응용 프로그램에서 사용하고 있지만 여러 응용 프로그램에서 공유 할 수 있습니다.

보기는 사용자와 실제로 직접 인터페이싱하는 것과 관련된 모든 표현 레이어입니다.

ViewModel은 기본적으로 두 가지를 함께 연결하는 응용 프로그램에 고유 한 “접착제”입니다.

인터페이스 방식을 보여주는 멋진 다이어그램이 있습니다.

http://reedcopsey.com/2010/01/06/better-user-and-developer-experiences-from-windows-forms-to-wpf-with-mvvm-part-7-mvvm/

귀하의 경우-몇 가지 세부 사항을 해결하겠습니다 …

유효성 검사 : 일반적으로 두 가지 형태로 제공됩니다. 사용자 입력과 관련된 유효성 검사는 ViewModel (주로) 및 View에서 발생합니다 (예 : 텍스트 입력을 방지하는 “Numeric”TextBox가 뷰에서 처리됩니다). 따라서 사용자 입력의 유효성 검사는 일반적으로 VM 문제입니다. 즉, 두 번째 유효성 검사 “계층”이 종종 있습니다. 이것은 사용되는 데이터가 비즈니스 규칙과 일치하는지 확인하는 것입니다. 이것은 종종 모델 자체의 일부입니다. 모델에 데이터를 푸시하면 유효성 검사 오류가 발생할 수 있습니다. 그런 다음 VM은이 정보를 View로 다시 매핑해야합니다.

작업 “DB에 쓰기, 이메일 보내기 등과 같이보기가없는 배후에서”작업 : 이것은 실제로 내 다이어그램에서 “도메인 특정 작업”의 일부이며 실제로는 모델의 일부입니다. 이것이 애플리케이션을 통해 노출하려는 것입니다. ViewModel은이 정보를 노출하는 다리 역할을하지만 작업은 순수 모델입니다.

ViewModel에 대한 작업 : ViewModel에는 INPC 이상의 것이 필요합니다. 또한 기본 설정 및 사용자 상태 저장 등과 같이 비즈니스 로직이 아닌 애플리케이션에 특정한 모든 작업이 필요합니다. 이것은 앱을 다양하게 할 것입니다. 동일한 “모델”을 인터페이스하는 경우에도 앱에 의해.

이에 대해 생각하는 좋은 방법-주문 시스템의 두 가지 버전을 만들고 싶다고 가정 해 보겠습니다. 첫 번째는 WPF에 있고 두 번째는 웹 인터페이스입니다.

주문 자체 (이메일 전송, DB 입력 등)를 처리하는 공유 로직이 모델입니다. 애플리케이션은 이러한 작업과 데이터를 사용자에게 노출하지만 두 가지 방법으로 수행합니다.

WPF 애플리케이션에서 사용자 인터페이스 (뷰어가 상호 작용하는 것)는 “뷰”입니다. 웹 애플리케이션에서는 기본적으로 클라이언트에서 javascript + html + css로 변환되는 코드입니다.

ViewModel은 사용중인 특정 뷰 기술 / 레이어에서 작동하도록 모델 (주문과 관련된 이러한 작업)을 조정하는 데 필요한 나머지 “접착제”입니다.


답변

INotifyPropertyChangedINotifyCollectionChanged를 기반으로하는 알림 은 정확히 필요한 것입니다. 속성 변경에 대한 구독, 속성 이름의 컴파일 타임 유효성 검사, 메모리 누수 방지를 통해 생활을 단순화하려면 Josh Smith의 MVVM Foundation 에서 PropertyObserver 를 사용하는 것이 좋습니다 . 이 프로젝트는 오픈 소스이므로 소스에서 프로젝트에 해당 클래스 만 추가 할 수 있습니다.

PropertyObserver를 사용하는 방법을 이해하려면 이 기사를 읽으십시오 .

또한 Rx (Reactive Extensions)를 자세히 살펴보십시오 . 모델에서 IObserver <T> 를 노출 하고 뷰 모델에서 구독 할 수 있습니다.


답변

사람들은 이것에 대해 놀라운 일을했지만 이와 같은 상황에서 MVVM 패턴이 고통스러워서 Supervising Controller 또는 Passive View 접근 방식을 사용하고 적어도 모델 객체에 대한 바인딩 시스템을 놓아 버릴 것입니다. 자체적으로 변경 사항을 생성합니다.