MVVM no WPF - Como alertar ViewModel sobre mudanças no Model ... ou devo?

112

Estou lendo alguns artigos sobre MVVM, principalmente este e este .

Minha pergunta específica é: Como comunico as alterações do Model do Model para o ViewModel?

No artigo de Josh, não vejo que ele faça isso. O ViewModel sempre pede propriedades ao Model. No exemplo de Rachel, ela tem o modelo implementado INotifyPropertyChangede gera eventos do modelo, mas eles são para consumo pela própria visualização (consulte seu artigo / código para obter mais detalhes sobre por que ela faz isso).

Em nenhum lugar vejo exemplos onde o modelo alerta o ViewModel sobre mudanças nas propriedades do modelo. Isso me preocupa, pois talvez não seja feito por algum motivo. Existe um padrão para alertar o ViewModel de mudanças no Model? Pareceria necessário, pois (1) concebivelmente, há mais de 1 ViewModel para cada modelo e (2) mesmo se houver apenas um ViewModel, alguma ação no modelo pode resultar na alteração de outras propriedades.

Suspeito que possa haver respostas / comentários do tipo "Por que você faria isso?" comentários, então aqui está uma descrição do meu programa. Eu sou novo no MVVM, então talvez todo o meu design esteja com defeito. Vou descrevê-lo brevemente.

Estou programando algo que é mais interessante (pelo menos, para mim!) Do que as classes "Cliente" ou "Produto". Estou programando BlackJack.

Eu tenho um modo de exibição que não tem nenhum código por trás e apenas depende da vinculação a propriedades e comandos no ViewModel (consulte o artigo de Josh Smith).

Para o bem ou para o mal, assumi a atitude de que o Modelo deve conter não apenas classes como PlayingCard, Deckmas também a BlackJackGameclasse que mantém o estado de todo o jogo e sabe quando o jogador foi à falência, o carteador deve tirar cartas e qual é a pontuação atual do jogador e do dealer (menos de 21, 21, rebentar, etc.).

Desde BlackJackGameI expor métodos como "drawcard" e ocorreu-me que, quando um cartão é desenhado, propriedades, tais como CardScore, e IsBustdeve ser atualizado e esses novos valores comunicados ao ViewModel. Talvez seja um pensamento errado?

Pode-se assumir a atitude de que ViewModel chamou o DrawCard()método, então ele deve saber como pedir uma pontuação atualizada e descobrir se ele está falido ou não. Opiniões?

Em meu ViewModel, tenho a lógica de pegar uma imagem real de uma carta de jogo (com base no naipe, classificação) e torná-la disponível para a visualização. O modelo não deve se preocupar com isso (talvez outro ViewModel usaria apenas números em vez de imagens de cartas). Claro, talvez alguns me digam que o Model nem deveria ter o conceito de um jogo de BlackJack e isso deveria ser tratado no ViewModel?

Dave
fonte
3
A interação que você está descrevendo soa como um mecanismo de evento padrão é tudo o que você precisa. O modelo pode expor um evento chamado OnBuste a VM pode se inscrever nele. Eu acho que você também pode usar uma abordagem IEA também.
code4life
Serei honesto, se eu quisesse fazer um verdadeiro 'aplicativo' de blackjack, meus dados ficariam escondidos atrás de algumas camadas de serviços / proxies e um nível pedante de testes de unidade semelhantes a A + B = C. Seria o proxy / serviço que informa sobre as mudanças.
Meirion Hughes
1
Obrigado a todos! Infelizmente, posso escolher apenas uma resposta. Estou escolhendo o de Rachel devido ao conselho extra de arquitetura e limpando a questão original. Mas houve muitas respostas excelentes e eu as aprecio. -Dave
Dave
2
FWIW: Depois de lutar por vários anos com as complexidades de manter VM e M por conceito de domínio, agora acredito que ter ambos falha em DRY; a necessária separação de interesses pode ser feita mais facilmente por ter duas INTERFACES em um único objeto - uma "Interface de Domínio" e uma "Interface ViewModel". Este objeto pode ser passado para a lógica de negócios e para a lógica de visualização, sem confusão ou falta de sincronização. Esse objeto é um "objeto de identidade" - ele representa exclusivamente a entidade. Manter a separação entre o código de domínio e o código de visualização precisa de ferramentas melhores para fazer isso dentro de uma classe.
Toolmaker Steve

Respostas:

61

Se você deseja que seus modelos alertem os ViewModels sobre mudanças, eles devem implementar INotifyPropertyChanged , e os ViewModels devem se inscrever para receber notificações de PropertyChange.

Seu código pode ser parecido com este:

// 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");
}

Mas normalmente isso só é necessário se mais de um objeto estiver fazendo alterações nos dados do modelo, o que geralmente não é o caso.

Se você já teve um caso em que na verdade não tem uma referência à sua propriedade Model para anexar o evento PropertyChanged a ela, então você pode usar um sistema de mensagens como o Prism EventAggregatorou MVVM Light Messenger.

Tenho uma breve visão geral dos sistemas de mensagens em meu blog, no entanto, para resumir, qualquer objeto pode transmitir uma mensagem e qualquer objeto pode se inscrever para ouvir mensagens específicas. Portanto, você pode transmitir um a PlayerScoreHasChangedMessagepartir de um objeto e outro objeto pode se inscrever para ouvir esses tipos de mensagens e atualizar sua PlayerScorepropriedade quando ouvir uma.

Mas não acho que isso seja necessário para o sistema que você descreveu.

Em um mundo MVVM ideal, seu aplicativo é composto de seus ViewModels e seus modelos são apenas os blocos usados ​​para construir seu aplicativo. Eles normalmente contêm apenas dados, portanto, não teriam métodos como DrawCard()(isso estaria em um ViewModel)

Portanto, você provavelmente teria objetos de dados Model simples como estes:

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

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

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

e você teria um objeto ViewModel como

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);
    }
}

(Todos os objetos acima devem ser implementados INotifyPropertyChanged, mas deixei de fora para simplificar)

Rachel
fonte
3
De maneira mais geral, todas as lógicas / regras de negócios vão no modelo? Para onde vai toda a lógica que diz que você pode pegar uma carta até 21 (mas o dealer permanece no 17), que você pode dividir as cartas, etc. Presumi que tudo pertencia à classe modelo e por essa razão senti que precisava uma classe de controlador BlacJackGame no modelo. Ainda estou tentando entender isso e gostaria de receber exemplos / referências. A ideia do blackjack como exemplo foi tirada de uma aula do iTunes sobre programação iOS, onde a lógica / regras de negócios estão definitivamente na classe de modelo de um padrão MVC.
Dave
3
@Dave Sim, o DrawCard()método estaria no ViewModel, junto com sua outra lógica de jogo. Em um aplicativo MVVM ideal, você deve ser capaz de executar seu aplicativo sem a interface do usuário, simplesmente criando ViewModels e executando seus métodos, como por meio de um script de teste ou uma janela de prompt de comando. Os modelos são normalmente apenas modelos de dados contendo dados brutos e validação de dados básicos.
Rachel de
6
Obrigado Rachel por toda a ajuda. Terei que pesquisar mais um pouco ou escrever outra pergunta; Ainda estou confuso sobre a localização da lógica do jogo. Você (e outros) defendem colocá-lo no ViewModel, outros dizem "lógica de negócios" que, no meu caso, presumo que as regras do jogo e o estado do jogo pertencem ao modelo (ver por exemplo: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) e stackoverflow.com/questions/10964003/… ). Reconheço que, neste jogo simples, provavelmente não importa muito. Mas seria bom saber. Thxs!
Dave
1
@Dave Posso estar usando o termo "lógica de negócios" incorretamente e confundindo-o com a lógica do aplicativo. Para citar o artigo MSDN que você vinculou "Para maximizar as oportunidades de reutilização, os modelos não devem conter nenhum comportamento específico de caso de uso ou de tarefa de usuário específico ou lógica de aplicativo" e "Normalmente, o modelo de visualização definirá comandos ou ações que podem ser representados na IU e que o usuário pode invocar " . Assim, coisas como a DrawCardCommand()estariam no ViewModel, mas acho que você poderia ter um BlackjackGameModelobjeto que contivesse um DrawCard()método que o comando chamasse se você quisesse
Rachel
2
Evite vazamentos de memória. Use um padrão WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS
24

Resposta curta: depende das especificações.

Em seu exemplo, os modelos estão sendo atualizados "por conta própria" e essas mudanças, é claro, precisam ser propagadas de alguma forma para as visualizações. Uma vez que as visualizações só podem acessar diretamente os modelos de visualização, isso significa que o modelo deve comunicar essas mudanças ao modelo de visualização correspondente. O mecanismo estabelecido para fazer isso é INotifyPropertyChanged, obviamente , o que significa que você obterá um fluxo de trabalho como este:

  1. Viewmodel é criado e envolve o modelo
  2. Viewmodel se inscreve no PropertyChangedevento do modelo
  3. Viewmodel é definido como view DataContext, propriedades são vinculadas etc.
  4. Ver ação de gatilhos no modelo de visão
  5. Viewmodel chama o método no modelo
  6. O modelo se atualiza
  7. Viewmodel lida com o modelo PropertyChangede aumenta o seu próprio PropertyChangedem resposta
  8. A visualização reflete as mudanças em seus vínculos, fechando o ciclo de feedback

Por outro lado, se seus modelos continham pouca (ou nenhuma) lógica de negócios, ou se por alguma outra razão (como ganhar capacidade transacional) você decidiu deixar cada viewmodel "possuir" seu modelo encapsulado, então todas as modificações no modelo passariam por o modelo de visão, de modo que tal arranjo não seria necessário.

Eu descrevo esse design em outra questão do MVVM aqui .

Jon
fonte
Olá, a lista que você fez é brilhante. No entanto, tenho um problema com 7. e 8. Em particular: Eu tenho um ViewModel, que não implementa INotifyPropertyChanged. Ele contém uma lista de filhos, que contém uma lista dos próprios filhos (é usado como um ViewModel para um controle WPF Treeview). Como faço o UserControl DataContext ViewModel "ouvir" as alterações de propriedade em qualquer um dos filhos (TreeviewItems)? Como exatamente faço para me inscrever em todos os elementos-filho que implementam INotifyPropertyChanged? Ou devo fazer uma pergunta separada?
Igor
4

Suas escolhas:

  • Implementar INotifyPropertyChanged
  • Eventos
  • POCO com manipulador de proxy

A meu ver, INotifyPropertyChangedé uma parte fundamental do .Net. ou seja, está dentro System.dll. Implementá-lo em seu "Modelo" é semelhante a implementar uma estrutura de evento.

Se você quiser POCO puro, terá que efetivamente manipular seus objetos por meio de proxies / serviços e, em seguida, seu ViewModel será notificado das mudanças ouvindo o proxy.

Pessoalmente, eu apenas implemento INotifyPropertyChanged vagamente e uso FODY para fazer o trabalho sujo para mim. Parece e se sente POCO.

Um exemplo (usando FODY para IL Weave os raisers PropertyChanged):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

então você pode fazer com que seu ViewModel ouça PropertyChanged para quaisquer alterações; ou alterações específicas de propriedade.

A beleza da rota INotifyPropertyChanged é que você a encadeia com uma Extended ObservableCollection . Então você despeja seus objetos próximos do poco em uma coleção e ouve a coleção ... se alguma coisa mudar, em qualquer lugar, você aprende sobre isso.

Serei honesto, isso poderia se juntar à discussão "Por que o INotifyPropertyChanged não foi autmaticamente manipulado pelo compilador", que se desenvolve para: Cada objeto em c # deve ter a facilidade de notificar se alguma parte dele foi alterada; isto é, implemente INotifyPropertyChanged por padrão. Mas não funciona e a melhor rota, que exige o mínimo de esforço, é usar IL Weaving (especificamente FODY ).

Meirion Hughes
fonte
4

Tópico bastante antigo, mas depois de muita pesquisa, descobri minha própria solução: A PropertyChangedProxy

Com essa classe, você pode registrar-se facilmente no NotifyPropertyChanged de outra pessoa e executar as ações apropriadas se for disparado para a propriedade registrada.

Aqui está um exemplo de como isso pode parecer quando você tem uma propriedade de modelo "Status" que pode mudar por conta própria e deve notificar automaticamente o ViewModel para disparar seu próprio PropertyChanged em sua propriedade "Status" para que a visualização também seja notificada: )

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));
    }
}

e aqui está a própria aula:

/// <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));
        }
    }
}
Roemer
fonte
1
Evite vazamentos de memória. Use um padrão WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/…
JJS
1
@JJS - OTOH, considere o padrão de evento fraco é perigoso . Pessoalmente, prefiro arriscar vazamento de memória se me esquecer de cancelar o registro ( -= my_event_handler), porque isso é mais fácil de rastrear do que um problema de zumbi raro + imprevisível que pode ou não acontecer.
Toolmaker Steve
@ToolmakerSteve obrigado por adicionar um argumento equilibrado. Eu sugiro que os desenvolvedores façam o que é melhor para eles, em sua própria situação. Não adote cegamente o código-fonte da Internet. Existem outros padrões, como o EventAggregator / EventBus comumente usado em mensagens de componentes cruzados (que também apresentam seus próprios perigos)
JJS
2

Achei este artigo útil: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf

Meu resumo:

A ideia por trás da organização do MVVM é permitir a reutilização mais fácil de visualizações e modelos e também permitir testes separados. Seu modelo de visão é um modelo que representa as entidades de visão, seu modelo representa as entidades de negócios.

E se você quisesse fazer um jogo de pôquer mais tarde? Grande parte da IU deve ser reutilizável. Se a lógica do seu jogo estiver vinculada ao seu modelo de visão, seria muito difícil reutilizar esses elementos sem ter que reprogramar o modelo de visão. E se você quiser mudar sua interface de usuário? Se a lógica do jogo estiver acoplada à lógica do modelo de visualização, você precisará verificar novamente se o jogo ainda funciona. E se você quiser criar um desktop e um aplicativo da web? Se o seu modelo de visão contiver a lógica do jogo, seria complicado tentar manter esses dois aplicativos lado a lado, pois a lógica do aplicativo seria inevitavelmente ligada à lógica de negócios no modelo de visão.

Notificações de mudança de dados e validação de dados acontecem em todas as camadas (a visão, o modelo de visão e o modelo).

O modelo contém suas representações de dados (entidades) e lógica de negócios específica para essas entidades. Um baralho de cartas é uma 'coisa' lógica com propriedades inerentes. Um bom deck não pode ter cartas duplicadas colocadas nele. Ele precisa expor uma maneira de obter as cartas do topo. Ele precisa saber para não distribuir mais cartões do que o restante. Esses comportamentos de baralho fazem parte do modelo porque são inerentes a um baralho de cartas. Haverá também modelos de revendedores, modelos de jogadores, modelos de mãos, etc. Esses modelos podem e irão interagir.

O modelo de visualização consistiria na apresentação e na lógica do aplicativo. Todo o trabalho associado à exibição do jogo é separado da lógica do jogo. Isso pode incluir a exibição de mãos como imagens, solicitações de cartões para o modelo do revendedor, configurações de exibição do usuário, etc.

A essência do artigo:

Basicamente, gosto de explicar isso: sua lógica de negócios e entidades compõem o modelo. Isso é o que seu aplicativo específico está usando, mas pode ser compartilhado entre muitos aplicativos.

A Visualização é a camada de apresentação - qualquer coisa relacionada à interface direta com o usuário.

O ViewModel é basicamente a "cola" específica do seu aplicativo que liga os dois.

Tenho um bom diagrama aqui que mostra como eles se relacionam:

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

No seu caso - vamos abordar alguns dos detalhes ...

Validação: normalmente vem em 2 formas. A validação relacionada à entrada do usuário aconteceria no ViewModel (principalmente) e na View (isto é: TextBox "Numeric" evitando que o texto seja inserido é manipulado para você na view, etc). Como tal, a validação da entrada do usuário é normalmente uma preocupação da VM. Dito isso, geralmente há uma segunda "camada" de validação - esta é a validação de que os dados usados ​​correspondem às regras de negócios. Isso geralmente faz parte do próprio modelo - quando você envia dados para o seu modelo, pode causar erros de validação. A VM terá então que remapear essas informações de volta para a Visualização.

Operações "nos bastidores, sem visualização, como escrever no banco de dados, enviar e-mail, etc": Isso é realmente parte das "Operações específicas do domínio" em meu diagrama e é puramente parte do modelo. Isso é o que você está tentando expor por meio do aplicativo. O ViewModel atua como uma ponte para expor essas informações, mas as operações são puramente Model.

Operações para ViewModel: O ViewModel precisa mais do que apenas INPC - ele também precisa de qualquer operação que seja específica para seu aplicativo (não sua lógica de negócios), como salvar preferências e estado do usuário, etc. Isso vai variar o aplicativo. por app., mesmo na interface do mesmo "modelo".

Uma boa maneira de pensar sobre isso - digamos que você queira fazer 2 versões do seu sistema de pedidos. O primeiro está em WPF e o segundo é uma interface da web.

A lógica compartilhada que trata dos próprios pedidos (envio de e-mails, entrada no BD, etc) é o Modelo. Seu aplicativo está expondo essas operações e dados ao usuário, mas fazendo isso de duas maneiras.

No aplicativo WPF, a interface do usuário (com a qual o visualizador interage) é a "visualização" - no aplicativo da web, esse é basicamente o código que (pelo menos eventualmente) é transformado em javascript + html + css no cliente.

O ViewModel é o resto da "cola" necessária para adaptar seu modelo (essas operações relacionadas à ordenação) para fazê-lo funcionar com a tecnologia de visualização / camada específica que você está usando.

VoteCoffee
fonte
Talvez um exemplo simples seja um reprodutor de música. Seus modelos conteriam as bibliotecas, o arquivo de som ativo, os codecs, a lógica do player e o código de processamento de sinal digital. Os modelos de visualização conteriam seus controles e visualizações e o navegador da biblioteca. há muita lógica da IU necessária para exibir todas essas informações e seria bom permitir que um programador se concentrasse em fazer a música tocar enquanto permite que outro programador se concentre em tornar a IU intuitiva e divertida. O modelo de visualização e o modelo devem permitir que esses dois programadores concordem sobre um conjunto de interfaces e trabalhem separadamente.
VoteCoffee
Outro bom exemplo é uma página da web. A lógica do lado do servidor geralmente é equivalente a um modelo. A lógica do lado do cliente é geralmente equivalente a um modelo de exibição. Eu facilmente imaginaria que a lógica do jogo pertenceria ao servidor e não seria confiada ao cliente.
VoteCoffee de
2

Notificação baseada em INotifyPropertyChanged e INotifyCollectionChanged é exatamente o que você precisa. Para simplificar sua vida com a assinatura de alterações de propriedade, validação em tempo de compilação do nome da propriedade, evitando vazamentos de memória, eu aconselharia você a usar PropertyObserver da Fundação MVVM de Josh Smith . Como este projeto é de código aberto, você pode adicionar apenas essa classe ao seu projeto a partir dos fontes.

Para entender como usar PropertyObserver leia este artigo .

Além disso, dê uma olhada mais a fundo nas extensões reativas (Rx) . Você pode expor IObserver <T> de seu modelo e assiná-lo no modelo de exibição.

Vladimir Dorokhov
fonte
Muito obrigado por consultar o excelente artigo de Josh Smith e cobrir os Eventos Fracos!
JJS de
1

Os caras fizeram um trabalho incrível respondendo isso, mas em situações como essa eu realmente sinto que o padrão MVVM é uma dor, então eu iria e usaria um controlador de supervisão ou uma abordagem de visão passiva e deixaria de lado o sistema de ligação, pelo menos para objetos de modelo que são gerar mudanças por conta própria.

Ibrahim Najjar
fonte
1

Há muito tempo venho defendendo o modelo direcional -> Exibir modelo -> Exibir fluxo de alterações, como você pode ver na seção Fluxo de alterações do meu artigo MVVM de 2008. Isso requer implementação INotifyPropertyChangedno modelo. Pelo que eu posso dizer, desde então se tornou uma prática comum.

Como você mencionou Josh Smith, dê uma olhada em sua classe PropertyChanged . É uma classe auxiliar para se inscrever no modeloINotifyPropertyChanged.PropertyChanged evento .

Na verdade, você pode levar essa abordagem muito mais longe, já que acabei de criar minha classe PropertiesUpdater . Propriedades no modelo de exibição são calculadas como expressões complexas que incluem uma ou mais propriedades no modelo.

HappyNomad
fonte
1

Não há nada de errado em implementar INotifyPropertyChanged dentro de Model e ouvi-lo dentro de ViewModel. Na verdade, você pode até pontuar na propriedade do modelo diretamente em XAML: {Binding Model.ModelProperty}

Quanto às propriedades somente leitura dependentes / calculadas, de longe não vi nada melhor e mais simples do que isso: https://github.com/StephenCleary/CalculatedProperties . É muito simples, mas incrivelmente útil, é realmente "fórmulas do Excel para MVVM" - funciona da mesma maneira que o Excel propagando alterações para células de fórmula sem esforço extra da sua parte.

Cola
fonte
0

Você pode gerar eventos do modelo, os quais o viewmodel precisaria se inscrever.

Por exemplo, recentemente trabalhei em um projeto para o qual tive que gerar uma visualização em árvore (naturalmente, o modelo tinha uma natureza hierárquica). No modelo, tive uma coleção observável chamada ChildElements.

No viewmodel, eu armazenei uma referência ao objeto no modelo e me inscrevi no CollectionChangedevento da coleção observável, assim:ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here) ...

Então, seu viewmodel é notificado automaticamente assim que uma mudança acontece no modelo. Você pode seguir o mesmo conceito usando PropertyChanged, mas precisará aumentar explicitamente os eventos de alteração de propriedade do seu modelo para que isso funcione.

Mash
fonte
Se estiver lidando com dados hierárquicos, você vai querer dar uma olhada na Demo 2 do meu artigo MVVM .
HappyNomad
0

Esta me parece uma questão muito importante - mesmo quando não há pressão para fazê-la. Estou trabalhando em um projeto de teste, que envolve um TreeView. Existem itens de menu e outros que são mapeados para comandos, por exemplo, Excluir. Atualmente, estou atualizando o modelo e o modelo de vista de dentro do modelo de vista.

Por exemplo,

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
}

Isso é simples, mas parece ter uma falha muito básica. Um teste de unidade típico executaria o comando e, em seguida, verificaria o resultado no modelo de visualização. Mas isso não testa se a atualização do modelo estava correta, uma vez que os dois são atualizados simultaneamente.

Portanto, talvez seja melhor usar técnicas como PropertyObserver para permitir que a atualização do modelo acione uma atualização do modelo de visualização. O mesmo teste de unidade agora só funcionaria se ambas as ações fossem bem-sucedidas.

Esta não é uma resposta potencial, eu percebo, mas parece que vale a pena colocá-la por aí.

Arte
fonte