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


112

나는 주로 이것이것같은 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에서 처리해야한다고 말할 것입니다.


3
설명하는 상호 작용은 표준 이벤트 메커니즘처럼 들립니다. 필요한 모든 것입니다. 모델은라는 이벤트를 노출 할 수 OnBust있으며 VM은이를 구독 할 수 있습니다. IEA 접근 방식도 사용할 수 있다고 생각합니다.
code4life 2013 년

솔직히 말하자면, 진짜 블랙 잭 '앱'을 만들려면 내 데이터가 몇 계층의 서비스 / 프록시와 A + B = C와 유사한 단위 테스트의 현학적 인 수준 뒤에 숨겨져있을 것입니다. 그것은 프록시가 될 것입니다. / 변경 사항을 알리는 서비스.
Meirion Hughes 2013

1
모두에게 감사 드려요! 안타깝게도 답은 하나만 고를 수 있습니다. 추가 아키텍처 조언과 원래 질문 정리로 인해 Rachel을 선택하고 있습니다. 하지만 훌륭한 답변이 많았고 감사합니다. -Dave
Dave


2
FWIW : 도메인 당 VM 및 M 개념을 유지하는 복잡성으로 몇 년 동안 고군분투 한 후 이제 두 가지 모두 DRY가 실패한다고 생각합니다. "도메인 인터페이스"와 "ViewModel 인터페이스"라는 단일 개체에 두 개의 인터페이스를 사용하면 필요한 문제를보다 쉽게 ​​분리 할 수 ​​있습니다. 이 개체는 혼동이나 동기화 부족없이 비즈니스 논리와보기 논리 모두에 전달할 수 있습니다. 이 개체는 "ID 개체"이며 개체를 고유하게 나타냅니다. 도메인 코드와 뷰 코드의 분리를 유지하려면 클래스 내에서 더 나은 도구가 필요합니다.
ToolmakerSteve 19

답변:


61

모델이 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하지만 단순성을 위해 생략했습니다)


3
보다 일반적으로 모든 비즈니스 로직 / 규칙이 모델에 포함됩니까? 카드를 최대 21 개까지 가져갈 수 있다는 논리 (하지만 딜러는 17 개에 남아 있음), 카드를 분할 할 수 있다는 논리는 어디로 갑니까? 나는 모든 것이 모델 클래스에 속한다고 생각했고 그 이유 때문에 필요하다고 느꼈습니다. 모델의 BlacJackGame 컨트롤러 클래스 나는 여전히 이것을 이해하려고 노력하고 있으며 예제 / 참조에 감사하겠습니다. 예를 들어 블랙 잭에 대한 아이디어는 비즈니스 로직 / 규칙이 MVC 패턴의 모델 클래스에있는 iOS 프로그래밍의 iTunes 클래스에서 비롯되었습니다.
Dave

3
@Dave 예, DrawCard()메서드는 다른 게임 로직과 함께 ViewModel에 있습니다. 이상적인 MVVM 애플리케이션에서는 ViewModel을 만들고 테스트 스크립트 나 명령 프롬프트 창을 통해 메서드를 실행하는 것만으로 UI없이 애플리케이션을 실행할 수 있어야합니다. 모델은 일반적으로 원시 데이터 및 기본 데이터 유효성 검사를 포함하는 유일한 데이터 모델입니다.
레이첼

6
모든 도움을 주신 Rachel에게 감사드립니다. 나는 이것을 좀 더 조사하거나 다른 질문을 써야 할 것입니다; 나는 여전히 게임 로직의 위치에 대해 혼란 스럽습니다. 당신과 다른 사람들은 그것을 ViewModel에 넣는 것을 옹호하고, 다른 사람들은 "비즈니스 로직"이라고 말합니다. 제 경우에는 게임 규칙과 게임 상태가 모델에 속한다고 가정합니다 (예 : msdn.microsoft.com/en-us 참조). /library/gg405484%28v=pandp.40%29.aspx ) 및 stackoverflow.com/questions/10964003/… ). 나는이 간단한 게임에서 아마 그다지 중요하지 않다는 것을 알고 있습니다. 그러나 알고 있으면 좋을 것입니다. Thxs!
Dave

1
@Dave 나는 "비즈니스 로직"이라는 용어를 잘못 사용하여 응용 프로그램 로직과 혼합하고있을 수 있습니다. 링크 된 MSDN 기사를 인용하기 위해 "재사용 기회를 극대화하기 위해 모델에는 사용 사례 별 또는 사용자 작업 별 동작 또는 응용 프로그램 논리가 포함되어서는 안됩니다.""일반적으로보기 모델은 나타낼 수있는 명령 또는 동작을 정의합니다. UI에서 사용자가 호출 할 수 있습니다 " . 그래서 a와 같은 DrawCardCommand()것이 ViewModel에있을 것입니다.하지만 여러분이 원한다면 명령이 호출 BlackjackGameModelDrawCard()메소드를 포함하는 객체를 가질 수있을 것 같습니다
Rachel

2
메모리 누수를 피하십시오. WeakEvent 패턴을 사용하십시오. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

24

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

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

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

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

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


안녕하세요, 당신이 만든 목록은 훌륭합니다. 그러나 7과 8에 문제가 있습니다. 특히 : INotifyPropertyChanged를 구현하지 않는 ViewModel이 있습니다. 여기에는 자식 목록 자체가 포함 된 자식 목록이 포함됩니다 (WPF Treeview 컨트롤의 ViewModel로 사용됨). UserControl DataContext ViewModel이 자식 (TreeviewItems)의 속성 변경을 "수신"하도록하려면 어떻게해야합니까? INotifyPropertyChanged를 구현하는 모든 자식 요소를 정확히 어떻게 구독합니까? 아니면 별도의 질문을해야합니까?
Igor

4

귀하의 선택 :

  • 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 )을 사용하는 것입니다.


4

상당히 오래된 스레드이지만 많은 검색 끝에 내 자신의 솔루션을 찾았습니다. 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));
        }
    }
}

1
메모리 누수를 피하십시오. WeakEvent 패턴을 사용하십시오. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS

1
@JJS-OTOH, The Weak Event Pattern Is Dangerous를 고려하십시오 . 개인적으로 등록 취소 ( -= my_event_handler)를 잊어 버리면 메모리 누수 위험이 있습니다 . 왜냐하면 발생하지 않을 수도있는 희귀하고 예측할 수없는 좀비 문제보다 추적하기 쉽기 때문입니다.
ToolmakerSteve

@ToolmakerSteve 균형 잡힌 인수를 추가해 주셔서 감사합니다. 개발자가 자신의 상황에서 최선을 다할 것을 제안합니다. 맹목적으로 인터넷에서 소스 코드를 채택하지 마십시오. EventAggregator / EventBus와 같은 다른 패턴이 있습니다. 일반적으로 사용되는 교차 구성 요소 메시징 (또한 자체 위험으로 처리됨)
JJS

2

이 기사가 도움이되었다고 생각합니다. 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은 사용중인 특정 뷰 기술 / 레이어에서 작동하도록 모델 (주문과 관련된 이러한 작업)을 조정하는 데 필요한 나머지 "접착제"입니다.


간단한 예로 음악 플레이어가 있습니다. 모델에는 라이브러리와 활성 사운드 파일, 코덱, 플레이어 로직 및 디지털 신호 처리 코드가 포함됩니다. 뷰 모델에는 컨트롤과 시각화, 라이브러리 브라우저가 포함됩니다. 모든 정보를 표시하는 데 필요한 많은 UI 로직이 있으며 한 프로그래머는 음악 재생에 집중하고 다른 프로그래머는 UI를 직관적이고 재미있게 만드는 데 집중할 수 있으면 좋을 것입니다. 뷰 모델과 모델은 두 프로그래머가 인터페이스 세트에 동의하고 별도로 작업 할 수 있도록해야합니다.
VoteCoffee

또 다른 좋은 예는 웹 페이지입니다. 서버 측 로직은 일반적으로 모델과 동일합니다. 클라이언트 측 로직은 일반적으로 뷰 모델과 동일합니다. 게임 로직이 서버에 속하고 클라이언트에게 맡겨지지 않을 것이라고 쉽게 상상할 수 있습니다.
VoteCoffee

2

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

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

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


Josh Smith의 훌륭한 기사를 참조하고 Weak Events를 다루어 주셔서 대단히 감사합니다!
JJS

1

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


1

나는 2008 년부터 MVVM 기사 의 변경 흐름 섹션 에서 볼 수 있듯이 방향 모델-> 모델보기-> 변경 흐름보기를 오랫동안 옹호 해 왔습니다 .이를 위해서는 모델에서 구현해야합니다 . 내가 말할 수있는 한, 그것은 그 이후로 일반적인 관행이되었습니다.INotifyPropertyChanged

Josh Smith를 언급 했으므로 그의 PropertyChanged 클래스를 살펴보십시오 . 모델의 INotifyPropertyChanged.PropertyChanged이벤트 를 구독하기위한 도우미 클래스입니다 .

최근에 PropertiesUpdater 클래스 를 생성하여이 접근 방식을 훨씬 더 발전시킬 수 있습니다 . 뷰 모델의 속성은 모델에 대한 하나 이상의 속성을 포함하는 복잡한 식으로 계산됩니다.


1

Model 내부에서 INotifyPropertyChanged 를 구현 하고 ViewModel 내부에서 듣는 것은 잘못된 것이 아닙니다 . 실제로 XAML에서 바로 모델의 속성에 점을 찍을 수도 있습니다. {Binding Model.ModelProperty}

종속 / 계산 된 읽기 전용 속성에 대해서는 지금까지 https://github.com/StephenCleary/CalculatedProperties 보다 더 좋고 간단한 것을 보지 못했습니다 . 매우 간단하지만 매우 유용합니다. 정말 "MVVM 용 Excel 수식"입니다. Excel에서 추가 노력없이 수식 셀에 변경 사항을 전파하는 것과 동일한 방식으로 작동합니다.


0

뷰 모델이 구독해야하는 모델에서 이벤트를 발생시킬 수 있습니다.

예를 들어, 저는 최근에 트 리뷰를 생성해야하는 프로젝트에서 작업했습니다 (당연히 모델에는 계층 적 특성이 있음). 모델에는이라는 observablecollection이 ChildElements있습니다.

뷰 모델에서 모델의 객체에 대한 참조를 저장하고 CollectionChanged다음과 같이 observablecollection 의 이벤트를 구독했습니다 . ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

그런 다음 모델에서 변경이 발생하면 뷰 모델이 자동으로 알림을받습니다. 를 사용하여 동일한 개념을 따를 수 PropertyChanged있지만 작동하려면 모델에서 속성 변경 이벤트를 명시 적으로 발생시켜야합니다.


계층 적 데이터를 다루는 경우 MVVM 기사데모 2 를보고 싶을 것 입니다.
HappyNomad 2013 년

0

이것은 저에게 정말 중요한 질문 인 것 같습니다. 저는 TreeView와 관련된 테스트 프로젝트를 진행하고 있습니다. 명령에 매핑되는 메뉴 항목 등이 있습니다 (예 : 삭제). 현재 저는 뷰 모델 내에서 모델과 뷰 모델을 모두 업데이트하고 있습니다.

예를 들면

public void DeleteItemExecute ()
{
    DesignObjectViewModel node = this.SelectedNode;    // Action is on selected item
    DocStructureManagement.DeleteNode(node.DesignObject); // Remove from application
    node.Remove();                                // Remove from view model
    Controller.UpdateDocument();                  // Signal document has changed
}

이것은 간단하지만 매우 기본적인 결함이있는 것 같습니다. 일반적인 단위 테스트는 명령을 실행 한 다음 뷰 모델에서 결과를 확인합니다. 그러나 두 모델이 동시에 업데이트되므로 모델 업데이트가 올바른지 테스트하지는 않습니다.

따라서 PropertyObserver와 같은 기술을 사용하여 모델 업데이트가 뷰 모델 업데이트를 트리거하도록하는 것이 더 좋습니다. 동일한 단위 테스트는 이제 두 작업이 모두 성공한 경우에만 작동합니다.

이것은 잠재적 인 대답이 아니라는 것을 알고 있지만 거기에 공개 할 가치가있는 것 같습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.