![]() | ||||||||||||||||||||||||||||||
![]() |
![]() |
|
|
|||||||||||||||||||||||||||
![]() |
|
MVC-подход к разработке пользовательских интерфейсов в Delphi. Часть 1. ГалочкаИсточник: habrahabrru/post/147133/ alan008
Под классическими приложениями я подразумеваю десктопные GUI-приложения для Windows на основе VCL. Про фреймворк FireMonkey, появившийся в новых версиях Delphi, пусть напишет статью кто-нибудь другой. Основная задача при создании пользовательского интерфейса придумать такое представление внутренних данных программы с помощью вышеназванных элементов, чтобы пользователю было удобно с этими данными работать. Ну или с другой стороны, придумать такой набор элементов, с помощью которого пользователь мог бы сказать программе то, что программа от него должна услышать. Говорят, что умные ребята за океаном, которые подходят к проектированию интерфейсов серьезно и обстоятельно, применяют и такие технологии, как отслеживание движений мыши при работе с программой (чтобы зря не возили туда обратно), подсчет кликов и даже слежение за направлением взгляда пользователя (если глаза начинают бегать в случайных направлениях, это уже должно настораживать).
Начнем с примитива. Галочка.Предположим, у вас в какой-то окне есть галочка (TCheckBox), отражающая выбор одного из двух вариантов. Чтобы говорить не о сферических конях в вакууме, придадим ей какой-то смысл. Пусть это будет окно импорта каких-то данных из файлов определенного каталога в базу данных. Галочка будет отражать, нужно ли удалять импортированные файлы после завершения операции. Тогда так и назовем нашу галочку
Кстати, давать префиксы именам контролов на основе типа контрола очень удобно. Например, cb для TCheckBox, rb для TRadioButton, bt для TButton. Если вы установите в Delphi пакет расширений cnPack, то при размещении очередного контрола на форме будет выскакивать окошечко с предложением сразу переименовать этот контрол в соответствии с вашими правилами. Это позволяет избежать засилья валяющихся на форме Button87, CheckBox32 и т.п. Как правило, после размещения контрола в нужном месте формы программист вздыхает с облегчением и успокаивается. Теперь он может из любого места программы обращаться к cbNeedDeleteFiles.Checked, чтобы узнать, выставлена галочка или нет. Вероятно, обращаться к свойству Checked программист будет не в единственном месте: при создании окна он может захотеть выставить умолчальное состояние данного свойства или сохраненное его значение, затем в основном месте (где выполняется импорт) нужно снова проверить этот атрибут, и, наконец, можно куда-то сохранить значение этого атрибута при закрытии окна, чтобы восстановить состояние галочки при следующем открытии окна. Также может потребоваться программно изменять значение этого атрибута на основе каких-то других условий. Например, пользователь может попросить, чтобы данная галочка всегда выставлялась автоматически при выборе каталога со входными файлами, если в данном каталоге содержатся только файлы одного строго определенного типа или если имена всех файлов каталога удовлетворяют определенной маске. И вот, в куче мест программы у нас появляется что-то подобное:
На первый взгляд ничего плохого тут нет. Но как показывает многолетняя практика, это УЖАСНО. Это и называется жесткой завязкой кода на пользовательский интерфейс. Допустим, впоследствии вам придется заменить TCheckBox на две радиокнопки: "Удалить импортированные файлы" и "Не удалять импортированние файлы". В этом не очень много смысла, но вы можете это сделать для лучшей визуализации или в рамках рефакторинга перед добавлением третьего состояния данной настройки типа "Удалить файлы только при отсутствии ошибок импорта". И в этот момент вам придется в куче мест, где раньше вы обращались к cbNeedDeleteFiles.Checked вставить какой-то код по работе с RadioButton'ами.
Как этого избежать?В сети давно и много трубят про MVC, MVP, MVVM. Будто это такие чудодейственные методики, следуя которым можно запрограммировать пользовательский интерфейс "правильно" и не иметь того гемора, который описан выше. На самом деле это лишь подходы, которые действительно помогают, но которые можно реализовать абсолютно по-разному в разных языках программирования и даже в одном языке. Т.е. это скорее советы, с какой стороны лучше подходить к программированию пользовательского интерфейса.
Далее необходимо связать состояние свойства NeedDeleteFiles с состоянием визуального компонента (TCheckBox'а) cbNeedDeleteFiles. Это удобно сделать через set метод свойства:
Зачем нужно условие FNeedDeleteFiles <> Value я поясню чуть позже. Главное, что теперь при присвоении значения свойству NeedDeleteFiles у нас будет автоматически выставляться галочка (это уже почти модель MVC - мы меняем значение элемента модели, а представление изменяется автоматически). Но это связь лишь в одну сторону - от внутренних данных к интерфейсу. Нужно еще добиться обратной связи - от представления (т.е. от галочки) к модели. Для этого в обработчике OnClick нашего чекбокса напишем такой код:
Т.е. действие над предствлением (в данном случает щелчок по галочке) приведет модель в соответствие с текущим состянием представления. Однако модель никогда не доверяет представлению и поэтому вызовет повторное приведение состояния представления к состоянию модели (принудительно выставит cbNeedDeleteFiles.Checked := FNeedDeleteFiles. Ничего страшного при этом не произойдет. И мы даже дополнительно застраховались проверкой if FNeedDeleteFiles <> Value от ситуации, что визуальный контрол снова вызовет обработчик OnClick. На самом деле он этого не сделает, т.к. там стоит аналогичная проверка:
Теперь у нас состояние свойства TfmImport.NeedDeleteFiles синхронизировано с состоянием галки cbNeedDeleteFiles.Checked в обе стороны. Во всех местах программы, где мы раньше обращались к cbNeedDeleteFiles.Checked теперь следует обращаться к свойству NeedDeleteFiles. Это позволяет нам полностью забыть о том, что представлением элемента NeedDeleteFiles является CheckBox. Вы даже не представляете, насколько это замечательно. Впоследствии мы можем заменить CheckBox на две радиокнопки или на что угодно и переписать нужно будет только set-метод SetNeedDeleteFiles (направление Model -> View) и обработчик, срабатывающий при изменении состояния представления, т.е. визульных компонентов (направление View -> Model). Я пропустил такой важный момент как первоначальную синхронизацию значения свойства NeedDeleteFiles с состоянием визуального компонента. Конечно, если при открытии окна ваша галка будет либо всегда выставлена, либо всегда снята, можно просто выставить правильное состояние в DesignTime, а соответствующее значение полю FNeedDeleteFiles присвоить в OnCreate класса формы. Однако это не очень надежно (за этим надо следить, легко допустить расхождение), поэтому в OnCreate у класса формы лучше разместить следующий код:
В следующей части статьи я постараюсь рассказать о более сложных случаях: работе в MVC-стиле со списками элементов (TListBox, TCheckListBox, TComboBox) и о подводных камнях при запоминании состояния визуальных элементов окна при его закрытии. Ссылки по теме
|
|