Какая архитектура программы лучше всего подходит для MDI-интерфейса?
Многодокументный интерфейс подразумевает, что в программе будет минимум две формы - Главная форма (контейнер) и форма, экземпляры которой будут создаваться внутри главной.
Первоначально я планировал отталкиваться в своей программе от классического MVC, но если один контроллер будет отвечать и за главную форму, и за дочерние, мне это кажется существенным нарушением, которое переусложнит контроллер.
Но ведь программы с MDI-интерфейсом достаточно распространены, и наверняка для них есть уже выверенная и хорошо подходящая архитектура. Подскажите, пожалуйста, какая именно?
P.S. На данный момент мне видится наиболее удачной следующая конфигурация:
Ответы (2 шт):
Для MDI
проектов можно использовать один из следующих распространённых архитектурных паттернов:
- MVC
- MVP
- MVVM
- Document-View
Вы можете использовать любой из представленных в силу вашего понимания каждого из них. Рассмотрим же организацию паттерна MVC
. Вообще стоит отметить, что более правильно было бы использовать MVVM
, который действует по правилам MVC
, но используется для настольных приложений и имеет некоторые особенности.
Если выходить из классического применения MVC
, контроллер должен отвечать за некоторую узкую область и предоставлять все необходимые методы для обработки данный, которые связаны с этой областью.
Исходя из требований в комментариях под вопросом, могу припустить, что было бы не плохой использовать отдельные контроллеры для каждой из форм. Управление дочерними формами можно сделать через передачу соответствующих параметров в контроллер, а так же можно хранить ссылки на дочерние формы и вызывать некоторые методы из родительской формы
Взаимодействие с родительским контроллером можно реализовать через callback
функцию, которую можно передать в конструкт при инициализации, либо в метод дочернего контроллера при его вызове. Логика выполнения примерно изображена на следующем рисунке (Простите за навыки рисования)
Так вы не будете перегружать основной контроллер действиями, которые ему не принадлежат, а сможете вынести их на уровень дочерних контроллеров, а так же сможете сохранить зависимость между контроллерами при надобности.
Для сохранения зависимости и общения между контроллерами я бы всё же выбрал бы stateless
подход. Суть в том, что бы всё взаимодействие проходило через базу данных либо сервисы с сохранением состояние. Но стоит отметить, такой подход вряд ли подойдёт, если вы хотите динамические изменения в программе без сохранения состояние. Структура такого подхода на следующем рисунке
Есть много выверенных и подходящих архитектур (MVC, MVP, MVVM) у которых у всех общий недостаток - они слишком абстрактны и мало отвечают на вопрос о том, как же на самом деле следует писать код.
На самом деле, MVC настолько абстрактна, что на Хабре до сих пор периодически спорят чем именно является форма - моделью, контроллером или видом
Поэтому, предлагаю спуститься немного ниже. И так, у вас есть две формы, и вам надо как-то связать их. И, как вы сами заметили, желательно сделать так, чтобы дочерняя форма не знала о родительской.
Сделать это можно тремя способами.
Способ 1. Через общую модель.
Дочерняя форма может поменять свойство в модели (желательно через дата-биндинг), модель отправит событие, главная форма отреагирует (желательно через дата-биндинг). В обратную сторону это тоже работает.
Разумеется, это не означает, что модели у главной и дочерней форм должны быть буквально общими. Помните, что модель, как и остальные компоненты MVC, может делиться на подмодели, как независимые, так и иерархически связанные. Передавайте в дочернюю форму ту часть модели, которая ей нужна.
Способ 2. Через события
Дочерняя форма может объявить событие, а главная форма - на него подписаться.
Способ 3. Через интерфейс
На случай, когда событий недостаточно или просто слишком много.
Дочерняя форма может запросить в конструкторе некоторый интерфейс (скажем, IChildFormOwner
или IChildFormHost
), а главная форма - его реализовать (напрямую или через внутренний класс).
Способ 4. Через задачи (Task)
Редкий способ, который применим, как правило, ровно в одном случае - в формах-диалогах, задача которых - запросить у пользователя данные. В таком случае жизненный цикл формы можно неплохо уложить в асинхронный метод, вроде ShowAsync:
private readonly TaskCompletionSource tcs = new();
private void bthOk_Click(object sender, EventArgs e)
{
tcs.TrySetResult();
Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
tcs.TrySetCanceled();
Close();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
tcs.TrySetCanceled();
base.OnFormClosed(e);
}
public Task ShowAsync()
{
Show();
Activate();
return tcs.Task;
}