prosource

WPF의 MVVM - View Model에 모델 변경 사항을 경고하는 방법...아니면 그럴까요?

probook 2023. 5. 13. 10:19
반응형

WPF의 MVVM - View Model에 모델 변경 사항을 경고하는 방법...아니면 그럴까요?

저는 MVVM 기사를 몇 가지 검토하고 있습니다. 주로 이것과 이것입니다.

제 구체적인 질문은 다음과 같습니다.모델 변경 사항을 모델에서 뷰 모델로 전달하려면 어떻게 해야 합니까?

조쉬의 기사에서, 저는 그가 이것을 하는 것을 보지 못했습니다.뷰 모델은 항상 모델에게 속성을 묻습니다.Rachel의 예에서, 그녀는 모델 구현을 가지고 있습니다.INotifyPropertyChanged및 모델에서 이벤트를 발생시키지만 뷰 자체에 의해 소비됩니다(그녀가 이 작업을 수행하는 이유에 대한 자세한 내용은 그녀의 기사/코드 참조).

모델이 View Model에 모델 속성 변경 사항을 경고하는 예를 볼 수 없습니다.이것은 아마도 어떤 이유로 끝나지 않았나 걱정이 됩니다.View Model에 모델 변경 사항을 알리는 패턴이 있습니까?(1) 각 모델에 대해 하나 이상의 View Model이 있을 수 있으며, (2) View Model이 하나만 있더라도 모델에 대한 일부 작업으로 인해 다른 속성이 변경될 수 있기 때문에 필요한 것 같습니다.

"왜 그러려고 합니까?"라는 양식의 답변/댓글이 있을 수도 있을 것 같습니다. 그래서 여기 제 프로그램에 대한 설명이 있습니다.저는 MVVM이 처음이라 전체 디자인에 결함이 있는 것 같습니다.간단히 설명하겠습니다.

저는 "고객" 또는 "제품" 수업보다 더 흥미로운 것을 프로그래밍하고 있습니다.나는 블랙잭을 프로그래밍하고 있습니다.

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

나쁘든, 저는 이 좋든나쁘든, 저모다같클포은않취다한태했니습도를아다는과 같은 만 포함해서는 안 .PlayingCard,Deck 만또한지하.BlackJackGame전체 게임의 상태를 유지하고 플레이어가 언제 파산했는지, 딜러가 카드를 추첨해야 하는지, 플레이어와 딜러의 현재 점수(21, 21, 파산 등)를 파악하는 클래스.

BlackJackGame는 "와 같은 방법을 때 "DrawCard와 같은 이 있다는 .CardScore,그리고.IsBust업데이트해야 하며 이러한 새 값이 View Model에 전달되어야 합니다.어쩌면 그것이 잘못된 생각일까요?

이 View Model이라고 수 .DrawCard()업데이트된 점수를 요청하고 자신이 파산했는지 여부를 확인하는 방법을 알아야 합니다.의견?

내 View Model에서는 플레이 카드의 실제 이미지(정장, 순위 기준)를 파악하여 보기에 사용할 수 있도록 하는 논리를 가지고 있습니다.모델은 이와 관련되지 않아야 합니다(아마도 다른 View 모델에서는 카드 이미지를 재생하는 대신 숫자만 사용할 것입니다).물론, 일부 사람들은 모델이 블랙잭 게임의 개념조차 가지고 있어서는 안 되며 뷰 모델에서 처리되어야 한다고 말할 것입니다.

모델이 View 모델에 변경 사항을 알리도록 하려면 해당 모델에서 INOTIFY를 구현해야 합니다.속성이 변경되었으며 View 모델이 등록하여 속성 변경 알림을 받아야 합니다.

코드는 다음과 같습니다.

// 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와 같은 할 수 .EventAggregatorLight's 는또 MVVM Light'sMessenger.

블로그에는 메시징 시스템에 대한 간략한 개요가 있지만 요약하자면, 어떤 개체든 메시지를 브로드캐스트할 수 있고 어떤 개체든 특정 메시지를 수신하도록 구독할 수 있습니다.그래서 당신은 방송을 할지도 모릅니다.PlayerScoreHasChangedMessage수 .PlayerScore재산.

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

이상적인 MVVM 환경에서 애플리케이션은 View 모델로 구성되며 모델은 애플리케이션을 구축하는 데 사용되는 블록에 불과합니다.데이터만 와 같은 방법은 사용하지 않습니다.DrawCard() Model에 View 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;
        }
    }
}

View Model 개체는 다음과 같습니다.

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. 뷰 모델이 생성되고 모델이 랩됩니다.
  2. "View model"을 합니다.PropertyChanged
  3. model은의 뷰모이모설정으로 .DataContext 등입니다.
  4. 뷰 모델에 대한 뷰 트리거 작업
  5. 모델에서 모델 호출 방법 보기
  6. 모델 업데이트 자체
  7. 뷰 모델은 모델의 작업을 처리합니다.PropertyChanged그리고 그 자신을 키웁니다.PropertyChanged
  8. 보기는 바인딩의 변경 사항을 반영하여 피드백 루프를 닫습니다.

반면에 모델에 비즈니스 로직이 거의(또는 전혀) 포함되어 있지 않거나 다른 이유(예: 트랜잭션 기능 획득)로 인해 각 뷰 모델이 랩된 모델을 "소유"하도록 결정한 경우 모델에 대한 모든 수정사항이 뷰 모델을 통과하므로 이러한 정렬이 필요하지 않습니다.

저는 이러한 설계를 다른 MVVM 질문에서 설명합니다.

선택 항목:

  • 정보 알림 구현속성 변경됨
  • 이벤트
  • 프록시 조작기가 있는 POCO

내가 보기엔INotifyPropertyChanged는 .의기인부다니분의입니다.그물. 즉, 안에 있습니다.System.dll이를 "모델"에 구현하는 것은 이벤트 구조를 구현하는 것과 비슷합니다.

순수 POCO를 사용하려면 프록시/서비스를 통해 객체를 효과적으로 조작해야 하며, 그러면 View 모델이 프록시를 청취하여 변경 사항을 통보받습니다.

개인적으로 저는 정보 알림을 느슨하게 구현할 뿐입니다.속성이 변경된 다음 FODY를 사용하여 더러운 작업을 수행합니다.그것은 POCO를 보고 느낍니다.

예(FOODY를 사용하여 변경된 특성 상승기를 IL로 엮음):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

그런 다음 View Model에서 변경된 속성이나 속성별 변경 내용을 청취할 수 있습니다.

정보통신의 아름다움PropertyChanged 경로입니다. 확장된 관측 가능한 컬렉션으로 연결하시겠습니까?그래서 당신은 당신의 가까운 포코 물건들을 수집품에 버리고, 수집품들을 듣습니다.만약 어떤 것이 바뀌면, 어디서든, 당신은 그것에 대해 배웁니다.

솔직히 말해서, 이것은 "왜 알림을 받지 않았는가"에 참여할 수 있습니다.컴파일러" 토론에서 자동으로 처리되는 속성 변경은 다음과 같습니다. c#의 모든 개체는 변경된 부분이 있는지 알려주는 기능을 가져야 합니다. 즉, INOTify 구현속성이 기본적으로 변경되었습니다.그러나 그렇게 하지 않으며, 가장 적은 노력이 필요한 최선의 방법은 IL 위빙(특히 FODY)을 사용하는 것입니다.

꽤 오래된 스레드이지만 많은 검색 끝에 저는 저만의 해결책을 생각해냈습니다: A PropertyChangedProxy

이 클래스를 사용하면 다른 사용자의 알림에 쉽게 등록할 수 있습니다.등록된 속성에 대해 실행된 경우 속성이 변경되고 적절한 작업을 수행합니다.

다음은 모델 속성 "Status"가 자체적으로 변경될 수 있는 경우 뷰 모델의 "Status" 속성을 자동으로 실행하도록 View 모델에 통보하여 뷰에도 알림이 표시되도록 하는 방법의 샘플입니다. :)

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는 재사용할 수 있어야 합니다.게임 로직이 뷰 모델에 묶여 있으면 뷰 모델을 다시 프로그래밍하지 않고는 이러한 요소를 재사용하기가 매우 어려울 것입니다.사용자 인터페이스를 변경하려면 어떻게 해야 합니까?게임 로직이 뷰 모델 로직에 연결되어 있으면 게임이 계속 작동하는지 다시 확인해야 합니다.데스크톱과 웹 앱을 만들고 싶다면 어떻게 하시겠습니까?뷰 모델에 게임 로직이 포함되어 있는 경우, 애플리케이션 로직이 뷰 모델의 비즈니스 로직과 필연적으로 연결되기 때문에 이 두 애플리케이션을 나란히 유지하는 것이 복잡해질 수 있습니다.

데이터 변경 알림 및 데이터 검증은 모든 계층(뷰, 뷰 모델 및 모델)에서 수행됩니다.

모델에는 데이터 표현(엔티티)과 해당 엔티티에 대한 비즈니스 로직이 포함됩니다.카드 한 벌은 고유한 속성을 가진 논리적인 '물건'입니다.좋은 덱은 중복된 카드를 넣을 수 없습니다.상단 카드를 얻을 수 있는 방법을 노출해야 합니다.남은 것보다 더 많은 카드를 주지 말아야 한다는 것을 알아야 합니다.이러한 데크 동작은 카드 데크에 고유하기 때문에 모델의 일부입니다.딜러 모델, 플레이어 모델, 핸드 모델 등도 있습니다.이러한 모델은 상호 작용할 수 있고 상호 작용할 것입니다.

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

기사의 핵심 내용:

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

보기는 프레젠테이션 계층으로, 실제로 사용자와 직접 연결되는 모든 것입니다.

View Model은 기본적으로 두 가지를 연결하는 응용 프로그램에 고유한 "글루"입니다.

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

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

당신의 경우에는 - 몇 가지 세부 사항을...

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

DB에 쓰기, 이메일 보내기 등과 같이 "보기가 없는 백그라운드 작업":이것은 제 다이어그램에 있는 "도메인별 작업"의 일부이며, 실제로는 순전히 모델의 일부입니다.이것이 응용 프로그램을 통해 노출하려는 내용입니다.View Model은 이 정보를 노출하는 브리지 역할을 하지만 작업은 순수 모델입니다.

View 모델에 대한 작업:View 모델에는 INPC 이상의 기능이 필요합니다. 기본 설정 및 사용자 상태 저장 등 비즈니스 로직이 아닌 애플리케이션 관련 작업도 필요합니다.이는 동일한 "모델"을 인터페이스하는 경우에도 앱에 따라 달라질 것입니다.

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

주문 자체(이메일 보내기, DB 입력 등)를 처리하는 공유 논리가 Model입니다.응용 프로그램은 이러한 작업과 데이터를 사용자에게 노출하지만 두 가지 방법으로 수행합니다.

WPF 응용 프로그램에서 사용자 인터페이스( 뷰어가 상호 작용하는 것)는 "보기"입니다. 웹 응용 프로그램에서는 기본적으로 (최소한 최종적으로) 클라이언트에서 javascript + html + css로 변환되는 코드입니다.

View Model은 사용 중인 특정 뷰 기술/레이어와 함께 작동하기 위해 모델(주문 관련 작업)을 조정하는 데 필요한 나머지 "글루"입니다.

2008년 MVVM 기사Flow of Changes 섹션에서 볼 수 있듯이, 저는 오랫동안 방향성 모델 -> View Model -> 변화의 흐름을 옹호해 왔습니다.이를 위해서는INotifyPropertyChanged 그것은 그 관행이 .제가 아는 한, 그것은 그 이후로 일반적인 관행이 되었습니다.

Josh Smith를 언급하셨기 때문에, 의 PropertyChanged 클래스를 살펴보세요.모델의 제품을 구독할 수 있는 도우미 클래스입니다.INotifyPropertyChanged.PropertyChanged이벤트

제가 최근에 속성을 만들어 보았듯이, 당신은 실제로 이 접근 방식을 훨씬 더 발전시킬 수 있습니다.업데이터 클래스.뷰 모델의 속성은 모델의 속성을 하나 이상 포함하는 복잡한 식으로 계산됩니다.

INOTIFY에 기반한 알림PropertyChangedInNotifyCollectionChanged가 바로 필요한 항목입니다.속성 변경 구독, 속성 이름 컴파일 시간 검증, 메모리 누수 방지를 통해 삶을 단순화하려면 Josh Smith의 MVVM FoundationPropertyObserver를 사용하는 것이 좋습니다.이 프로젝트는 오픈 소스이므로 소스에서 해당 클래스만 프로젝트에 추가할 수 있습니다.

PropertyObserver를 사용하는 방법에 대해서는 이 문서를 참조하십시오.

또한 RX(Ractive Extensions)에 대해서도 자세히 알아봅니다.IObserver를 노출할 수 있습니다.모델에서 시작하여 뷰 모델에서 구독합니다.

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

예를 들어, 저는 최근에 트리 뷰를 생성해야 하는 프로젝트에서 작업했습니다(자연스럽게 모델에는 계층적 특성이 있음).는 모에서저관가수찰집가있다었니습지고을품능한델는able▁calledlectioncol다니라는 관찰 가능한 컬렉션을 .ChildElements.

하고, " "를 구독했습니다.CollectionChanged집합의 다음과 .ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

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

그들은 이것에 대답하는 것을 매우 잘했지만, 이러한 상황에서 저는 MVVM 패턴이 정말로 고통스럽다고 생각하기 때문에, 저는 감독 컨트롤러 또는 패시브 뷰 접근 방식을 사용하여 스스로 변경을 생성하는 모델 개체에 대해 적어도 바인딩 시스템에서 손을 떼곤 했습니다.

INOTIFY를 구현하는 것은 잘못된 것이 아닙니다.모델 내부에서 속성이 변경되고 뷰 모델 내부에서 속성을 듣습니다.실제로 XAML: {Binding Model}에서 모델의 속성을 바로 볼 수도 있습니다.모델 속성}

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

이것은 제가 보기에 정말 중요한 질문으로 보입니다. 심지어 그것을 해야 한다는 압박감이 없을 때도 그렇습니다.저는 트리뷰와 관련된 테스트 프로젝트를 진행하고 있습니다.명령에 매핑된 메뉴 항목 등이 있습니다(예: Delete).현재 뷰 모델 내에서 모델과 뷰 모델을 모두 업데이트하고 있습니다.

예를들면,

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와 같은 기술을 사용하여 모델 업데이트가 뷰 모델 업데이트를 트리거하도록 하는 것이 좋습니다.이제 두 가지 작업이 모두 성공한 경우에만 동일한 장치 테스트가 작동합니다.

이것이 잠재적인 해답은 아니라는 것을 저는 알고 있지만, 그것을 제시할 가치가 있는 것처럼 보입니다.

언급URL : https://stackoverflow.com/questions/15439841/mvvm-in-wpf-how-to-alert-viewmodel-of-changes-in-model-or-should-i

반응형