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 INotifyPropertyChanged
para 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 IDataErrorInfo
e 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 Item
que 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 DialogResult
propriedade 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.