Урок 6. Главная форма
Last updated
Last updated
На прошлом уроке мы рассмотрели разные варианты удаления связанных записей и остановились на варианте с архивированием.
На этом уроке добавим основную сущность нашего проекта: заказы. В последний раз подменим стартовую форму – теперь на ней будет отображаться список заказов, а доступ к спискам городов и клиентов предоставим через главное меню. Для списка клиентов реализуем работу с архивом, так как каждый заказ будет привязан к конкретному клиенту.
В этом уроке мы:
познакомимся с главным меню MainMenu;
рассмотрим SecondaryGetDataConnection.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
По итогам текущего урока у нас должны получиться формы вида:
Начнем с таблицы в базе данных.
Создайте таблицу template.order для заказов. В таблице будем хранить идентификатор клиента (client_id), номер заказа (order_number), дату и время заказа (order_date) и комментарий к заказу (description). Все поля, кроме комментария, должны иметь ограничение not null.
Для дат со временем используем тип timestamp without time zone, который не хранит информацию о временной зоне. В таком случае PostgreSQL предполагает, что все временные данные принадлежат одной временной зоне, и не производит никаких преобразований. О том, как происходит работа с временными зонами в платформе Workflow Technology, мы поговорим далее в уроке.
Для удаления заказов в таблице будем использовать колонку deleted типа boolean с ограничением not null и значение по умолчанию false. Значение в этой колонке будем обновлять через update-запрос при попытке удалить заказ. Таким образом, мы будем хранить в базе данных информацию обо всех когда-либо созданных заказах. А в одном из будущих уроков реализуем форму для просмотра удаленных заказов.
Мы меняли главную форму по мере появления новых сущностей, которые занимали роль основной сущности бизнес-процесса на момент их создания. Поначалу это был город, и на главной форме мы работали со списком городов. Затем появился клиент, и мы стали работать со списком клиентов. Теперь основной сущностью бизнес-процесса будет заказ, так как именно через заказ будет генерироваться основная выручка.
Давайте в последний раз заменим стартовую форму. Для этого файл TemplateStart.xml переименуем в TemplateClientList.xml, создадим форму для списка заказов и назовем ее TemplateStart.xml.
Для создания формы воспользуйтесь паттерном. Самостоятельно создайте все необходимы объект и элементы формы.
По завершению у вас должна получиться форма вида:
Таблица на форме списка заказов должна содержать колонки OrderId, ClientId, CityId, № (RowNumber), Номер заказа (OrderNumber), Дата и время заказа (OrderDate), Город (CityTitle) и Клиент (ClientTitle).
В запрос для получения списка заказов и PrimaryGetDataConnection для таблицы не добавляйте поля ClientTitle и CityTitle. Позже в уроке мы рассмотрим, как динамически подтягивать имена клиентов и названия городов в таблицу, имея только идентификаторы в колонках ClientId и CityId.
Для колонки OrderDate используйте вложенный необязательный тэг <DataType>
, задающий формат строки, в которую будет преобразовано значение из источника данных при заполнении таблицы:
Для удаления заказов реализуйте update-запрос на обновление значения в колонках deleted и date_deleted. Не забудьте в select-запросе на получение списка заказов исключить удаленные заказы, чтобы не отображать их на главной форме.
Реализуйте выделение нужной строки в таблице заказов после создания и редактирования заказа.
Команда INSERT
позволяет получать данные из добавленной записи без использования дополнительного запроса к базе данных. Для этого используется предложение RETURNING
.
С его помощью мы можем получить идентификатор добавленного заказа и вернуть его на форму. Результат запроса будет записан в результат выполнения команды SaveCommand, откуда его можно сохранить в параметр формы.
На кнопках используйте режим DisabledMode.
Создайте форму карточки заказа (TemplateOrderEdit.xml) с полями: Номер заказа (OrderNumberTextBox), Дата и время заказа (OrderDateDateTimePicker), Клиент (ClientComboBox), Город (CityTextBox), Телефон клиента (PhoneTextBox) и Комментарий к заказу (DescriptionTextBox).
Для выпадающего списка клиентов ClientComboBox создайте отдельный запрос на получения списка клиентов и назовите его, например, ClientShortSelectSqlQuery. Этот запрос должен возвращать поля ClientId, ClientTitle, CityTitle и Phone. Для запроса ClientShortSelectSqlQuery создайте отдельный SqlQueryPermission (например, ClientOrderViewSqlQueryPermission), который добавьте в роли OrderViewRole и OrderEditRole.
Поля "Город" (CityTextBox) и "Телефон клиента" (PhoneTextBox) оставьте без указания источника данных в тэге <Text>
и сделайте их нередактируемыми, указав признак <ReadOnly>
со значением True. Эти поля будут информативными и будут заполнятся данными из ClientShortSelectSqlQuery для клиента, выбранного в выпадающем списке ClientComboBox. Позже в уроке рассмотрим инструмент, который позволяет это сделать.
Так как пользователь не может редактировать значение объектов CityTextBox и PhoneTextBox, то исключим их из проверки свойства формы FormChanged, прописав в тэге <MyObject>
атрибут ChangeForm
со значением False.
Для того чтобы перевести текстовое поле "Комментарий к заказу" (DescriptionTextBox) в многострочный режим, добавьте в описание поля тэг <Multiline>
со значением True и настройте высоту поля.
Не забудьте на форме реализовать паттерн onClose.
По завершении у вас будет форма вида:
Так как в карточке заказа мы используем DateTimePicker для даты со временем, необходимо сделать отступление и поговорить о том, как происходит работа со временем и временными зонами в платформе. Подробно по этому вопросу можно почитать по ссылке, а сейчас затронем только те моменты, которые важны для текущей работы.
Дата со временем (в формате даты) между клиентом и сервером всегда передается в UTC. Когда дату со временем передаем с формы на сервер, платформа переводит время из временной зоны клиента в UTC, а сервер полученную дату переводит из UTC в свою временную зону.
Если в DateTimePicker используется только дата (без времени) или только время, то на сервер значение DateTimePicker отправится без перевода в UTC. Пример такой отправки мы наблюдаем в карточке клиента с полем "Дата рождения". А если в DateTimePicker используется дата и время, то они будут приведены к UTC. Эта логика будет отрабатываться в карточке заказа.
Аналогичное преобразование происходит и в обратную сторону. Сервер получает дату со временем из база данных, переводит ее в UTC и передает клиенту. Клиент, получив дату со временем, переводит ее из UTC в свою временную зону.
Даты со временем хранятся в базе данных во временной зоне сервера, по умолчанию это Etc/GMT. Чтобы задать свою временную зону, необходимо в серверный файл настроек (appsettings.json) внести строки:
В качестве значения вложенного поля TimeZone ожидается имя временной зоны в формате IANA. В базе данных в таблице public.time_zone_info в поле name указаны все доступные имена временных зон, поддерживаемые платформой.
Для хранения временной зоны клиента используется колонка time_zone_info_id в таблице public."user". По умолчанию там стоит идентификатор временной зоны Europe/Moscow. На временную зону клиента не влияют настройки часового пояса в системе Windows.
Давайте вернемся в файл карточки заказа (TemplateOrderEdit.xml) и доделаем поля "Город" (CityTextBox) и "Телефон клиента" (PhoneTextBox),
Для получения данных о выбранном клиенте будем использовать вторично загружающее соединение с данными SecondaryGetDataConnection, для которого в качестве источника данных укажем ClientPrimaryGetDataConnection и добавим фильтр по полю ClientId. В качестве значения фильтра будем брать значение из выпадающего списка ClientComboBox.
SecondaryGetDataConnection можно использовать на форме так же, как и PrimaryGetDataConnection, обращаясь к его полям, которые наследуются из источника. Например, для задания значения тэга <Text>
у объекта CityTextBox будем использовать запись вида:
Аналогично заполните тэг <Text>
у объекта PhoneTextBox.
Запустите проект и проверьте отображение информации о выбранном клиенте в карточке заказа.
Отлично! Теперь можем вернуться в файл главной формы (TemplateStart.xml). Оставшуюся часть урока полностью посвятим ей.
В таблице заказов остались незаполненными колонки "Город" (CityTitle) и "Клиент" (ClientTitle). Давайте рассмотрим механизм, который позволяет подтягивать в колонку таблицы значение из стороннего источника данных на основе значения из другой колонки таблицы.
Создайте ClientSimpleSelectSqlQuery с двумя полями ClientId и ClientTitle, и поместите его в разрешение ClientOrderViewSqlQueryPermission, так как он связан с просмотром заказов.
В файле главной формы создайте ClientPrimaryGetDataConnection на этот запрос. Этот DataConnection будем использовать в качестве источника данных для заполнения колонки ClientTitle таблицы OrderDatabaseTable.
Для подстановки значения в колонку используется вложенный тэг <Substitution>
у тэга <Column>
. Добавим в описание колонки ClientTitle таблицы OrderDatabaseTable тэг <Substitution>
:
Атрибут SourceColumn
тэга <Substitution>
ожидает имя колонки, на основе значения которой будут подставляться данные для отображения. В качестве значения тэга <Substitution>
ожидается матрица из двух колонок: первая должна содержать ключ, по которому будет происходить выборка значения, а вторая - самое значение.
Аналогично сделайте для колонки CityTitle. Создайте для этого новый запрос CitySimpleSelectSqlQuery, который будет возвращать список всех городов (архивных и неархивных), и создайте для него отдельное разрешение CityOrderViewSqlQueryPermission.
Запустите проект и проверьте корректность заполнения таблицы.
Сейчас у нас нет возможности просматривать списки клиентов и городов. Давайте добавим главное меню <MainMenu>
, чтобы иметь доступ к нашим спискам. Но для начала самостоятельно создайте команды ClientListFormShowCommand и CityListFormShowCommand для открытия форм списка клиентов и списка городов соответственно. А затем скопируйте представленный ниже код и добавьте его в файл стартовой формы перед описанием <MyObjects>
.
В пункте меню FileRefreshMenuItem (Обновить) в качестве значения атрибута Name
тэга <Command>
укажите имя команды на обновление PrimaryGetDataConnection, который используете в таблице заказов.
Если сейчас запустим проект, то на стартовой форме не увидим главное меню. Это связано с тем, что меню размещается в верхней части формы, там, где расположена панель HeadPanel, а точнее, под этой панелью. Давайте немного сдвинем панель вниз, задав координате <Top>
значение 24:
Запустим проект и проверим отображение главного меню.
Отлично! Теперь мы имеем доступ к нашим спискам и можем их редактировать.
В карточке клиента у нас есть кнопка CityListButton для доступа к списку городов. Ее можно удалить - она нам больше не пригодится. Удалите ее и все элементы, связанные с логикой обработки результата с формы списка городов.
Сейчас при изменении города или клиента главная форма не узнает об этом, и в таблице будет отображаться старая информация. Чтобы этот момент поправить, мы должны отлавливать наличие изменений на форме списка, передавать признак изменений на главную форму и обновлять соответствующий DataConnection.
На форме списка городов уже есть параметр Updated, которому присваиваем значение True, когда редактируем список городов. Добавьте такой же параметр на форму списка клиентов и изменяйте его значение, когда происходит редактирование списка клиентов.
Затем на главной форме для CityPrimaryGetDataConnection и ClientPrimaryGetDataConnection создайте отдельные команды DataConnectionRefreshCommand. Для каждой команды создайте отдельный Execution, который будет проверять параметр Updated у соответствующей команды FormShowCommand.
После добавления в проект сущности заказа, логика удаления клиента перестает корректно работать. Идентификатор записи клиента может использоваться в заказе, а следовательно, простое удаление связанной записи, как мы уже знаем, приведет к ошибке.
Реализуйте архивирование записей клиентов, как это было сделано в прошлом уроке для списка городов. Не забудьте исключить архивных клиентов из выпадающего списка в карточке заказа. А также не забудьте реализовать уведомление пользователя о невозможности удалить клиента, который используется в заказах.
В этом уроке мы окончательно определились с главной формой в нашем проекте. До конца практики ее внешний вид не будет сильно меняться. В следующем уроке мы добавим фильтры для упрощения работы со списком заказов. Рассмотрим различные виды фильтрации данных на форме и особенности их применения.
Также на уроке мы познакомились с объектом MainMenu, который применяется для организации классического доступа к различным функциям приложения.
Рассмотрели механизм подстановки данных в колонку таблицы DatabaseTable, который в коде реализуется через вложенный тэг <Substitution>
у тэга <Column>
.
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.