Как структурировать программу C# WinForms Model-View-Presenter (пассивное представление)?

StackOverflow https://stackoverflow.com/questions/4329776

Вопрос

Я разрабатываю графический интерфейс, который имеет следующую основную идею (аналогично смоделированному по образцу базового внешнего вида Visual Studio):

  1. Навигация по файлам
  2. Селектор элементов управления (для выбора того, что отображать в компоненте «Редактор»)
  3. редактор
  4. Регистратор (ошибки, предупреждения, подтверждения и т. д.)

На данный момент я буду использовать TreeView для навигации по файлам, ListView для выбора элементов управления, которые будут отображаться в редакторе, и RichTextBox для регистратора.Редактор будет иметь два типа режимов редактирования в зависимости от того, что выбрано в TreeView.Редактор будет либо RichTextBox для ручного редактирования текста внутри файлов, либо панелью с перетаскиванием DataGridViews и подтекстовыми полями для редактирования на этой панели.

Я пытаюсь следовать шаблону проектирования пассивного просмотра для полного отделения модели от представления и наоборот.Суть этого проекта в том, что любой добавляемый мною компонент может быть отредактирован/удален.Таким образом, мне нужна независимость от одного элемента управления к другому.Если сегодня я использую TreeView для навигации по файлам, а завтра мне говорят использовать что-то другое, то я хочу сравнительно легко реализовать новый элемент управления.

Я просто не понимаю, как структурировать программу.Я понимаю один Presenter для каждого элемента управления, но я не знаю, как заставить его работать так, чтобы у меня было представление (весь графический интерфейс программы) с элементами управления (подвидами), так что ВЕСЬ вид можно заменить, а также отдельный элементы управления, которые отражают мою модель.

В основном представлении, которое по стандартам пассивного просмотра должно быть облегченным, нужно ли мне реализовывать подпредставления по отдельности?Если да, скажем, у меня есть интерфейс INavigator для абстрагирования роли объекта Navigator.Навигатору потребуется презентатор и модель для взаимодействия между представлением навигатора и основным представлением.Я чувствую, что где-то теряюсь в жаргоне шаблонов проектирования.

Самый похожий вопрос можно найти здесь, но это не дает достаточно подробного ответа на мой вопрос.

Кто-нибудь, пожалуйста, помогите мне понять, как «структурировать» эту программу?Я ценю любую помощь.

Спасибо,

Дэниел

Это было полезно?

Решение

Абстракция хороша, но важно помнить, что в какой -то момент что-нибудь Должен знать кое -что о одной или двух вещах, иначе у нас будет просто куча красиво абстрактных Legos, сидящих на полу, а не собираются в дом.

Инъекция инверсии контроля/зависимости/Flippy-Dippy-ay-down-whatever-we're-calling-it-it в неделю контейнер как Автофак может действительно помочь объединить все это вместе.

Когда я собираю приложение Winforms, я обычно получаю повторяющуюся шаблон.

Я начну с Program.cs файл, который настраивает контейнер AutoFac, а затем получает экземпляр MainForm от этого и показывает MainForm. Анкет Некоторые люди называют это оболочкой, рабочим пространством или настольным компьютером, но, во всяком случае, это «форма», которая имеет строку меню и отображает либо детские окна, либо управление пользователем ребенка, и когда оно закрывается, приложение выходит.

Далее это вышеупомянутое MainForm. Анкет Я делаю основные вещи, такие как перетаскивание SplitContainers и MenuBarS и тому подобное в визуальном дизайнере Visual Studio, а затем я начинаю получать фантазию в коде: у меня будут определенные ключевые интерфейсы «введены» в MainFormКонструктор, чтобы я мог использовать их, чтобы моя основная форма могла организовать управление детьми, не зная о них так много.

Например, у меня может быть IEventBroker интерфейс, который позволяет различным компонентам публиковать или подписаться на «события», как BarcodeScanned или ProductSaved. Анкет Это позволяет части приложения реагировать на события в свободно связанном образом, не полагаясь на проводку традиционных событий .NET. Например, EditProductPresenter это идет вместе с моим EditProductUserControl можно сказать this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah)) и IEventBroker проверил бы его список подписчиков для этого события и позвонит их обратным вызорам. Например, ListProductsPresenter мог прослушать это событие и динамически обновить ListProductsUserControl что это прикреплено. Чистый результат состоит в том, что если пользователь сохраняет продукт в одном пользовательском управлении, докладчик другого пользователя может отреагировать и обновить себя, если он будет открыт, без какого -либо элемента управления, чтобы знать о существовании друг друга и без MainForm необходимость организовать это событие.

Если я проектирую приложение MDI, у меня может быть MainForm реализовать IWindowWorkspace интерфейс, который имеет Open() и Close() методы Я мог бы внедрить этот интерфейс в моих различных докладчиков, чтобы они могли открывать и закрывать дополнительные окна, не узнавая о MainForm напрямую. Например, ListProductsPresenter может захотеть открыть EditProductPresenter и соответствует EditProductUserControl Когда пользователь дважды щелкнет строку в сетке данных в ListProductsUserControl. Анкет Он может ссылаться на IWindowWorkspace-Что на самом деле MainForm, но это не нужно знать это-и звонить Open(newInstanceOfAnEditControl) и предположим, что контроль был показан в соответствующем месте приложения каким -то образом. ( MainForm Реализация, по -видимому, будет обмениваться видом на панель где -то на панели.)

Но как, черт возьми ListProductsPresenter Создайте Этот экземпляр EditProductUserControl? Делегатные фабрики Autofac Здесь истинная радость, поскольку вы можете просто ввести делегата в докладчик, и Autofac автоматически подключит его, как будто это был завод (псевдокод следует):


public class EditProductUserControl : UserControl
{
    public EditProductUserControl(EditProductPresenter presenter)
    {
        // initialize databindings based on properties of the presenter
    }
}

public class EditProductPresenter
{
    // Autofac will do some magic when it sees this injected anywhere
    public delegate EditProductPresenter Factory(int productId);

    public EditProductPresenter(
        ISession session, // The NHibernate session reference
        IEventBroker eventBroker,
        int productId)    // An optional product identifier
    {
        // do stuff....
    }

    public void Save()
    {
        // do stuff...
        this.eventBroker.Publish("ProductSaved", new EventArgs(this.product));
    }
}

public class ListProductsPresenter
{
    private IEventBroker eventBroker;
    private EditProductsPresenter.Factory factory;
    private IWindowWorkspace workspace;

    public ListProductsPresenter(
        IEventBroker eventBroker,
        EditProductsPresenter.Factory factory,
        IWindowWorkspace workspace)
    {
       this.eventBroker = eventBroker;
       this.factory = factory;
       this.workspace = workspace;

       this.eventBroker.Subscribe("ProductSaved", this.WhenProductSaved);
    }

    public void WhenDataGridRowDoubleClicked(int productId)
    {
       var editPresenter = this.factory(productId);
       var editControl = new EditProductUserControl(editPresenter);
       this.workspace.Open(editControl);
    }

    public void WhenProductSaved(object sender, EventArgs e)
    {
       // refresh the data grid, etc.
    }
}

Итак ListProductsPresenter знает о Edit Набор функций (т.е. Редактировать докладчик и Edit User Control)-и это прекрасно, они идут рука об руку-но это не нужно знать обо всех зависимости из Edit Набор функций, вместо этого полагаясь на делегата, предоставленного Autofac, для разрешения всех этих зависимостей для него.

Как правило, я обнаружил, что у меня есть соответствие между один на одно между «моделью ведущей/представления/контроллера» (давайте не будем слишком навязывать различия, так как в конце дня они все очень похожи) и ""UserControl/Form" UserControl Принимает докладчик/модель/контроллер представления в его конструкторе и сами по себе базы, как это является уместным, как можно больше. Некоторые люди скрывают UserControl от докладчика через интерфейс, например IEditProductView, что может быть полезно, если представление не является полностью пассивным. Я склонен использовать DataBinding для всего, поэтому общение делается через INotifyPropertyChanged И не беспокойтесь.

Но вы сделаете свою жизнь намного проще, если докладчик бесстыдно привязан к обзору. Разве свойство в вашей объектной модели не сетчалась с DataBinding? Разоблачить новую собственность так. У тебя никогда не будет EditProductPresenter и EditProductUserControl с одним макетом, а затем захотите написать новую версию пользовательского управления, которая работает с одним и тем же докладчиком. Вы просто отредактируете их обоих, они предназначены для всех намерений и цели с одной единицей, одной функцией, докладчиком существует только потому, что он легко проверяется модулем, а пользовательское управление - нет.

Если вы хотите, чтобы функция была заменена, вам нужно абстрагировать всю функцию как таковую. Так что у вас может быть INavigationFeature интерфейс, что ваш MainForm разговаривает с. Вы можете иметь TreeBasedNavigationPresenter это реализует INavigationFeature и потребляется TreeBasedUserControl. Анкет И у вас может быть CarouselBasedNavigationPresenter Это также реализует INavigationFeature и потребляется CarouselBasedUserControl. Анкет Пользователь управляет и докладчики все еще идут рука об руку, но ваши MainForm не пришлось бы заботиться, взаимодействует ли он с видом на дереве или на основе каруселей, и вы можете поменять их без MainForm быть мудрее.

В заключение легко запутать себя. Каждый педантик и использует немного другую терминологию, чтобы передать они тонкие (и часто не важные) различия между тем, что является сходными архитектурными моделями. По моему скромному мнению, инъекция зависимостей творит чудеса для создания композиционных, расширяющихся приложений, поскольку связь снижается; Разделение функций на «докладчики/модели/контроллеры представления» и «Просмотры/Управление/формы пользователя» творит чудеса для качества, поскольку большая часть логики втягивается в первое, что позволяет легко тестироваться на единицу; И объединение двух принципов, кажется, действительно то, что вы ищете, вы просто запутались в терминологии.

Или я могу быть полон этого. Удачи!

Другие советы

Я знаю, что этому вопросу почти 2 года, но я нахожусь в очень похожей ситуации.Как и вы, я рылся в Интернете ДНЯМИ и не нашел конкретного примера, который соответствовал бы моим потребностям - чем больше я искал, тем больше я продолжал возвращаться на одни и те же сайты снова и снова, пока у меня не было около 10 страниц фиолетового цвета. ссылки в Гугле!

В любом случае, мне интересно, придумали ли вы когда-нибудь удовлетворительное решение проблемы?Я опишу, как я это сделал на данный момент, основываясь на том, что я прочитал за последнюю неделю:

Моими целями были:Пассивная форма, ведущий первый (ведущий создает форму, чтобы форма не знала о его докладчике) вызовы в докладчике, поднимая события в форме (View)

Приложение имеет единственный FormMain, который содержит два пользовательских элемента управления:

ControlsView (имеет 3 кнопки) DocumentView (стороннее средство просмотра эскизов изображений)

«Основная форма» содержит панель инструментов для обычных операций сохранения файлов и т. д.и еще немного.Пользовательский элемент управления "ControlsView" позволяет пользователю нажимать кнопку "Сканировать документы" Он также содержит элемент управления древовидным представлением для отображения иерархии документов и страниц В "DocumentView" отображаются эскизы отсканированных документов

Мне действительно казалось, что у каждого элемента управления должна быть своя собственная триада MVP, а также основная форма, но я хотел, чтобы все они ссылались на одну и ту же модель.Я просто не мог понять, как скоординировать связь между органами управления.

Например, когда пользователь нажимает «Сканировать», ControlsPresenter берет на себя ответственность за получение изображений со сканера, и я хотел, чтобы он добавлял страницу в древовидное представление по мере того, как каждая страница возвращается со сканера — нет проблем, — но мне также нужна миниатюра появиться в DocumentsView одновременно (проблема, поскольку докладчики не знают друг о друге).

Мое решение заключалось в том, чтобы ControlsPresenter вызывал метод в модели для добавления новой страницы в бизнес-объект, а затем в модели я вызывал событие «PageAdded».

Затем у меня есть и ControlsPresenter, и DocumentPresenter, «прослушивающие» это событие, так что ControlsPesenter сообщает своему представлению о добавлении новой страницы в древовидное представление, а DocumentPresenter сообщает своему представлению о добавлении новой миниатюры.

Обобщить:

Представление элементов управления — вызывает событие «ScanButtonClicked».

Controls Presenter — слышит событие, вызывает класс Scanner для AcquireImages следующим образом:

GDPictureScanning scanner = new GDPictureScanning();

IEnumerable<Page> pages = scanner.AquireImages();
foreach (Page page in pages)
{
m_DocumentModel.AddPage(page);                
//The view gets notified of new pages via events raised by the model
//The events are subscribed to by the various presenters so they can 
//update views accordingly                
}

При сканировании каждой страницы цикл сканирования вызывает «возврат новой страницы (PageID)».Вышеупомянутый метод вызывает m_DocumentModel.AddPage(page).В модель добавляется новая страница, что вызывает событие.И презентатор элементов управления, и презентатор документа «слышат» событие и соответственно добавляют элементы.

Единственное, в чем я не уверен, — это инициализация всех докладчиков — я делаю это в Program.cs следующим образом:

static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

IDocumotiveCaptureView view = new DocumotiveCaptureView();
IDocumentModel model = new DocumentModel();
IDocumotiveCapturePresenter Presenter = new DocumotiveCapturePresenter(view, model);
IControlsPresenter ControlsPresenter = new ControlsPresenter(view.ControlsView, model);
IDocumentPresenter DocumentPresenter = new DocumentPresenter(view.DocumentView, model);

Application.Run((Form)view);                                                         
}

Не уверен, хорошо это, плохо или безразлично!

В любом случае, какой огромный пост по вопросу двухлетней давности - хотя было бы неплохо получить обратную связь...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top