Урок 4. Паттерн onClose
Last updated
Last updated
На прошлом уроке мы расширили приложение, добавив форму списка клиентов и карточку редактирования клиента. Список клиентов стал главной формой, а доступ к форме списка городов осуществляется через карточку клиента.
На этом уроке рассмотрим подход к улучшению опыта взаимодействия пользователя с формами для редактирования данных, а именно, паттерн onClose для проверки изменений на форме и уведомления о них пользователя при закрытии формы без сохранения.
Также рассмотрим, как обозначать обязательные поля на форме и проверять корректность их заполнения.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
Суть паттерна заключается в том, чтобы при закрытии формы без сохранения проверять, были ли изменения на форме, и предупреждать о них пользователя. Тем самым исключать неприятную ситуацию потери данных, когда пользователь случайно закрыл форму, не сохранив изменения. Вопрос, который будем задавать пользователю, может выглядеть следующим образом:
По кнопке "Да" мы будем сохранять изменения перед тем, как закрыть форму. По кнопке "Нет" будет закрывать форму без сохранения. А по кнопке "Отмена" будем оставаться на форме и ждать дальнейших действий пользователя.
Перейдем в файл карточки клиента (TemplateClientEdit.xml). На ее примере будем рассматривать паттерн.
Для отслеживания изменений у формы есть get-проперти FormChanged, которое возвращает значение True, если изменилось значение хотя бы одного объекта.
Также есть set-проперти FormChanged, с его помощью можно вручную сбрасывать признак изменений на форме, но не сами изменения.
Давайте создадим условие, в котором будем проверять значение этого свойства:
Значение свойства формы FormChanged определяется совокупностью get-проперти ValueChanged всех объектов на форме.
Свойство ValueChanged обозначает, было ли изменено значение объекта в процессе работы.
Чтобы форма исключала объект из проверки для определения значения свойства FormChanged, необходимо при описании объекта в тэге <MyObject>
указать необязательный атрибут ChangeForm
со значением False.
Также у тэга <MyObject>
есть необязательный вложенный тэг <Change>
, который задает настройки изменения свойства ValueChanged:
<Change User="True" Source="False" ValueSet="True" />
Атрибут User
относится к графическому интерфейсу и определяет, будет ли свойство ValueChanged иметь значение True, если пользователь изменит значение объекта;
Атрибут Source
определяет, какое значение будет присвоено свойству ValueChanged, если значение объекта обновится из источника. Если атрибут Source
имеет значение False, и при этом значение из источника обновится, то ValueChanged присваивается значение False;
Атрибут ValueSet
также определяет, какое значение будет присвоено свойству ValueChanged, если значение объекта будет присвоено из команды ValueSetCommand. Если атрибут ValueSet
имеет значение False, и при этом значение было присвоено из команды ValueSetCommand, то свойство ValueChanged будет иметь значение False.
У формы есть get-проперти ChangedObjects, которое возвращает список измененных объектов в виде строки:
С помощью этого get-проперти можно проверить, какие объекты формы изменились и повлияли на результат get-проперти формы FormChanged.
Для вывода сообщений пользователю используется команда MessageBoxCommand, которая открывает диалоговое окно сообщения с набором кнопок. Давайте создадим такую команду:
В атрибуте Type
тэга <Buttons>
указываем нужный нам набор кнопок.
Теперь нам нужен Execution, который по условию будет вызывать команду SaveOnCloseMessageBoxCommand.
На форме уже есть Execution, который обрабатывает событие закрытия формы:
В нем обрабатывается не только закрытие по крестику, которое отслеживает условие FormClosingCondition. Также отслеживается нажатие клавиши Esc. За это отвечает условие EscapeKeyDownCondition типа KeyDownCondition.
Давайте переделаем этот Execution так, чтобы он учитывал наличие изменений на форме и вызывал команду с диалоговым окном:
Создадим условия, которые будут проверять, какую кнопку в диалоговом окне нажал пользователь. Команда MessageBoxCommand возвращает значение нажатой кнопки:
Yes - нажали кнопку "Да";
No - нажали кнопку "Нет";
Cancel - нажали кнопку "Отмена".
Достаточно создать два условия для кнопок "Да" и "Нет":
Создадим Execution для обработки нажатия кнопки "Да" диалогового окна:
А для обработки нажатия кнопки "Нет" диалогового окна создадим Execution, который помимо этого условия будет отрабатывать закрытие формы без изменений:
Запустите приложение. Убедитесь, что формы успешно загружены. Проверьте только что добавленный функционал.
Стоит отметить, что такой подход отлично работает, когда на форме нет полей, обязательных для заполнения, и полей, для которых важна корректность введенных данных. Если такие поля есть и заполнены некорректно, то при нажатии на кнопку "Да" диалогового окна в базу запишутся некорректные данные. Следовательно, при закрытии формы без сохранения мы должны проверять еще и корректность введенных данных.
Так как сущность "Клиент" будет одной из основных в нашем приложении, и клиент, что логично, не может быть без имени, поле "Имя (ФИО)" необходимо сделать обязательным для заполнения. Также сделаем обязательным поле "Город", так как дальнейшая логика будет привязываться к городам клиентов.
Дополнительной гарантией корректности данных будут ограничения на колонки в таблице template.client в базе данных. Перейдем в программу для управления СУБД PostgreSQL, и для нашей базы данных template_project выполним запросы, представленные ниже. Перед этим очистите таблицу от данных, либо заполните эти колонки во всех строках какой-либо информацией, чтобы не возникло ошибок с заданием ограничений на колонки.
Для проверки текстового поля NameTextBox понадобится условие типа IsNullOrEmptyCondition:
А для проверки выпадающего списка создадим условие типа IsNullCondition:
Чтобы сообщить пользователю, что поля обязательны для заполнения, воспользуемся конструкцией Checking. Для этого перед тэгом <Executions>
разместим тэг <Checkings>
, в который расположим следующий код:
Назначение конструкции <Checking>
в том, что при выполнении условия, описанного в тэге <ConditionExpression>
, слева от объекта, указанного в тэге <Object>
, будет отображаться красная полоска. При наведении курсора мыши на полоску будет всплывать подсказка с текстом из тэга <AsteriskHint>
.
Теперь на кнопку "Сохранить" необходимо повесить проверку заполнения обязательных полей.
Чтобы проверить, все ли поля заполнены, достаточно узнать, сработал ли хотя бы один Checking. Для этого у формы есть свойство CheckingFired. Создадим EqualCondition, в котором проверим это свойство. И если оно равно False, то все поля корректно заполнены.
Это условие можно просто указать в тэге <Enabled>
кнопки SaveButton, чтобы она стала неактивной. Так мы поступали с кнопками редактирования и удаления записей на формах списков клиентов и городов. Но такой подход не позволяет пользователю узнать, где он допустил ошибку, и почему кнопка неактивна.
Можно создать команду MessageBoxCommand, которая предупредит пользователя, если он попытается сохранить некорректные данные. Для этого в тэге <Commands>
кнопки SaveButton нужно создать блок <If>
, который определит, какая команда будет выполняться: либо команда сохранения данных, либо команда отображения сообщения о некорректном заполнении полей.
Но есть более изящный способ. У кнопки Button есть режим DisabledMode, который включается через тэг <DisabledMode>
. Кнопка всегда остается активной, даже если Enabled равен False. При нажатии на "неактивную" кнопку (Enabled = False) будет показываться сообщение с текстом из тэга <DisabledText>
.
Скорректируем описание кнопки "Сохранить" с использованием DisabledMode. Для этого в тэге <Enabled>
пропишем условие MandatoryFieldsAreFilledEqualCondition и добавим тэги <DisabledMode>
и <DisabledText>
.
Таким образом, общий синтаксис кнопки SaveButton будет выглядеть так:
Запустите приложение. Убедитесь, что формы успешно загружены. Проверьте только что добавленный функционал.
Изменения в паттерне будут заключаться в том, что, отловив событие закрытия формы и проверив наличие изменений, мы должны выяснить, все ли поля корректно заполнены. И если поля заполнены правильно, то задаем вопрос о сохранении, вызвав команду SaveOnCloseMessageBoxCommand. А если есть поля с некорректными данными, или не все обязательные поля заполнены, то мы должны предупредить пользователя об утере несохраненных изменений. В этом случае будем показывать сообщение вида:
По кнопке "Да" мы будем закрыть форму без сохранения изменений. По кнопке "Нет" будем оставаться на форме и ждать дальнейших действий пользователя.
Давайте опишем команду MessageBoxCommand, которая будет отображать это диалоговое окно:
Создадим условие проверки нажатия кнопки "Да" диалогового окна:
Скорректируем наш первый Execution следующим образом:
А в Execution на закрытие формы добавим условие CloseOnCloseMessageBoxCommandYesEqualCondition:
Запустите приложение. Убедитесь, что формы успешно загружены. Проверьте только что добавленный функционал.
На этом мы закончили рассматривать паттерн onClose.
Давайте для поля "Контактный телефон" реализуем проверку формата введенного номера. Для этого в описание объекта PhoneTextBox добавим тэг <Mask>
со значением PHONE. Таким образом, синтаксис текстового поля будет выглядеть так:
Запустите приложение и откройте карточку клиента. Проверьте, как теперь отображается поле "Контактный телефон".
Маска телефонного номера задает формат и ограничивает ввод символов в текстовое поле. Но нам нужно проверять, что пользователь ввел все необходимые данные. Для этого у объекта TextBox есть свойство MaskCompleted. Создадим условие для проверки этого свойства:
Так как поле "Контактный телефон" не является обязательным для заполнения, то полноту введенных данных необходимо проверять только в том случае, если пользователь начал заполнять поле. Для этого создадим Condition, в котором будем проверять, пустое ли поле:
Значение объекта PhoneTextBox сравниваем с символом "+", т.к. маска начинается с этого символа.
Создадим Checking для PhoneTextBox:
Запустите приложение и откройте карточку клиента. Проверьте отображение сообщения об ошибке при заполнении текстового поля "Контактный телефон".
Давайте поправим один момент. Сейчас при сохранении информации о клиенте, если поле "Контактный телефон" оставили пустым, в базу запишется строка "+". Чтобы в таком случае в таблицу template.client в базе данных в колонку phone писалось значение NULL, необходимо в ClientInsertSetDataConnection и ClientUpdateSetDataConnection изменить описание параметра Phone на следующий код:
На форме списка клиентов (TemplateStart.xml) на кнопки редактирования и удаления записей добавьте режим DisabledMode;
На форме списка городов (TemplateCityList.xml) на кнопки редактирования и удаления записей добавьте режим DisabledMode;
На форме карточки города (TemplateCityEdit.xml) поле "Наименование" сделайте обязательным для заполнения. Не забудьте про ограничение на колонку в таблице в базе данных;
На форме карточки города (TemplateCityEdit.xml) на кнопку "Сохранить" добавьте режим DisabledMode;
На форме карточки города (TemplateCityEdit.xml) реализуйте паттерн onClose;
На форме списка городов (TemplateCityList.xml) реализуйте паттерн onClose.
На уроке мы рассмотрели паттерн onClose, суть которого заключается в том, чтобы определить, были ли изменения на форме, и предупредить пользователя, если он захочет закрыть форму без сохранения.
Также мы узнали, как можно уведомить пользователя об обязательных полях, и как такие поля повлияют на использование паттерна onClose.
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.