Страницы

Поиск по вопросам

среда, 27 ноября 2019 г.

Как начать пользоваться MVP + WinForms?

#c_sharp #база_данных #winforms #mvp


Пишу приложение с использованием БД - Firebird. Компьютеры у людей не очень мощные
и WPF там тормозит. Поэтому необходимо на WinForms (прощай удобный MVVM). Узнал что
для удобной работы люди используют MVP. 

Есть какой-то вводный материал или статьи нормальные на эту тему, или может собственный
опыт у кого есть? Ибо самостоятельное выяснение нормальных результатов не дало.
    


Ответы

Ответ 1



Немного ссылок: Вводная от Википедии Model-View-Presenter и сопутствующие паттерны Особенности реализации MVP для Windows Forms (тут, на мой взгляд, немного наворочено, но для ознакомления тоже подойдет) Изложу также свой опыт. Для каждого экрана должно быть три модуля: модель, представление и презентер. Модель отвечает за работу с данными (загрузка/сохранение). Она является своеобразным фасадом к некоторому источнику данных или к слою доступа к данным. Ее задача -- загружать и сохранять данные согласно задачам конкретного экрана. Представление отвечает за пользовательский интерфейс. Это, по сути, и есть ваш экран. Представление вызывает методы презентера (например, в обработчиках событий), а также предоставляет методы для отображения данных (они вызываются презентером). Презентер отвечает за взаимодействие между представлением (которое умеет только показывать данные и реагировать на действия пользователя) и моделью (которая знает только про данные). Как правило, это включает в себя логику представления данных, валидацию и другие вещи, тесно связанные с интерфейсом. Направление ссылок получается следующим: представление <-> презентер -> модель. Важно запомнить, что эта тройка нужна для каждого экрана. Это не что-то единое для всего приложения. Иногда возможны исключения: в случае необходимости единого управления несколькими экранами может быть несколько представлений/моделей и всего один презентер. Например, на экране есть кнопка "Загрузить заказы". В обработчике кнопки вызывается соответствующий метод презентера -- LoadOrders(). Внутри метода презентера идет обращение к модели, внутри модели непосредственно загружаются данные. После того, как презентер получил от модели данные, он может как-то преобразовать их для показа или выполнить над ними какую-то логику. После этого данные либо возвращаются из метода, либо -- что более канонично -- вызывается метод представления SetOrders(), внутри которого данные уже непосредственно загружаются в какой-либо контрол. По коду получается следующая структура. Представление: interface IOrdersView { void SetOrders(Order[] orders); } class OrdersForm : Form, IOrdersView { private OrdersPresenter presenter; public void OrdersForm() { ... // презентер можно создавать в конструкторе, // а можно иметь отдельный метод инициализации экрана, // который будет вызываться сразу после создания формы, // создавать презентер и вызывать у него метод загрузки данных presenter = new OrdersPresenter(this, new OrdersModel()); } void btnLoadOrders_Click(...) { presenter.LoadOrders(); } void IOrdersView.SetOrders(Order[] orders) { // загружаем данные в контрол } } Презентер: class OrdersPresenter { private readonly IOrdersView view; private readonly IOrdersModel model; OrdersPresenter(IOrdersView view, IOrdersModel model) { this.view = view; this.model = model; } void LoadOrders() { var orders = model.LoadOrders(); view.SetOrders(orders); } } Модель: interface IOrdersModel { Order[] LoadOrders(); } class OrdersModel : IOrdersModel { Order[] IOrdersModel.LoadOrders() { // тут логика по загрузке } } Интерфейсы в принципе опциональны, но удобны для тестирования и моков. Вопросы из комментариев: А как мне допустим если в таблице (DataGridView) изменили запись, записать эти изменения в базу. Сразу причем. То есть как только закончили редактирование сразу в базу. Отслеживаете событие изменения ячейки/строки, вызываете презентер, передав ему измененную запись, дальше презентер при необходимости валидирует и передает запись на сохранение модели. А модель уже обращается к слою доступа к данным (DAL'у). В интерфейсе IOrdersView объявлен метод SetOrders(), который принимает значение типа Order[]. Но что это за тип? Где он описан? В данном случае это какой-то пользовательский тип. Важное тут -- что SetOrders() принимает некоторые данные, которые готовы для отображения и которые представление (в данном случае форма) знает, как отображать. Это может быть и DataTable, и массив строк. Что угодно. Во View Вы создаете экземпляр класса OrdersPresenter и в качестве параметра передаете экземпляр класса OrdersModel. Насколько это укладывается в концепцию? Разве View и Model не должны быть развязаны и ничего не знать друг о друге? В идеале -- да, представление и модель должны быть развязаны и не должны ничего знать друг о друге. Создание экземпляра модели внутри представление является некоторым упрощением, и в целом втискивается в шаблон, поскольку представление не использует модель явным образом. Если же оставаться пуристом, то есть следующие варианты: Создавать модель внутри презентера. Главный недостаток -- плохая тестируемость презентера, поскольку невозможно подменить модель своей реализацией (а в тестах на презентер она всегда подменяется). Обойти это можно имея в презентере два конструктора -- один создает модель по умолчанию, второй -- принимает модель извне. Хотя и тут найдутся пуристы, утверждающие, что иметь специальные члены, которые используются только в тестах, плохо. Поэтому я иду по простому пути и всегда создаю модель в представлении. Передавать в представление уже созданный презентер. Т.о. модель будет инициализирована по крайней мере вне текущего представления. Однако по большому счету это ничего не дает, т.к. текущее представление будет открываться из другого представления, и теперь уже другому представлению нужно будет что-то знать о модели. Правильно я понял, что если у меня в программе 100 таблиц и мне нужно 100 форм для работы с ними, то для каждой формы я должен содать свой интерфейс IOrdersModel и класс унаследованный от него, который может быть уже не Orders, а Person, например и свой Presenter? Интерфейс типа IOrdersView тоже для каждой формы создавать? В общем случае да, для каждой XXXForm у вам должны быть XXXView, XXXPresenter и XXXModel. Однако если форм действительно много и они очень однотипные, то, возможно, достаточно будет обобщенных IView, Presenter, IModel, где T -- конкретный тип редактируемой сущности.

Ответ 2



WPF там тормозит. Поэтому необходимо на WinForms (прощай удобный MVVM). Прощаться с MVVM не обязательно если реализация моделей в WPF-приложении не зависит от контролов, то модели можно перенести в WinForms. Т.к. в WinForms есть своя реализация bindings, но она немного менее удобная чем в WPF. Пример Model и View с bindings. Модель отделена от View, т.к. модель остается прежней, если поменять View. [STAThread] static void Main() { var m = new Model(); View.Show(m); } class View { static public void Show(object model) { var f = new Form(); var b = new RadioButton() { Parent = f, Dock = DockStyle.Fill }; // привязываем RadioButton.Checked к значению bool SomeValue b.DataBindings.Add("Checked", model, "SomeValue"); var t = new TextBox() { Parent = f, Dock = DockStyle.Top }; // привязываем Text к свойству int Number. привязка как TwoWay в WPF. var tb = t.DataBindings.Add("Text", model, "Number", true, DataSourceUpdateMode.OnPropertyChanged); // это как IValueConverter в WPF tb.Format += (s, e) => // транслируем данные из model.Number в TextBox.Text e.Value = e.Value + "!"; tb.Parse += (s, e) => { // транслируем TextBox.Text в model.Number. var m = Regex.Match(e.Value.ToString(), "\\d+"); e.Value = Convert.ChangeType(m.Success ? m.Value : "0", e.DesiredType); }; f.ShowDialog(); } } class Model : INotifyPropertyChanged { public Model() { // таймер - для изменения свойств модели. они будут выводиться в View. var t = new Timer() { Interval = 500 }; t.Tick += (s, e) => { this.SomeValue = !this.SomeValue; this.Changed("SomeValue"); }; t.Start(); } // событие необходимо для уведомлений о том, что изменились значения свойств public event PropertyChangedEventHandler PropertyChanged = delegate { }; private void Changed(string name) { this.PropertyChanged(this, new PropertyChangedEventArgs(name)); } // свойства public bool SomeValue { get; set; } public int Number { get { return _Number; } set { _Number = value; MessageBox.Show(value.ToString()); // тут это только для примера Changed("Number"); } } int _Number = 123; } После открытия формы RadioButton переключается каждый полсекунды. А если начать ввод значения в TextBox, то строка преобразуется в число и передается в Model.Number и откроется MessageBoх. Как видно, Model и View отделены друг от друга. И достаточно просто заменить View, не меняя при этом Model. Если Model определить в отдельной сборке, то ее можно использовать как в WinForms, так и в WPF приложениях.

Комментариев нет:

Отправить комментарий