Урок 4. Паттерн onClose

На прошлом уроке мы расширили приложение, добавив форму списка клиентов и карточку редактирования клиента. Список клиентов стал главной формой, а доступ к форме списка городов осуществляется через карточку клиента.

На этом уроке рассмотрим подход к улучшению опыта взаимодействия пользователя с формами для редактирования данных, а именно, паттерн onClose для проверки изменений на форме и уведомления о них пользователя при закрытии формы без сохранения.

Также рассмотрим, как обозначать обязательные поля на форме и проверять корректность их заполнения.

circle-check

Паттерн onClose

Суть паттерна заключается в том, чтобы при закрытии формы без сохранения проверять, были ли изменения на форме, и предупреждать о них пользователя. Тем самым исключать неприятную ситуацию потери данных, когда пользователь случайно закрыл форму, не сохранив изменения. Вопрос, который будем задавать пользователю, может выглядеть следующим образом:

По кнопке "Да" мы будем сохранять изменения перед тем, как закрыть форму. По кнопке "Нет" будет закрывать форму без сохранения. А по кнопке "Отмена" будем оставаться на форме и ждать дальнейших действий пользователя.

Перейдем в файл карточки клиента (TemplateClientEdit.xml). На ее примере будем рассматривать паттерн.

Проверка изменений на форме

Для отслеживания изменений у формы есть get-проперти FormChangedarrow-up-right, которое возвращает значение True, если изменилось значение хотя бы одного объекта.

circle-info

Также есть set-проперти FormChangedarrow-up-right, с его помощью можно вручную сбрасывать признак изменений на форме, но не сами изменения.

Давайте создадим условие, в котором будем проверять значение этого свойства:

circle-info

Значение свойства формы FormChangedarrow-up-right определяется совокупностью get-проперти ValueChangedarrow-up-right всех объектов на форме.

Свойство 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.

circle-info

У формы есть get-проперти ChangedObjectsarrow-up-right, которое возвращает список измененных объектов в виде строки:

С помощью этого get-проперти можно проверить, какие объекты формы изменились и повлияли на результат get-проперти формы FormChangedarrow-up-right.

Для вывода сообщений пользователю используется команда MessageBoxCommandarrow-up-right, которая открывает диалоговое окно сообщения с набором кнопок. Давайте создадим такую команду:

В атрибуте Type тэга <Buttons> указываем нужный нам набор кнопок.

Теперь нам нужен Execution, который по условию будет вызывать команду SaveOnCloseMessageBoxCommand.

На форме уже есть Execution, который обрабатывает событие закрытия формы:

В нем обрабатывается не только закрытие по крестику, которое отслеживает условие FormClosingConditionarrow-up-right. Также отслеживается нажатие клавиши Esc. За это отвечает условие EscapeKeyDownCondition типа KeyDownConditionarrow-up-right.

Давайте переделаем этот Execution так, чтобы он учитывал наличие изменений на форме и вызывал команду с диалоговым окном:

Обработка ответа пользователя на сообщение

Создадим условия, которые будут проверять, какую кнопку в диалоговом окне нажал пользователь. Команда MessageBoxCommand возвращает значение нажатой кнопки:

  • Yes - нажали кнопку "Да";

  • No - нажали кнопку "Нет";

  • Cancel - нажали кнопку "Отмена".

Достаточно создать два условия для кнопок "Да" и "Нет":

Создадим Execution для обработки нажатия кнопки "Да" диалогового окна:

А для обработки нажатия кнопки "Нет" диалогового окна создадим Execution, который помимо этого условия будет отрабатывать закрытие формы без изменений:

Запустите приложение. Убедитесь, что формы успешно загружены. Проверьте только что добавленный функционал.

Стоит отметить, что такой подход отлично работает, когда на форме нет полей, обязательных для заполнения, и полей, для которых важна корректность введенных данных. Если такие поля есть и заполнены некорректно, то при нажатии на кнопку "Да" диалогового окна в базу запишутся некорректные данные. Следовательно, при закрытии формы без сохранения мы должны проверять еще и корректность введенных данных.

Обязательные поля

Так как сущность "Клиент" будет одной из основных в нашем приложении, и клиент, что логично, не может быть без имени, поле "Имя (ФИО)" необходимо сделать обязательным для заполнения. Также сделаем обязательным поле "Город", так как дальнейшая логика будет привязываться к городам клиентов.

Дополнительной гарантией корректности данных будут ограничения на колонки в таблице template.client в базе данных. Перейдем в программу для управления СУБД PostgreSQL, и для нашей базы данных template_project выполним запросы, представленные ниже. Перед этим очистите таблицу от данных, либо заполните эти колонки во всех строках какой-либо информацией, чтобы не возникло ошибок с заданием ограничений на колонки.

Проверка обязательных полей

Для проверки текстового поля NameTextBox понадобится условие типа IsNullOrEmptyConditionarrow-up-right:

А для проверки выпадающего списка создадим условие типа IsNullConditionarrow-up-right:

Чтобы сообщить пользователю, что поля обязательны для заполнения, воспользуемся конструкцией Checkingarrow-up-right. Для этого перед тэгом <Executions> разместим тэг <Checkings>, в который расположим следующий код:

Назначение конструкции <Checking>в том, что при выполнении условия, описанного в тэге <ConditionExpression>, слева от объекта, указанного в тэге <Object>, будет отображаться красная полоска. При наведении курсора мыши на полоску будет всплывать подсказка с текстом из тэга <AsteriskHint>.

Предупреждение пользователя

Теперь на кнопку "Сохранить" необходимо повесить проверку заполнения обязательных полей.

Чтобы проверить, все ли поля заполнены, достаточно узнать, сработал ли хотя бы один Checking. Для этого у формы есть свойство CheckingFiredarrow-up-right. Создадим EqualConditionarrow-up-right, в котором проверим это свойство. И если оно равно False, то все поля корректно заполнены.

Это условие можно просто указать в тэге <Enabled> кнопки SaveButton, чтобы она стала неактивной. Так мы поступали с кнопками редактирования и удаления записей на формах списков клиентов и городов. Но такой подход не позволяет пользователю узнать, где он допустил ошибку, и почему кнопка неактивна.

Можно создать команду MessageBoxCommand, которая предупредит пользователя, если он попытается сохранить некорректные данные. Для этого в тэге <Commands> кнопки SaveButton нужно создать блок <If>, который определит, какая команда будет выполняться: либо команда сохранения данных, либо команда отображения сообщения о некорректном заполнении полей.

Но есть более изящный способ. У кнопки Buttonarrow-up-right есть режим DisabledModearrow-up-right, который включается через тэг <DisabledMode>. Кнопка всегда остается активной, даже если Enabled равен False. При нажатии на "неактивную" кнопку (Enabled = False) будет показываться сообщение с текстом из тэга <DisabledText>.

Скорректируем описание кнопки "Сохранить" с использованием DisabledMode. Для этого в тэге <Enabled> пропишем условие MandatoryFieldsAreFilledEqualCondition и добавим тэги <DisabledMode> и <DisabledText>.

Таким образом, общий синтаксис кнопки SaveButton будет выглядеть так:

Запустите приложение. Убедитесь, что формы успешно загружены. Проверьте только что добавленный функционал.

Паттерн onClose и обязательные поля

Изменения в паттерне будут заключаться в том, что, отловив событие закрытия формы и проверив наличие изменений, мы должны выяснить, все ли поля корректно заполнены. И если поля заполнены правильно, то задаем вопрос о сохранении, вызвав команду SaveOnCloseMessageBoxCommand. А если есть поля с некорректными данными, или не все обязательные поля заполнены, то мы должны предупредить пользователя об утере несохраненных изменений. В этом случае будем показывать сообщение вида:

По кнопке "Да" мы будем закрыть форму без сохранения изменений. По кнопке "Нет" будем оставаться на форме и ждать дальнейших действий пользователя.

Давайте опишем команду MessageBoxCommand, которая будет отображать это диалоговое окно:

Создадим условие проверки нажатия кнопки "Да" диалогового окна:

Скорректируем наш первый Execution следующим образом:

А в Execution на закрытие формы добавим условие CloseOnCloseMessageBoxCommandYesEqualCondition:

Запустите приложение. Убедитесь, что формы успешно загружены. Проверьте только что добавленный функционал.

На этом мы закончили рассматривать паттерн onClose.

Использование маски в текстовом поле

Давайте для поля "Контактный телефон" реализуем проверку формата введенного номера. Для этого в описание объекта PhoneTextBox добавим тэг <Mask> со значением PHONE. Таким образом, синтаксис текстового поля будет выглядеть так:

Запустите приложение и откройте карточку клиента. Проверьте, как теперь отображается поле "Контактный телефон".

Маска телефонного номера задает формат и ограничивает ввод символов в текстовое поле. Но нам нужно проверять, что пользователь ввел все необходимые данные. Для этого у объекта TextBoxarrow-up-right есть свойство MaskCompletedarrow-up-right. Создадим условие для проверки этого свойства:

Так как поле "Контактный телефон" не является обязательным для заполнения, то полноту введенных данных необходимо проверять только в том случае, если пользователь начал заполнять поле. Для этого создадим 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-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.

file-archive
317KB

Last updated