Quem deve controlar a navegação em um aplicativo MVVM?

33

Exemplo # 1: Tenho uma visualização exibida no meu aplicativo MVVM (vamos usar o Silverlight para os fins da discussão) e clico em um botão que deve me levar a uma nova página.

Exemplo 2: Essa mesma visualização possui outro botão que, quando clicado, deve abrir uma visualização de detalhes em uma janela filho (caixa de diálogo).

Sabemos que haverá objetos de comando expostos pelo nosso ViewModel vinculados aos botões com métodos que respondem ao clique do usuário. Mas o que então? Como concluímos a ação? Mesmo se usarmos o chamado NavigationService, o que estamos dizendo?

Para ser mais específico, em um modelo tradicional de exibição primeiro (como esquemas de navegação baseados em URL, como na web ou na estrutura de navegação interna do SL), os objetos de comando precisariam saber qual exibição exibir a seguir. Isso parece ultrapassar os limites quando se trata da separação de preocupações promovidas pelo padrão.

Por outro lado, se o botão não fosse conectado a um objeto Command e se comportasse como um hiperlink, as regras de navegação poderiam ser definidas na marcação. Mas queremos que os modos de exibição controlem o fluxo de aplicativos e a navegação não é apenas outro tipo de lógica de negócios? (Posso dizer sim em alguns casos e não em outros.)

Para mim, a implementação utópica do padrão MVVM (e já ouvi outras pessoas afirmarem isso) seria ter o ViewModel conectado de forma que o aplicativo possa ser executado sem cabeça (ou seja, sem exibições). Isso fornece a maior área de superfície para testes baseados em código e torna o Views uma aparência verdadeira no aplicativo. E meu ViewModel não deve se importar se for exibido na janela principal, em um painel flutuante ou em uma janela filho, deveria?

De acordo com essa abordagem, depende de algum outro mecanismo em tempo de execução "vincular" o que o View deve ser exibido para cada ViewModel. Mas e se quisermos compartilhar uma View com vários ViewModels ou vice-versa?

Portanto, dada a necessidade de gerenciar o relacionamento View-ViewModel para que saibamos o que exibir quando juntamente com a necessidade de navegar entre as visualizações, incluindo a exibição de janelas / caixas de diálogo filho, como podemos realmente fazer isso no padrão MVVM?

SonOfPirate
fonte

Respostas:

21

A navegação sempre deve ser realizada no ViewModel.

Você está no caminho certo ao pensar que a implementação perfeita do padrão de design do MVVM significaria que você poderia executar seu aplicativo inteiramente sem Views, e não poderá fazer isso se seus Views controlarem sua Navegação.

Eu normalmente tenho um ApplicationViewModel, ou ShellViewModel, que lida com o estado geral do meu aplicativo. Isso inclui o CurrentPage(que é um ViewModel) e o código para manipulação ChangePageEvents. (Geralmente também é usado para outros objetos de todo o aplicativo, como o CurrentUser ou ErrorMessages também)

Portanto, se algum ViewModel, em qualquer lugar, transmitir a ChangePageEvent(new SomePageViewModel), a ShellViewModelmensagem será capturada e mudada CurrentPagepara a página especificada na mensagem.

Na verdade, escrevi um post sobre navegação com MVVM, se você estiver interessado

Rachel
fonte
2
Abordagem interessante. Quatro comentários: 1) O Silverlight não suporta a propriedade DataType no DataTemplate, portanto, não é possível mapear o DataTemplate para o ViewModel no SL. 2) Isso não trata das possibilidades muitos para muitos entre Views e ViewModels. 3) Ele não suporta janelas filho (ou pelo menos não vejo como). 4) Requer um acoplamento estreito entre o seu Application / Shell ViewModel e seus filhos (netos, etc.) Se eu tiver 40 páginas no meu aplicativo, esse ViewModel seria uma maneira difícil de gerenciar.
SonOfPirate
Dito isto, é definitivamente algo a considerar.
SonOfPirate
@SonOfPirate 1) O Silverlight não suporta mapeamento implícito do DataTemplate (ainda), no entanto, ele suporta a DataTemplateSelector, que é o que eu costumo usar para aplicativos Silverlight. 2) Eu já usei isso em muitas situações antes. Por exemplo, um ViewModel pode ter várias Views ou uma View pode ser associada a múltiplos ViewModels. Para um exemplo do primeiro, consulte rachel53461.wordpress.com/2011/05/28/… . Para o posterior, você só precisa especificar que vários ViewModels mapeiam para a mesma exibição no seu DataTemplateSelector.
Rachel
3) Esse é apenas um exemplo básico. Se você soubesse que seu aplicativo teria várias janelas, você obviamente alteraria o ShellViewModel para lidar com várias CurrentPages4) Mais uma vez, esse era apenas um exemplo básico. Na realidade, meus PageViewModels são todos baseados em alguma classe base, portanto meu ShellViewModel funciona apenas com a classe ou interface base, como IPageViewModel. Realmente, a maior parte confusa do mapeamento é o DataTemplateSelector, que precisaria mapear 40 Views para 40 ViewModels.
Rachel
@SonOfPirate Espero que tenha respondido algumas de suas perguntas. Sinta-se livre para me procurar no bate-papo se você tiver outros :)
Rachel
6

Para encerrar, pensei em publicar a direção que finalmente escolhi para resolver esse problema.

A primeira decisão foi alavancar a estrutura do Silverlight Page Navigation fornecida imediatamente. Essa decisão foi baseada em vários fatores, incluindo o conhecimento de que esse tipo de navegação está sendo transportado pela Microsoft para os aplicativos Windows 8 Metro e é consistente com a navegação nos aplicativos Phone 7.

Para fazê-lo funcionar, examinei o trabalho que o ASP.NET MVC fez com a navegação baseada em convenções. O controle Frame usa URIs para localizar a 'página' a ser exibida. A semelhança proporcionou uma oportunidade de usar uma abordagem semelhante baseada em convenções no aplicativo Silverlight. O truque era fazer com que tudo funcionasse juntos de uma maneira MVVM.

A solução é o NavigationService. Este serviço expõe vários métodos, como NavigateTo e Back, que o ViewModels pode usar para iniciar uma alteração de página. Quando uma nova página é solicitada, o NavigationService envia um CurrentPageChangedMessage usando o recurso MVVMLight Messenger.

A exibição que contém o controle Frame possui seu próprio ViewModel definido como o DataContext que está atendendo a esta mensagem. Quando recebido, o nome da nova visualização é colocado através de uma função de mapeamento que aplica nossas regras de convenções e é definida como a propriedade CurrentPage. A propriedade Source do controle Frame está vinculada à propriedade CurrentPage. Como resultado, a configuração da propriedade atualiza a Origem e aciona a navegação.

Voltando ao NavigationService. O método NavigateTo aceita o nome da página de destino. Para garantir que os ViewModels não tenham preocupações com a interface do usuário, o nome usado é o nome do ViewModel a ser exibido. Na verdade, criei uma enumeração que possui um campo para cada ViewModel navegável como auxiliar e para eliminar seqüências de caracteres mágicas em todo o aplicativo. A função de mapeamento que mencionei acima removerá o sufixo "ViewModel" do nome, anexará "Página" ao nome e definirá o nome completo como "Exibições {Nome} Page.xaml".

Assim, por exemplo, para navegar para a exibição de detalhes do cliente, posso ligar para:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

O valor de CustomerDetails é "CustomerDetailsViewModel", que é mapeado para "Views \ CustomerDetailsPage.xaml".

A vantagem dessa abordagem é que a interface do usuário é completamente dissociada dos modelos de exibição, mas temos suporte completo à navegação. Agora posso refazer meu aplicativo, no entanto e sempre que achar necessário, sem alterações no código.

Espero que a explicação ajude.

SonOfPirate
fonte
2

Semelhante ao que Rachel disse, meu aplicativo principalmente MVVM tem Presenterque lidar com alternâncias entre janelas ou páginas. Rachel chama isso de ApplicationViewModel, mas, na minha experiência, geralmente tem que fazer mais do que apenas ser um destino obrigatório (como receber mensagens, criar Windows etc.), para que seja tecnicamente mais parecido com um Presenterou tradicional Controller.

No meu aplicativo, meu Presenterinício com a CurrentViewModel. O Presenterintercepta toda a comunicação entre o Viewe o ViewModel. Uma das coisas que oViewModel pode fazer durante uma interação é retornar um novo ViewModel, o que significa que uma nova página ou um novo Windowdeve ser exibido. Ele Presentercuida da criação ou substituição Viewdo novo ViewModele da configuração do novo DataContext.

O resultado de uma ação também pode ser que a ViewModelesteja "completa"; nesse caso, oPresenter detecta e fecha a janela ou a ViewModelretira da pilha da VM e volta a exibir a página anterior.

Scott Whitlock
fonte
Como o apresentador sabe qual exibição exibir?
SonOfPirate
1
@SonOfPirate - Isso normalmente é feito por meio de mecanismos WPF. O Presenterapenas cola o retornado ViewModelna árvore visual e o WPF pega o apropriado View, conecta o DataContexte coloca na árvore visual. Você pode fazer isso usando DataTemplates que declaram qual ViewModeltipo eles processam ou pode criar um seletor de modelo de dados personalizado. Isso ainda está dentro do domínio dos recursos do WPF.
21130 Scott Whitlock