2010-12-01 3 views
5

Я проектирование GUI, который имеет следующую основную идею (аналогично образцу Visual Studio, основной внешний вид и ощущение):Как структурировать программу C# WinForms Model-View-Presenter (Пассивный просмотр)?

  1. навигации Файл
  2. селектор управления (для выбора того, что для отображения в компоненте редактора)
  3. редактор
  4. Logger (ошибки, предупреждения, подтверждения и т.д.)

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

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

Я просто не понимаю, как структурировать программу. Я понимаю один Presenter per Control, но я не знаю, как заставить его работать так, что у меня есть View (весь графический интерфейс программы) с элементами управления (sub-Views), так что ENTIRE View можно сменить, а также индивидуально которые отражают мою модель.

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

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

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

Спасибо,

Daniel

ответ

22

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

Контейнер с инверсией управления/зависимой инъекцией/flippy-dippy-upside-down-whatever-we're-call-it-this-week, такой как Autofac, действительно может помочь собрать все это вместе.

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

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

Следующий приведенный выше номер MainForm. Я делаю основные вещи, такие как перетаскивание некоторых SplitContainers и MenuBar s и таких в визуальный конструктор Visual Studio, а затем я начинаю получать фантазии в коде: у меня будут определенные интерфейсы ключей, «введенные» в конструктор MainForm так что я могу их использовать, так что мой 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) и предположите, что элемент управления был показан в соответствующем месте приложения. (The MainForm реализация будет, по-видимому, поменять управление в поле зрения на панели где-нибудь.)

Но как, черт бы ListProductsPresenterсоздать этот экземпляр EditProductUserControl? Autofac's delegate factories являются истинная радость здесь, так как вы можете просто вводить делегат в ведущий и 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 функции set (т. е. редактор редактирования и пользовательский элемент управления редактирования) - и это прекрасно, они идут рука об руку - но ему не нужно знать обо всех зависимостях набора функций Edit , вместо этого полагаясь на делегата, предоставленного Autofac, чтобы разрешить все эти зависимости для него.

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

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

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

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

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

+0

Спасибо за ответ. Я не пытаюсь погрузиться в философский жаргон, который может быть вдохновлен разговорами о шаблонах дизайна, но я пытаюсь получить некоторые технические знания под моим поясом. Таким образом, этот пассивный взгляд действительно усугубляет меня. Как я могу создать с нуля мастер-представление с субвью внутри него (чтобы весь основной вид был взаимозаменяемым, а также любой из подзонов внутри главного представления)? Я не могу найти ничего в сети, чтобы правильно решить эту проблему (возможно, никто еще не решил ее окончательно?). – 2010-12-02 16:38:51

+0

Этот ответ более 6 лет, и все же это одно из лучших объяснений практического использования MVP, которое я нашел в Интернете. Спасибо, это невероятно образовательно, даже если программирование WinForms стало несколько устаревшим в наши дни. – Tarec 2017-04-24 11:00:10

2

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

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

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

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

ControlsView (имеет 3 кнопки) DocumentView (3-й стороннее изображение, просмотр миниатюр)

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

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

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

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

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

Резюмируя:

управления View - вызывает событие "ScanButtonClicked"

управления 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     
} 

Поскольку каждая страница отсканирована , цикл сканирования вызывает «yield return new Page (PageID)». Вышеуказанный метод вызывает m_DocumentModel.AddPage (страница). Новая страница добавляется в модель, что вызывает событие. Оба управляющих презентатора и ведущий документа «слышат» событие и соответственно добавляют элементы.

бит я не «уверен» о том, инициализация всех предъявителей - я делаю это в 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);               
} 

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

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