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 INotifyPropertyChanged
e 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
, Deck
mas também a BlackJackGame
classe 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 BlackJackGame
I expor métodos como "drawcard" e ocorreu-me que, quando um cartão é desenhado, propriedades, tais como CardScore
, e IsBust
deve 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?
OnBust
e a VM pode se inscrever nele. Eu acho que você também pode usar uma abordagem IEA também.Respostas:
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:
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
EventAggregator
ou MVVM LightMessenger
.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
PlayerScoreHasChangedMessage
partir de um objeto e outro objeto pode se inscrever para ouvir esses tipos de mensagens e atualizar suaPlayerScore
propriedade 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:
e você teria um objeto ViewModel como
(Todos os objetos acima devem ser implementados
INotifyPropertyChanged
, mas deixei de fora para simplificar)fonte
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.DrawCardCommand()
estariam no ViewModel, mas acho que você poderia ter umBlackjackGameModel
objeto que contivesse umDrawCard()
método que o comando chamasse se você quisesseResposta 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:PropertyChanged
evento do modeloDataContext
, propriedades são vinculadas etc.PropertyChanged
e aumenta o seu próprioPropertyChanged
em respostaPor 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 .
fonte
Suas escolhas:
A meu ver,
INotifyPropertyChanged
é uma parte fundamental do .Net. ou seja, está dentroSystem.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):
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 ).
fonte
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: )
e aqui está a própria aula:
fonte
-= 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.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:
fonte
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.
fonte
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.
fonte
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
INotifyPropertyChanged
no 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 modelo
INotifyPropertyChanged.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.
fonte
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.
fonte
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
CollectionChanged
evento 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.fonte
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,
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í.
fonte