Model-View-Presenter em WinForms

Eu estou tentando implementar o método MVP pela primeira vez, usando WinForms.

Eu estou tentando entender a function de cada camada.

No meu programa eu tenho um botão GUI que quando clicado abre uma janela openfiledialog.

Portanto, usando o MVP, a GUI manipula o evento click do botão e, em seguida, chama o presenter.openfile ();

Dentro de presenter.openfile (), deve então delegar a abertura desse arquivo para a camada de modelo, ou como não há dados ou lógica para processar, deve simplesmente agir sobre a requisição e abrir a janela openfiledialog?

Atualização: Eu decidi oferecer uma recompensa, pois sinto que preciso de mais assistência sobre isso, e de preferência adaptada aos meus pontos específicos abaixo, para que eu tenha contexto.

Ok, depois de ler no MVP, decidi implementar o Passive View. Efetivamente eu terei um monte de controles em um WinForm que serão manipulados por um apresentador e então as tarefas delegadas ao (s) modelo (s). Meus pontos específicos estão abaixo:

  1. Quando o winform é carregado, ele precisa obter um treeview. Estou correto em pensar que a visão deve, portanto, chamar um método como: presenter.gettree (), este por sua vez irá delegar ao modelo, que irá obter os dados para o treeview, criá-lo e configurá-lo, devolvê-lo ao apresentador, que por sua vez, passará para a visão que, em seguida, simplesmente atribuí-lo a, digamos, um painel?

  2. Isso seria o mesmo para qualquer controle de dados no WinForm, como eu também tenho um datagridview?

  3. Meu aplicativo, tem várias classs de modelo com o mesmo assembly. Ele também suporta uma arquitetura de plug-in com plug-ins que precisam ser carregados na boot. Será que a exibição simplesmente chamaria um método de apresentador, que por sua vez chamaria um método que carregasse os plug-ins e exibisse as informações na exibição? Qual camada controlaria as referências do plug-in. A vista conteria referências a eles ou ao apresentador?

  4. Estou correto em pensar que a visão deve lidar com cada coisa sobre apresentação, da cor do nó do treeview, ao tamanho do datagrid, etc?

Eu acho que eles são minhas principais preocupações e se eu entendi como o stream deve ser para estes, eu acho que vai ficar bem.

Esta é a minha humilde opinião sobre o MVP e seus problemas específicos.

Primeiro , qualquer coisa com a qual um usuário pode interagir, ou apenas ser mostrado, é uma visão . As leis, o comportamento e as características de tal visão são descritos por uma interface . Essa interface pode ser implementada usando uma interface do usuário do WinForms, uma UI do console, uma interface do usuário da web ou mesmo nenhuma interface do usuário (geralmente ao testar um apresentador) – a implementação concreta não importa, desde que obedeça às leis de sua interface de exibição .

Em segundo lugar , uma visão é sempre controlada por um apresentador . As leis, o comportamento e as características desse apresentador também são descritos por uma interface . Essa interface não tem interesse na implementação da visualização concreta, desde que obedeça às leis de sua interface de visualização.

Terceiro , como um apresentador controla sua visão, para minimizar dependencies, não há realmente nenhum ganho em ter a visão sabendo algo sobre o apresentador. Há um contrato acordado entre o apresentador e a visão e isso é declarado pela interface de exibição.

As implicações do terceiro são:

  • O apresentador não tem nenhum método que a exibição possa chamar, mas a exibição tem events que o apresentador pode assinar.
  • O apresentador conhece sua opinião. Eu prefiro fazer isso com injeção de construtor no apresentador de concreto.
  • A visão não tem idéia do que o apresentador está controlando; nunca será fornecido nenhum apresentador.

Para o seu problema, o código acima pode se parecer com um código simplificado:

interface IConfigurationView { event EventHandler SelectConfigurationFile; void SetConfigurationFile(string fullPath); void Show(); } class ConfigurationView : IConfigurationView { Form form; Button selectConfigurationFileButton; Label fullPathLabel; public event EventHandler SelectConfigurationFile; public ConfigurationView() { // UI initialization. this.selectConfigurationFileButton.Click += delegate { var Handler = this.SelectConfigurationFile; if (Handler != null) { Handler(this, EventArgs.Empty); } }; } public void SetConfigurationFile(string fullPath) { this.fullPathLabel.Text = fullPath; } public void Show() { this.form.ShowDialog(); } } interface IConfigurationPresenter { void ShowView(); } class ConfigurationPresenter : IConfigurationPresenter { Configuration configuration = new Configuration(); IConfigurationView view; public ConfigurationPresenter(IConfigurationView view) { this.view = view; this.view.SelectConfigurationFile += delegate { // The ISelectFilePresenter and ISelectFileView behaviors // are implicit here, but in a WinForms case, a call to // OpenFileDialog wouldn't be too far fetched... var selectFilePresenter = Gimme.The(); selectFilePresenter.ShowView(); this.configuration.FullPath = selectFilePresenter.FullPath; this.view.SetConfigurationFile(this.configuration.FullPath); }; } public void ShowView() { this.view.SetConfigurationFile(this.configuration.FullPath); this.view.Show(); } } 

Além do acima, eu normalmente tenho uma interface base IView onde eu escondo o Show() e qualquer visão do proprietário ou o título de exibição que meus pontos de vista geralmente se beneficiam.

Para suas perguntas:

1. Quando o winform carrega, ele tem que obter um treeview. Estou correto em pensar que a visão deve, portanto, chamar um método como: presenter.gettree (), este por sua vez irá delegar ao modelo, que irá obter os dados para o treeview, criá-lo e configurá-lo, devolvê-lo ao apresentador, que por sua vez, passará para a visão que, em seguida, simplesmente atribuí-lo a, digamos, um painel?

Eu chamaria IConfigurationView.SetTreeData(...) de IConfigurationPresenter.ShowView() , logo antes da chamada para IConfigurationView.Show()

2. Isso seria o mesmo para qualquer controle de dados no WinForm, como eu também tenho um datagridview?

Sim, eu chamaria IConfigurationView.SetTableData(...) para isso. Cabe à visão formatar os dados dados a ela. O apresentador simplesmente obedece ao contrato da visão de que deseja dados tabulares.

3. Meu aplicativo tem várias classs de modelo com o mesmo assembly. Ele também suporta uma arquitetura de plug-in com plug-ins que precisam ser carregados na boot. Será que a exibição simplesmente chamaria um método de apresentador, que por sua vez chamaria um método que carregasse os plug-ins e exibisse as informações na exibição? Qual camada controlaria as referências do plug-in. A vista conteria referências a eles ou ao apresentador?

Se os plug-ins estiverem relacionados à visualização, as visualizações devem conhecê-los, mas não o apresentador. Se eles são todos sobre dados e modelo, então a visão não deve ter nada a ver com eles.

4. Estou correto em pensar que a visão deve lidar com cada coisa sobre apresentação, da cor do nó do treeview, ao tamanho do datagrid, etc?

Sim. Pense nisso como o apresentador fornecendo XML que descreve dados e a exibição que pega os dados e aplica uma folha de estilo CSS a ele. Em termos concretos, o apresentador pode chamar IRoadMapView.SetRoadCondition(RoadCondition.Slippery) e a exibição renderiza a estrada na cor vermelha.

E os dados dos nós clicados?

5. Se, quando clico nos treenodes, devo passar pelo nó específico para o apresentador e, a partir daí, o apresentador poderia descobrir quais dados precisa e, em seguida, solicita ao modelo esses dados, antes de apresentá-los de volta à exibição?

Se possível, eu passaria todos os dados necessários para apresentar a tree em uma visão em um único tiro. Mas se alguns dados são muito grandes para serem passados ​​desde o começo ou se são dynamics em sua natureza e precisam do “instantâneo mais recente” do modelo (através do apresentador), então eu adicionaria algo como event LoadNodeDetailsEventHandler LoadNodeDetails para a interface view, para que o apresentador possa assiná-lo, busque os detalhes do nó em LoadNodeDetailsEventArgs.Node (possivelmente por meio de seu ID de algum tipo) a partir do modelo, para que a visualização possa atualizar os detalhes do nó mostrado quando o delegado do manipulador de events retornar. Observe que padrões asynchronouss disso podem ser necessários se a busca dos dados for muito lenta para uma boa experiência do usuário.

O apresentador, que contém toda a lógica na visão, deve responder ao botão que está sendo clicado, como diz @JochemKempe. Em termos práticos, o manipulador de events de clique de botão chama presenter.OpenFile() . O apresentador pode então determinar o que deve ser feito.

Se decidir que o usuário deve selecionar um arquivo, ele retornará para a exibição (por meio de uma interface de visualização) e permitirá que a exibição, que contém todos os detalhes técnicos da interface do usuário, exiba o OpenFileDialog . Esta é uma distinção muito importante em que o apresentador não deve ser autorizado a realizar operações vinculadas à tecnologia de interface do usuário em uso.

O arquivo selecionado será então retornado ao apresentador que continua sua lógica. Isso pode envolver qualquer modelo ou serviço que deve manipular o processamento do arquivo.

A principal razão para usar um padrão MVP, é separar a tecnologia da interface do usuário da lógica da visão. Assim, o apresentador orquestra toda a lógica enquanto a visualização a mantém separada da lógica da interface do usuário. Isso tem o efeito colateral muito bom de tornar o apresentador totalmente testável.

Atualização: como o apresentador é a incorporação da lógica encontrada em uma visualização específica , a relação entre apresentador e visualizador é IMO, um relacionamento um-para-um. E para todos os propósitos práticos, uma instância de visualização (digamos, um formulário) interage com uma instância de apresentador, e uma instância de apresentador interage com apenas uma instância de exibição.

Dito isso, na minha implementação do MVP com WinForms, o apresentador sempre interage com a exibição por meio de uma interface que representa as habilidades da interface do usuário da exibição. Não há limitação em qual visualização implementa essa interface, portanto, diferentes “widgets” podem implementar a mesma interface de visualização e reutilizar a class do apresentador.

O apresentador deve atuar no final da solicitação para exibir a janela openfiledialog conforme sugerido. Como nenhum dado é requerido do modelo, o apresentador pode, e deve, manipular o pedido.

Vamos supor que você precise dos dados para criar algumas entidades em seu modelo. Você pode passar o stream para a camada de access, onde você tem um método para criar entidades do stream, mas sugiro que você lide com a análise do arquivo em seu apresentador e use um construtor ou um método Create por entidade em seu modelo.