Урок 3. Выпадающий список
Last updated
Last updated
Продолжим совершенствовать наш проект. Создадим форму для списка клиентов и карточку клиента. И заменим стартовую форму проекта. Теперь это будет форма списка клиентов.
На этом уроке:
познакомимся со сниппетами, которые ускоряют разработку форм;
рассмотрим применение SetDataConnection;
рассмотрим возможность передавать значения параметров в момент вызова команды.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
Форма списка клиентов должна содержать таблицу и три кнопки редактирования списка. Сохранение данных в таблицу в базе данных будет происходить на форме карточки клиента. Поэтому на форме списка кнопка "Сохранить" и FootPanel будут отсутствовать.
Для создания формы воспользуйтесь паттерном Empty Form (пустая форма с хэдером).
После того как создали файл формы, необходимо подменить стартовую форму. В конфигурационном файле клиентской части (WorkflowForms.dll.config) в параметре StartFormFileName указан путь до стартовой формы. Его оставим без изменений. А переименуем сами xml-файлы форм. Для этого перейдем в папку форм проекта Template\Projects\1. Template\Forms\ и переименуем файлы:
файл TemplateStart.xml переименуем в TemplateCityList.xml, т.к. это форма списка городов;
а только что созданный TemplateClientList.xml переименуем в TemplateStart.xml, таким образом будущий список клиентов станет стартовой формой.
После этого перейдем в Eclipse и обновим состояние нашего проекта в окне Project Explorer, чтобы редактор увидел изменения в именах файлов. Для этого выберем наш проект и нажмем клавишу F5, либо кликнем правой кнопкой мыши на имени проекта и в открывшемся меню выберем пункт Refresh. Затем перейдем в файл формы списка городов и в тэге <Form>
исправим значение атрибута Name на TemplateCityListForm. Это необходимо, чтобы в журнале событий ошибки имели правильное и уникальное имя формы, и мы могли быстро найти нужный файл.
Перезапустите проект и убедитесь, что стартовая форма успешно заменилась новым файлом.
Самостоятельно создайте необходимые элементы формы:
таблицу клиентов с колонками ClientId, № (RowNumber), ФИО (Name), Город (CityTitle) и Телефон (Phone);
три кнопки редактирования списка. Не забудьте про условие активности кнопок, если выделена строка таблицы.
Пока нужен только интерфейс.
В результате у вас должна получиться похожая форма:
Перейдем в программу для управления СУБД PostgreSQL. И для нашей базы данных template_project выполним следующий скрипт:
Обратите внимание, что для колонки client_id используем тип bigint, а для city_id используем тип smallint. Это связано с тем, что таблица template.client потенциально может иметь количество записей, которое превысит максимальное значение типа smallint, равное 32767. В то время, как для таблицы template.city тип bigint будет избыточным.
Перейдем в файл описания работы серверной части приложения (Template.xml). Добавим запрос для получения списка клиентов:
Создайте ClientViewSqlQueryPermission с этим запросом и дайте права доступа группе GuestGroup.
На форме списка клиентов (TemplateStart.xml) создайте PrimaryGetDataConnection на этот запрос и укажите его в тэге <SourceDataConnection>
для таблицы ClientDatabaseTable.
Перезапустите проект. Убедитесь, что форма успешно загружена, и в журнале нет никаких сообщений об ошибках.
Отлично! Теперь можем заняться формой для карточки клиента.
Карточка клиента должна содержать поля: ФИО, Дата рождения, Город, Контактный телефон и E-mail. Поле "Город" будет представлено в виде выпадающего списка. Справа от поля "Город" будет кнопка, по которой будет открываться существующая форма со списком городов.
Скачайте новый архив с шаблонами по ссылке ниже. Замените содержимое папки с шаблонами, на которую ссылаются свойства проекта, на содержимое из архива.
С помощью шаблона Entity Form создайте форму для карточки клиента со следующими параметрами:
Вернемся в файл стартовой формы (TemplateStart.xml). Создадим команду FormShowCommand для формы карточки клиента, в которой сразу укажем два параметра:
Форма списка клиентов в таблице содержит колонки не для всех полей, которые описаны в задании на карточку клиента. Нет смысла перегружать sql-запрос для списка клиентов лишними полями, которые понадобятся на другой форме. В карточке клиента будем использовать дополнительный sql-запрос, в котором для конкретного клиента будем получать все необходимые данные. Для этого нам и понадобится параметр ClientId. В файле карточки клиента (TemplateClientEdit.xml) эти параметры уже есть.
Также мы не будем делать отдельные команды открытия формы на добавление и редактирование. Нам хватит и одной. А значения параметров будем подставлять в месте вызова команды. В этом нам поможет универсальное значение Input, которое используется только при описании команд и позволяет сократить дублирование кода.
Для параметра Edit в качестве значения укажем <Input>
со значением по умолчанию False. А для параметра ClientId укажем <Input>
без значения.
Для открытия формы в режиме добавления записи нам не нужно передавать ClientId. А для параметра Edit мы уже указали значение по умолчанию. Таким образом, на кнопке ClientAddButton в тэге <Commands>
пропишем простой вызов команды по имени:
Для открытия формы в режиме редактирования в команду ClientEditFormShowCommand через Input будем передавать нужные значения параметров. И так как открыть форму карточки клиента можно либо по кнопке "Редактировать запись...", либо по двойному клику по строке в таблице, мы создадим SequentialCommand, в которой будем вызывать ClientEditFormShowCommand:
Укажем команду ClientEditSequentialCommand в тэге <Commands>
кнопки "Редактировать запись...".
Добавьте самостоятельно обработку двойного клика по строке таблицы и вызов команды ClientEditSequentialCommand.
Также самостоятельно создайте команду типа DataConnectionRefreshCommand на ClientPrimaryGetDataConnection, которая будет выполняться в Execution по изменению параметра Updated у команды ClientEditFormShowCommand.
Перезапустите проект. Убедитесь, что форма успешно загружена, и карточка клиента открывается без сообщений об ошибках.
Отлично! Теперь можем приступить к наполнению формы карточки клиента объектами. Но для начала реализуем запрос и загружающее соединение с данными для получения информации по клиенту на форме.
Перейдем в файл описания работы серверной части приложения (Template.xml). Добавим запрос для получения данных по конкретному клиенту:
Добавим его в ранее созданный ClientViewSqlQueryPermission.
Давайте сразу сделаем запросы на сохранение изменений в таблицу template.client:
Запрос ClientInsertSqlQuery возвращает идентификатор client_id новой записи. Этот идентификатор будем использовать на формах, как результат выполнения команды SaveCommand. Но об этом позже, а пока настроим права доступа к новым запросам.
Соберем новые запросы в ClientEditSqlQueryPermission:
И сделаем ClientEditRole:
Скорректируем права доступа для GuestGroup:
Перейдем в файл карточки клиента (TemplateClientEdit.xml) и первым делом создадим PrimaryGetDataConnection:
Создавать объекты на форме можно по одному: отдельно Label, отдельно связанный с ним TextBox. А можно воспользоваться сниппетом, который вставит пару объектов с корректной привязкой координат относительно друг друга и контейнера, в котором они располагаются.
Вставить сниппет можно из окна автозаполнения. Разместим курсор внутри ContentPanel и начнем писать "Lab". Нажмите на сочетание клавиш Ctrl+Space, чтобы открылось окно автозаполнения.
Редактор подсказывает нам три сниппета: "Label + ComboBox", "Label + DateTimePicker" и "Label + TextBox". Справа от имени сниппета указывается его короткое имя. Например, lcombox. По этому имени также можно искать конкретный сниппет. Эти сниппеты пригодятся нам при создании объектов на форме.
Выберем сниппет "Label + TextBox" и нажмем Enter. Откроется окно настроек:
Последние два поля оставим пустыми, т.к. в ContentPanel у нас еще нет никаких объектов, к которым можно привязаться. Жмем Finish.
Редактор на место курсора подставит код наших объектов NameLabel и NameTextBox. Также откроет блокнот с текстом подсказки необходимых изменений. Сейчас эта подсказка нас не интересует - все необходимые доработки уже существуют.
Давайте смотреть, что добавил редактор. В объекте NameLabel редактор подсвечивает ошибки:
Это из-за того, что последние два поля в настройках сниппета мы оставили пустыми. Давайте заменим значения для тэгов <Top>
и <Left>
на 5 и 10 соответственно.
Вторым интересным моментом является то, что редактор для объекта NameTextBox в тэг <Text>
сразу подставил наше соединение с данными ClientPrimaryGetDataConnection и указал нужное поле.
Перейдем в приложение и нажмем кнопку "Добавить запись...". Убедимся, что карточка клиента успешно загружена, и проверим расположение объектов.
Сначала доделаем все поля на форме, а затем скорректируем ее размеры.
Для создания поля "Дата рождения" воспользуемся сниппетом "Label + DateTimePicker":
Теперь заполним два последних поля. И укажем в них ранее созданные поля.
Жмем Finish.
Обратите внимание на значения тэгов <Top>
у DateOfBirthLabel и DateOfBirthDateTimePicker. Отступ между объектами NameTextBox и DateOfBirthLabel равен 5 пикселей. А отступ между DateOfBirthLabel и DateOfBirthDateTimePicker - нулевой. Такие значения отступов позволяют визуально отделить поля одного свойства от полей другого свойства.
Редактор для объекта DateOfBirthDateTimePicker в тэг <Value>
сразу подставил наше соединение с данными ClientPrimaryGetDataConnection и указал нужное поле.
Перейдем в приложение и убедимся, что карточка клиента успешно загружена, и проверим расположение объектов.
Внесем небольшие правки в описание объекта DateOfBirthDateTimePicker. Во-первых, изменим его ширину, прописав в тэг <Width>
значение 150, чтобы поле выглядело симпатичнее. Во-вторых, изменим значение атрибута Show у тэга <NullValue>
на True. Атрибут Show определяет, может ли дата иметь значение NULL.
В итоге синтаксис объекта DateOfBirthDateTimePicker будет выглядеть так:
По заданию поле "Город" должно быть сделано в виде выпадающего списка (ComboBox). Поэтому для его создания воспользуемся сниппетом "Label + ComboBox":
Помимо знакомых уже полей настроек, этот сниппет имеет две специальных настройки: Имя схемы БД и Имя таблицы БД. Они необходимы для автоматического создания SqlQuery, на основе которого будет заполняться выпадающий список.
Жмем Finish.
Как можем видеть, редактор в файле описания работы серверной части приложения (Template.xml) создал запрос CityShortSelectSqlQuery. Осталось добавить этот запрос в какой-нибудь SqlQueryPermission. Например, в CityViewSqlQueryPermission.
Вы могли заметить, что текст запроса в созданном CityShortSelectSqlQuery полностью совпадает с текстом запроса в CitySelectSqlQuery. Такое дублирование кода допустимо. Лучше иметь отдельный запрос на заполнение выпадающего списка. Забегая вперед, назову две причины такого разделения. Во-первых, в будущем это упростит настраивание прав доступа. А, во-вторых, текст обоих запросов будет дополняться и изменяться по-разному.
Вернемся в файл карточки клиента (TemplateClientEdit.xml) и посмотрим, что добавилось там.
Редактор создал CityShortPrimaryGetDataConnection, который прописал в тэг <ValueList>
созданного объекта CityComboBox. А в тэг <Value>
того же объекта подставил ClientPrimaryGetDataConnection и указал нужное поле.
Перейдем в приложение и убедимся, что карточка клиента успешно загружена, и проверим расположение объектов.
Самостоятельно создайте поля "Контактный телефон" и "E-mail", использую нужные сниппеты. И скорректируйте размеры формы.
В результате у вас должна получиться форма подобного вида:
По заданию справа от поля "Город" должна располагаться кнопка, по которой будет открываться форма со списком городов. Давайте создадим ее.
Для начала скачайте архив с изображением для этой кнопки и распакуйте его в папку \Template\Projects\1. Template\Forms\Images\16x16.
Создайте самостоятельно команду на открытие формы TemplateCityList.xml.
Создадим саму кнопку, разместив код, представленный ниже, сразу после описания объекта CityComboBox.
Не забудьте поправить ширину CityComboBox с учетом ширины созданной кнопки и отступа в 5 пикселей между объектами.
Перейдем в приложение и убедимся, что карточка клиента успешно загружена, и форма со списком городов открывается.
Осталось реализовать обновление CityShortPrimaryGetDataConnection, который предоставляет список для CityComboBox, если на форме были сохранены изменения.
Для этого самостоятельно выполните следующие пункты:
Создайте DataConnectionRefreshCommand на CityShortPrimaryGetDataConnection;
Создайте условие для проверки параметра Updated у команды открытия формы TemplateCityList.xml;
Создайте Execution, который по условию из предыдущего пункта будет вызывать команду на обновление CityShortPrimaryGetDataConnection;
На форме TemplateCityList.xml создайте параметр Updated и команду UpdatedTrueValueSetCommand;
Там же на форме TemplateCityList.xml в тэге <Commands>
кнопки SaveButton исправьте список выполняемых команд. Удалите вызов команды CityDataConnectionRefreshCommand. Вместо нее добавьте вызовы команд UpdatedTrueValueSetCommand и FormCloseCommand;
Так же на форме TemplateCityList.xml удалите описание команды CityDataConnectionRefreshCommand. Эта команда нам больше не понадобится.
Перейдем в приложение. Убедимся, что формы успешно загружаются, а при сохранении изменений на форме списка городов, в карточке клиента обновляется список городов в CityComboBox.
Запросы ClientInsertSqlQuery, ClientUpdateSqlQuery у нас уже есть. Давайте сделаем для них два SetDataConnection:
Как вы помните, чтобы SetDataConnection выполнился, нужна команда SaveCommand, которая будет активировать соединение с данными для отправки. Давайте создадим две команды для каждого SetDataConnection:
Как вы помните, наш запрос ClientInsertSqlQuery возвращает идентификатор client_id новой записи. Команда ClientInsertSaveCommand получает этот идентификатор от ClientInsertSetDataConnection и записывает его в свой результат выполнения. Давайте создадим команду типа ValueSetCommand, которая запишет этот результат в параметр формы ClientId:
Это нам нужно для того, чтобы на родительской форме мы могли использовать идентификатор нового клиента.
Скорректируем синтаксис команды SaveSequentialCommand, которая уже есть в коде:
Вернемся в файл стартовой формы (TemplateStart.xml). Здесь мы получим значение параметра ClientId из команды ClientEditFormShowCommand, которое будем использовать для выделение строки в таблице ClientDatabaseTable. Это позволит пользователю убедиться в том, что новая запись успешно сохранилась. Для выделения строки в таблице создадим следующую команду:
Добавьте вызов этой команды в Execution по изменению параметра Updated у команды ClientEditFormShowCommand после вызова команды типа DataConnectionRefreshCommand.
Перезапустите проект. Убедитесь, что формы успешно загружаются, и происходит выделение строки нового клиента.
Вернемся в файл стартовой формы (TemplateStart.xml) и реализуем функционал удаления записей из таблицы клиентов.
У нас уже есть sql-запрос на удаление записи в таблице template.client в базе данных. Давайте создадим SetDataConnection для этого запроса:
Sql-запрос ожидает только один параметр ClientId. В качестве значения в этот параметр передадим значение из одноименной колонки выделенной строки таблицы ClientDatabaseTable.
После удаления записи из таблицы в базе данных нам нужно будет обновить данные, получаемые из ClientPrimaryGetDataConnection. Это можно сделать используя знакомую нам команду DataConnectionRefreshCommand. А можно использовать более компактный способ обновить DataConnection.
У SetDataConnection и DatabaseTableSetDataConnection есть необязательный тэг <Refresh>
, в качестве значения которого указывается список тэгов <DataConnection>
c именами загружающих соединений с данными, которые будут обновлены после выполнения сохранения.
Давайте добавим такой тэг в наш ClientDeleteSetDataConnection. Таким образом, общий синтаксис соединения с данными для отправки будет выглядеть так:
Самостоятельно создайте команду SaveCommand для нового DataConnection и пропишите ее в тэге <Commands>
кнопки "Удалить запись...".
Перезапустите проект. Убедитесь, что форма успешно загружена, и попытайтесь удалить, создать и обновить записи в таблице.
На уроке мы узнали, что помимо паттернов форм, есть сниппеты, которые позволяют вставлять куски кода в редактируемый файл. Сниппеты ускоряют процесс наполнения формы объектами.
Также рассмотрели использование универсального значения Input, которое позволяет передавать в команду значения параметров в месте вызова команды, а не при ее описании.
Еще с первого урока у нас есть проблема с отсутствием проверки корректности введенных пользователем данных. На следующем уроке рассмотрим, как это сделать корректно и удобно для пользователя. Также рассмотрим паттерн onClose, суть которого заключается в проверке наличия изменений на форме и уведомлении пользователя о них при попытке закрыть форму без сохранения.
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.