Bons exemplos de modelo MVVM

141

Atualmente, estou trabalhando com o modelo Microsoft MVVM e considero frustrante a falta de exemplos detalhados. O exemplo incluído do ContactBook mostra muito pouco tratamento de comandos e o único outro exemplo que encontrei é de um artigo da MSDN Magazine em que os conceitos são semelhantes, mas usam uma abordagem um pouco diferente e ainda carecem de complexidade. Existem exemplos decentes de MVVM que mostram pelo menos operações básicas CRUD e troca de diálogo / conteúdo?


As sugestões de todos foram realmente úteis e começarei a compilar uma lista de bons recursos

Estruturas / Modelos

Artigos úteis

Screencasts

Bibliotecas adicionais

jwarzech
fonte
Fico feliz que esses recursos tenham ajudado. Atualmente, estou no meu segundo aplicativo MVVM de produção e continuarei adicionando conteúdo que será útil para quem está iniciando à medida que o encontro.
jwarzech

Respostas:

59

Infelizmente, não há um ótimo exemplo de aplicativo MVVM que faça tudo, e há várias abordagens diferentes para fazer as coisas. Primeiro, você pode se familiarizar com uma das estruturas de aplicativos existentes no mercado (o Prism é uma escolha decente), porque elas fornecem ferramentas convenientes, como injeção de dependência, comando, agregação de eventos etc. para experimentar facilmente diferentes padrões adequados a você. .

O lançamento do prisma:
http://www.codeplex.com/CompositeWPF

Ele inclui um aplicativo de exemplo bastante decente (o corretor da bolsa), juntamente com muitos exemplos menores e como fazer. No mínimo, é uma boa demonstração de vários sub-padrões comuns que as pessoas usam para fazer o MVVM realmente funcionar. Eles têm exemplos para CRUD e diálogos, acredito.

O prisma não é necessariamente para todos os projetos, mas é bom se familiarizar.

CRUD: Esta parte é bastante fácil, as ligações bidirecionais do WPF facilitam a edição da maioria dos dados. O verdadeiro truque é fornecer um modelo que facilite a configuração da interface do usuário. No mínimo, você deseja garantir que seu ViewModel (ou objeto de negócios) seja implementado INotifyPropertyChangedpara oferecer suporte à ligação e que você possa vincular propriedades diretamente aos controles da interface do usuário, mas também poderá implementarIDataErrorInfo para validação. Normalmente, se você usar algum tipo de solução ORM, configurar o CRUD é muito fácil.

Este artigo demonstra operações simples de crud: http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx

Ele é construído no LinqToSql, mas isso é irrelevante para o exemplo - tudo o que é importante é que seus objetos de negócios implementem INotifyPropertyChanged (que classes geradas pelo LinqToSql fazem). MVVM não é o objetivo desse exemplo, mas acho que não importa nesse caso.

Este artigo demonstra a validação de dados
http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx

Novamente, a maioria das soluções ORM gera classes que já implementam IDataErrorInfoe geralmente fornecem um mecanismo para facilitar a adição de regras de validação personalizadas.

Na maioria das vezes, você pode pegar um objeto (modelo) criado por algum ORM e envolvê-lo em um ViewModel que o contém e comandos para salvar / excluir - e você está pronto para vincular a interface do usuário diretamente às propriedades do modelo.

A visualização ficaria assim: (ViewModel possui uma propriedade Itemque mantém o modelo, como uma classe criada no ORM):

<StackPanel>
   <StackPanel DataContext=Item>
      <TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
      <TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
   </StackPanel>
   <Button Command="{Binding SaveCommand}" />
   <Button Command="{Binding CancelCommand}" />
</StackPanel>

Diálogos: Diálogos e MVVM são um pouco complicados. Eu prefiro usar um pouco da abordagem do Mediador com diálogos, você pode ler um pouco mais sobre isso nesta pergunta StackOverflow:
Exemplo de diálogo WPF MVVM

Minha abordagem usual, que não é bem clássica do MVVM, pode ser resumida da seguinte forma:

Uma classe base para um ViewModel da caixa de diálogo que expõe comandos para ações de confirmação e cancelamento, um evento para que a visualização saiba que uma caixa de diálogo está pronta para ser fechada e o que mais você precisar em todas as suas caixas de diálogo.

Uma exibição genérica para sua caixa de diálogo - pode ser uma janela ou um controle de tipo de sobreposição "modal" personalizado. No fundo, é um apresentador de conteúdo no qual despejamos o modelo de exibição e ele lida com a fiação para fechar a janela - por exemplo, na alteração do contexto de dados, você pode verificar se o novo ViewModel é herdado da sua classe base e, se for, inscreva-se no evento de fechamento relevante (o manipulador atribuirá o resultado do diálogo). Se você fornecer uma funcionalidade de fechamento universal alternativa (o botão X, por exemplo), certifique-se de executar o comando de fechamento relevante no ViewModel também.

Em algum lugar em que você precisa fornecer modelos de dados para seus ViewModels, eles podem ser muito simples, especialmente porque você provavelmente tem uma visão para cada caixa de diálogo encapsulada em um controle separado. O modelo de dados padrão para um ViewModel ficaria assim:

<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
   <views:AddressEditView DataContext="{Binding}" />
</DataTemplate>

A visualização da caixa de diálogo precisa ter acesso a eles, porque, caso contrário, não saberá como exibir o ViewModel, além da interface do usuário da caixa de diálogo compartilhada, seu conteúdo é basicamente o seguinte:

<ContentControl Content="{Binding}" />

O modelo de dados implícito mapeará a visualização para o modelo, mas quem a inicia?

Esta é a parte não tão mvvm. Uma maneira de fazer isso é usar um evento global. O que eu acho que é a melhor coisa a fazer é usar uma configuração do tipo agregador de eventos, fornecida por injeção de dependência - dessa forma, o evento é global para um contêiner, não para o aplicativo inteiro. O Prism usa a estrutura unity para semântica de contêineres e injeção de dependência, e no geral eu gosto bastante do Unity.

Geralmente, faz sentido que a janela raiz assine esse evento - ela pode abrir a caixa de diálogo e definir seu contexto de dados para o ViewModel que é passado com um evento gerado.

A configuração dessa maneira permite que o ViewModels solicite ao aplicativo que abra uma caixa de diálogo e responda às ações do usuário sem saber nada sobre a interface do usuário, de modo que a MVVM-ness permaneça completa.

Há momentos, no entanto, em que a interface do usuário precisa aumentar as caixas de diálogo, o que pode tornar as coisas um pouco mais complicadas. Considere, por exemplo, se a posição da caixa de diálogo depende da localização do botão que a abre. Nesse caso, você precisa ter algumas informações específicas da interface do usuário ao solicitar uma caixa de diálogo aberta. Geralmente, crio uma classe separada que contém um ViewModel e algumas informações relevantes da interface do usuário. Infelizmente, alguns acoplamentos parecem inevitáveis ​​lá.

Pseudo-código de um manipulador de botão que gera uma caixa de diálogo que precisa de dados de posição do elemento:

ButtonClickHandler(sender, args){
    var vm = DataContext as ISomeDialogProvider; // check for null
    var ui_vm = new ViewModelContainer();
    // assign margin, width, or anything else that your custom dialog might require
    ...
    ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
    // raise the dialog show event
}

A visualização da caixa de diálogo será vinculada aos dados da posição e passará o ViewModel contido para o interior ContentControl. O ViewModel em si ainda não sabe nada sobre a interface do usuário.

Em geral, eu não uso a DialogResultpropriedade return do ShowDialog()método ou espero que o thread bloqueie até que a caixa de diálogo seja fechada. Um diálogo modal não-padrão nem sempre funciona assim e, em um ambiente composto, muitas vezes você realmente não quer que um manipulador de eventos bloqueie assim. Prefiro deixar que os ViewModels lidem com isso - o criador de um ViewModel pode se inscrever em seus eventos relevantes, definir métodos de confirmação / cancelamento, etc., para que não haja necessidade de confiar nesse mecanismo da interface do usuário.

Então, em vez deste fluxo:

// in code behind
var result = somedialog.ShowDialog();
if (result == ...

Eu uso:

// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit 
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container

Eu prefiro assim, porque a maioria dos meus diálogos são controles pseudo-modais sem bloqueio e fazê-lo dessa maneira parece mais direto do que contornar isso. Fácil de testar também.

Egor
fonte
Obrigado pela resposta detalhada! Recentemente, descobri que meu maior problema é quando preciso que um MainViewModel se comunique com outros modelos de exibição para lidar com o fluxo do aplicativo. No entanto, parece que o MVVM + Mediador parece ser a abordagem popular.
11139 jwarzech
2
O Mediador definitivamente ajuda, o padrão agregador de eventos (o Prism tem uma boa implementação) também é realmente útil quando o baixo acoplamento é uma meta. Além disso, seu viewmodel principal normalmente possui seus próprios modelos filhos e não deve ter problemas de comunicação com eles. Você precisa usar um mediador ou / e um agregador de eventos quando seus modelos de exibição filhos precisam interagir com outros módulos em seu aplicativo que eles não conhecem necessariamente - incluindo a interface do usuário (meu exemplo de diálogo é sobre este caso específico).
211 Egor
1
As diretrizes para trabalhar com caixas de diálogo e janelas foram realmente úteis. No entanto, estou com alguns problemas: 1. Como você define o título da janela na visualização? 2. Como você lida com a configuração da janela do proprietário?
precisa saber é o seguinte
@ Daniel Skinner: Eu estou supondo que você esteja falando sobre diálogos aqui, me corrija se eu estiver errado. O título da caixa de diálogo é apenas outra propriedade e você pode vinculá-lo ao que quiser. Se você seguiu minha abordagem com uma classe base de modelo de exibição de diálogo (vamos fingir que tem uma propriedade de título), em toda a janela de diálogo genérica, você pode usar a ligação UI à UI para definir o título como {Binding Path = DataContext.Title, ElementName = NameOfContentPresenter}. A janela do proprietário é um pouco mais complicada - significa que o mediador que realmente abre a caixa de diálogo precisa saber sobre a visualização do aplicativo raiz.
Egor 29/01
Na verdade, retiro isso - independentemente de como você estrutura isso em algum momento, quem está realmente aparecendo na caixa de diálogo precisa ter uma referência à janela / exibição do aplicativo raiz. Observe onde eu disse: "Geralmente, faz sentido para a janela raiz se inscrever neste evento - ele pode abrir a caixa de diálogo e definir seu contexto de dados para o viewmodel que é passado com um evento gerado". É aqui que você definiria o proprietário.
Egor
6

Jason Dolinger fez um bom screencast do MVVM. Como Egor mencionou, não há um bom exemplo. Eles acabaram. Muitos são bons exemplos de MVVM, mas não quando você se depara com problemas complexos. Todo mundo tem seu próprio caminho. Laurent Bugnion também tem uma boa maneira de se comunicar entre os modelos de exibição. http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx O Cinch também é um bom exemplo. Paul Stovel tem um bom post que explica muito também com sua estrutura Magellan.

nportelli
fonte
3

Você já viu Caliburn ? A amostra do ContactManager contém muitas coisas boas. As amostras WPF genéricas também fornecem uma boa visão geral dos comandos. A documentação é bastante boa e os fóruns estão ativos. Recomendado!

Andy S
fonte
2

O projeto de amostra na estrutura Cinch mostra ferramentas básicas de CRUD e navegação. É um exemplo bastante bom do uso do MVVM e inclui um artigo com várias partes explicando seu uso e motivações.

Reed Copsey
fonte
2

Eu também compartilhei sua frustração. Estou escrevendo um aplicativo e tinha esses 3 requisitos:

  • Extensível
  • WPF com MVVM
  • Exemplos compatíveis com GPL

Tudo o que encontrei foram pedaços, então comecei a escrever da melhor maneira possível. Depois que entrei um pouco nisso, percebi que poderia haver outras pessoas (como você) que poderiam usar um aplicativo de referência, então refatorei o material genérico em uma estrutura de aplicativo WPF / MVVM e a liberei sob a LGPL. Eu o chamei SoapBox Core . Se você for para a página de downloads, verá que ele vem com um pequeno aplicativo de demonstração, e o código-fonte desse aplicativo de demonstração também está disponível para download. Espero que você ache isso útil. Além disso, envie um e-mail para scott {at} soapboxautomation.com se você quiser mais informações.

EDIT : Também postou um artigo do CodeProject explicando como funciona.

Scott Whitlock
fonte
2

Eu escrevi um exemplo simples do MVVM do zero no projeto de código, aqui está o link MVVM WPF passo a passo . Começa a partir de uma arquitetura simples de 3 camadas e gradua você para usar alguma estrutura como o PRISM.

insira a descrição da imagem aqui

Shivprasad Koirala
fonte
1

Até eu compartilhei a frustração até tomar o assunto em minhas mãos. Comecei o IncEditor.

O IncEditor ( http://inceditor.codeplex.com ) é um editor que tenta apresentar aos desenvolvedores o WPF, MVVM e MEF. Eu o iniciei e consegui obter algumas funcionalidades como suporte ao 'tema'. Como não sou especialista em WPF, MVVM ou MEF, não posso colocar muita funcionalidade nele. Faço um pedido sincero a vocês para que melhorem, para que malucos como eu possam entender melhor.

Abdulsattar Mohammed
fonte