Esta é minha humilde opinião sobre MVP e seus problemas específicos.
Em primeiro lugar , qualquer coisa com a qual um usuário possa interagir, ou apenas ser mostrado, é uma visualização . As leis, comportamento e características de tal visão são descritos por uma interface . Essa interface pode ser implementada usando uma IU WinForms, uma IU de console, uma IU da web ou até mesmo nenhuma IU (geralmente ao testar um apresentador) - a implementação concreta simplesmente não importa, contanto que obedeça às leis de sua interface de visualização .
Em segundo lugar , uma visualização é sempre controlada por um apresentador . As leis, comportamento e características de tal apresentador também são descritos por uma interface . Essa interface não tem interesse na implementação de visão concreta, desde que obedeça às leis de sua interface de visão.
Terceiro , como um apresentador controla sua visualização, para minimizar as dependências, não há realmente nenhuma vantagem em ter a visualização sabendo alguma coisa sobre seu apresentador. Existe um contrato acordado entre o apresentador e a visão e isso é declarado pela interface da visão.
As implicações do Terceiro são:
- O apresentador não tem nenhum método que a visão possa chamar, mas a visão tem eventos que o apresentador pode assinar.
- O apresentador conhece sua visão. Eu prefiro fazer isso com injeção de construtor no apresentador de concreto.
- A visualização não tem ideia de qual apresentador a está controlando; apenas nunca será fornecido um apresentador.
Para o seu problema, o código acima pode ser parecido com este em um código um tanto 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<ISelectFilePresenter>();
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 exposto acima, geralmente tenho uma IView
interface básica onde guardo a Show()
e qualquer visualização do proprietário ou título de visualização da qual minhas visualizações geralmente se beneficiam.
Para suas perguntas:
1. Quando o winform carrega, ele deve obter uma visualização em árvore. Estou correto em pensar que a view deve, portanto, chamar um método como: presenter.gettree (), este por sua vez irá delegar ao modelo, que irá obter os dados para a treeview, criá-la e configurá-la, retorná-la ao apresentador, que por sua vez passará para a visão que então simplesmente o atribuirá a, digamos, um painel?
Eu ligaria IConfigurationView.SetTreeData(...)
de IConfigurationPresenter.ShowView()
, logo antes da ligação paraIConfigurationView.Show()
2. Isso seria o mesmo para qualquer controle de dados no Winform, já que também tenho um datagridview?
Sim, eu chamaria IConfigurationView.SetTableData(...)
por isso. Cabe à visualização formatar os dados fornecidos a ela. O apresentador simplesmente obedece ao contrato da visão de que deseja dados tabulares.
3. Meu aplicativo tem várias classes de modelo com a mesma montagem. Ele também oferece suporte a uma arquitetura de plug-ins com plug-ins que precisam ser carregados na inicialização. A visão simplesmente chamaria um método do apresentador, que por sua vez chamaria um método que carrega os plug-ins e exibe as informações na visão? Qual camada controlaria as referências do plugin. A visualização conteria referências a eles ou ao apresentador?
Se os plug-ins estiverem relacionados a visualizações, as visualizações devem conhecê-los, mas não o apresentador. Se eles tratam de dados e modelo, a visão não deve ter nada a ver com eles.
4. Estou correto em pensar que a visualização deve lidar com tudo sobre a apresentação, desde a cor do nó da visualização em árvore ao tamanho do datagrid, etc?
Sim. Pense nisso como o apresentador fornecendo XML que descreve os dados e a exibição que obtém os dados e aplica uma folha de estilo CSS a eles. Em termos concretos, o apresentador pode chamar IRoadMapView.SetRoadCondition(RoadCondition.Slippery)
e a visualização então renderiza a estrada na cor vermelha.
E quanto aos dados para nós clicados?
5. Se, ao clicar nos treenodos, devo passar pelo nó específico para o apresentador e, a partir daí, o apresentador trabalhará quais dados ele precisa e, em seguida, solicitará esses dados ao modelo, antes de apresentá-los de volta à exibição?
Se possível, eu passaria todos os dados necessários para apresentar a árvore em uma visualização de uma só vez. Mas se alguns dados forem muito grandes para serem transmitidos desde o início ou se forem dinâmicos em sua natureza e precisarem do "último instantâneo" do modelo (por meio do apresentador), eu adicionaria algo como event LoadNodeDetailsEventHandler LoadNodeDetails
a interface de visualização, para que o o apresentador pode se inscrever nele, buscar os detalhes do nó LoadNodeDetailsEventArgs.Node
(possivelmente por meio de seu ID de algum tipo) do modelo, de modo que a visualização possa atualizar os detalhes do nó mostrado quando o delegado do manipulador de eventos retornar. Observe que padrões assíncronos disso podem ser necessários se a busca de dados for muito lenta para uma boa experiência do usuário.