To jest moje skromne podejście do MVP i Twoich konkretnych problemów.
Po pierwsze , wszystko, z czym użytkownik może wchodzić w interakcje lub po prostu być pokazywane, to widok . Prawa, zachowanie i cechy takiego widoku są opisane przez interfejs . Ten interfejs można zaimplementować za pomocą interfejsu użytkownika WinForms, interfejsu konsoli, interfejsu internetowego lub nawet żadnego interfejsu użytkownika (zwykle podczas testowania prezentera) - konkretna implementacja nie ma znaczenia, o ile jest zgodna z prawami interfejsu widoku .
Po drugie , widok jest zawsze kontrolowany przez prezentera . Prawa, zachowanie i cechy takiego prezentera są również opisane przez interfejs . Ten interfejs nie jest zainteresowany implementacją widoku konkretnego, o ile jest zgodny z prawami interfejsu widoku.
Po trzecie , ponieważ prezenter kontroluje swój widok, aby zminimalizować zależności, tak naprawdę nie ma żadnej korzyści z tego, że widok w ogóle wie cokolwiek o swoim prezencie. Istnieje uzgodniona umowa między prezenterem a widokiem i jest to określone w interfejsie widoku.
Konsekwencje Trzeciego to:
- Prezenter nie ma żadnych metod, które może wywołać widok, ale widok zawiera zdarzenia, które prezenter może zasubskrybować.
- Prezenter zna swój pogląd. Wolę to osiągnąć za pomocą wtrysku konstruktora na betonowym prezencie.
- Widok nie ma pojęcia, który prezenter go kontroluje; po prostu nigdy nie otrzyma żadnego prezentera.
W przypadku Twojego problemu powyższy kod może wyglądać tak w nieco uproszczonym kodzie:
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()
{
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
{
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();
}
}
Oprócz powyższego zwykle mam IView
interfejs podstawowy , w którym przechowuję Show()
i dowolny widok właściciela lub tytuł, z którego zwykle korzystają moje widoki.
Na Twoje pytania:
1. Kiedy winform ładuje się, musi uzyskać widok drzewa. Czy mam rację sądząc, że widok powinien zatem wywołać metodę taką jak: presenter.gettree (), to z kolei oddeleguje do modelu, który pobierze dane do drzewa, utworzy go i skonfiguruje, zwróci do prezenter, który z kolei przejdzie do widoku, który następnie po prostu przypisze go, powiedzmy, do panelu?
Zadzwoniłbym IConfigurationView.SetTreeData(...)
z IConfigurationPresenter.ShowView()
, tuż przed wezwaniem doIConfigurationView.Show()
2. Czy wyglądałoby to tak samo dla każdej kontroli danych w Winform, ponieważ mam również datagridview?
Tak, wezwałbym IConfigurationView.SetTableData(...)
to. Sformatowanie podanych danych zależy od widoku. Prezenter po prostu przestrzega umowy widoku, że chce danych tabelarycznych.
3. Moja aplikacja ma kilka klas modeli z tym samym zestawem. Obsługuje również architekturę wtyczek z wtyczkami, które muszą być ładowane podczas uruchamiania. Czy widok po prostu wywołałby metodę prezentera, która z kolei wywołałaby metodę ładującą wtyczki i wyświetlającą informacje w widoku? Która warstwa będzie wtedy kontrolowała odwołania do wtyczek. Czy widok zawiera odniesienia do nich czy do prezentera?
Jeśli wtyczki są powiązane z widokami, widoki powinny o nich wiedzieć, ale nie prezenter. Jeśli dotyczą danych i modelu, widok nie powinien mieć z nimi nic wspólnego.
4. Czy mam rację sądząc, że widok powinien obsługiwać każdą rzecz dotyczącą prezentacji, od koloru węzła drzewa, po rozmiar datagrid, itp.?
Tak. Pomyśl o tym jak o prezenterce dostarczającej XML opisujący dane i widok, który pobiera dane i stosuje do nich arkusz stylów CSS. Mówiąc konkretnie, prezenter może zadzwonić, IRoadMapView.SetRoadCondition(RoadCondition.Slippery)
a widok renderuje drogę w kolorze czerwonym.
A co z danymi dla klikniętych węzłów?
5. Jeśli po kliknięciu treenodes powinienem przejść przez określony węzeł do prezentera, a następnie na tej podstawie prezenter powinien dowiedzieć się, jakich danych potrzebuje, a następnie poprosić model o te dane, zanim zaprezentuje je z powrotem w widoku?
Jeśli to możliwe, przekazałbym wszystkie dane potrzebne do przedstawienia drzewa w widoku w jednym ujęciu. Ale jeśli niektóre dane są zbyt duże, aby można je było przekazać od początku lub jeśli mają dynamiczny charakter i wymagają „najnowszej migawki” z modelu (przez prezentera), to dodałbym coś podobnego event LoadNodeDetailsEventHandler LoadNodeDetails
do interfejsu widoku, aby prezenter może go zasubskrybować, pobrać szczegóły węzła w LoadNodeDetailsEventArgs.Node
(prawdopodobnie za pośrednictwem pewnego rodzaju identyfikatora) z modelu, dzięki czemu widok może zaktualizować pokazane szczegóły węzła, gdy delegat programu obsługi zdarzeń powróci. Zwróć uwagę, że wzorce asynchroniczne mogą być potrzebne, jeśli pobieranie danych może być zbyt wolne, aby zapewnić dobre wrażenia użytkownika.