Урок 1. Форма списка и добавление записей
Last updated
Last updated
В разделе Платформа Workflow Technology мы познакомились с архитектурой платформы, составом проекта и настройкой взаимосвязи его компонент. Развернули серверную и клиентскую части учебного проекта. И добавили исходники учебного проекта в редактор Workflow XML Editor.
Пришло время развивать наш проект и знакомиться с возможностями редактора и платформы.
Начнем мы с того, что на стартовой форме создадим таблицу для списка городов с кнопкой добавления записи в таблицу и кнопкой сохранения изменений в базу данных. Так же создадим форму для карточки сущности с текстовым полем "Наименование".
В этом уроке мы научимся:
получать данные из таблицы в базе данных;
добавлять записи в таблицу в базе данных;
создавать и открывать новые формы;
передавать данные с дочерней формы на родительскую;
редактировать таблицу на форме.
Список будет на стартовой форме в виде таблицы с колонками: "№" (Порядковый номер записи) и "Наименование". Справа от таблицы будет размещена кнопка добавления записи в таблицу. По этой кнопке будет открываться форма карточки сущности. Под таблицей будет располагаться кнопка для сохранения изменений в базе данных.
По завершению этого этапа мы с вами получим форму вида:
Перейдем в Eclipse и откроем файл стартовой формы (TemplateStart.xml). Первым делом давайте изменим заголовок и размер формы.
Параметру формы Title зададим новое значение "Города".
В тэге <Form>
для атрибутов Width
и Height
зададим значение 500 и 300 соответственно. И добавим атрибут FormBorderStyle
со значением Sizable. Это позволит изменять размер окна.
Давайте добавим таблицу (объект типа DatabaseTable) на форму. Для этого поместите курсор внутри объекта ContentPanel, после тэга <BackColor>
. Начните писать, затем нажмите на сочетание клавиш Ctrl+Space. Откроется окно автозаполнения.
Выберете в списке автозаполнения нужный элемент. Редактор подставит весь обязательный и необходимый код. Останется только задать нужные значения.
Первым делом зададим нашему объекту имя. Например, CityDatabaseTable. Подробно про тип DatabaseTable можете прочитать по ссылке. А сейчас рассмотрим предложенные редактором элементы.
Тэги <Top>
и <Left>
задают координаты верхней левой точки объекта в пределах контейнера, в котором расположен объект. Ожидаются положительные целочисленные значения. Для таблицы CityDatabaseTable контейнером является панель ContentPanel, для которой контейнером является сама форма.
Тэги <Height>
и <Width>
задают высоту и ширину объекта. Ожидаются положительные целочисленные значения.
Значения этих элементов можно задать постоянными. Для <Top>
и <Left>
укажем значения 5 и 10 соответственно.
А можно задать гибкие значения, привязав к свойствам других объектов. Для этого можно использовать универсальные значения Calculate или Formula для вычисления числового выражения. Или напрямую привязать к свойству другого объекта. Это позволит динамически изменять размер и положение объектов на форме при изменении размеров самого окна.
Давайте высоту таблицы привяжем к высоте контейнера ContentPanel и используем значение свойства Top самой таблицы, как отступ сверху и снизу.
Для вычисления ширины таблицы самостоятельно переделайте код, используя ширину контейнера и свойство Left самой таблицы.
<AllowResizeColumns>
- признак, определяющий, может ли пользователь изменять ширину столбцов посредством графического интерфейса таблицы. Значение атрибута Value
оставим по умолчанию;
<AllowResizeRows>
- признак, определяющий, может ли пользователь изменять высоту строк посредством графического интерфейса таблицы. Оставим значение атрибута Value
по умолчанию;
<AllowInsert>
, <AllowUpdate>
и <AllowDelete>
- признаки, определяющие, может ли пользователь редактировать строки в таблице посредством графического интерфейса таблицы. Всем признакам укажем значение False, т.к. для редактирования таблицы будем использовать отдельную форму и набор set-проперти таблицы;
<AutoSizeColumnsMode>
- название типа автоматического изменения ширины столбцов таблицы. Оставим значение атрибута Value
по умолчанию;
<SourceDataConnection>
- загружающее соединение с данными, данные которого будут использоваться в таблице;
<Columns>
- содержит список столбцов таблицы.
У нас пока что нет источника данных для таблицы поэтому можем удалить или закомментировать тэг <SourceDataConnection>
, чтобы редактор не выкидывал ошибку, и форма корректно открывалась.
Для того чтобы закомментировать часть кода, нужно выделить код и нажать сочетание клавиш Ctrl+<. Для снятия комментария с кода достаточно поставить курсор в любое место комментария и нажать эту же комбинацию клавиш.
Добавим колонки в таблицу. Нам нужно создать три колонки:
CityId - скрытая служебная колонка с идентификатором записи в базе;
RowNumber - порядковый номер отображаемой записи;
Title - наименование города, которое введет пользователь.
Тэг <AutoFill Type="RowNumber" />
отвечает за автоматическую генерацию порядкового номера строки в таблице.
Для автоматического форматирования xml-кода используйте сочетание клавиш Ctrl+Q
В итоге синтаксис таблицы CityDatabaseTable будет выглядеть так:
Давайте посмотрим, что у нас получилось. Убедитесь, что запущен сервер. Если нет, то запустите файл _start.bat из папки, в которую разворачивали серверную часть приложения, например, D:\WorkflowEngine\Template. Запустите приложение WorkflowForms.exe из папки, в которую разворачивали клиентскую часть, например, D:\WorkflowForms\Template. Если клиентская часть уже была запущена, то лучше закрыть и запустить заново.
К сожалению, запуск WT-приложения из Eclipse не доступен.
Для кнопок нам понадобятся иконки. Скачайте архив с изображениями и разархивируйте его в папку проекта \Template\Projects\1. Template\Forms\Images\24x24.
Добавим кнопку (объект типа Button), по которой будет открываться форма с карточкой города. Добавьте в ContentPanel вслед за CityDatabaseTable следующий код:
Так как кнопка графически находится справа от таблицы, что и указано в тэге <Left>
, то необходимо скорректировать ширину таблицы. Для этого в <Calculate>
для тэга <Width>
объекта CityDatabaseTable необходимо учесть ширину кнопки и отступ в 5 пикселей между таблицей и кнопкой. Заменим описание тэга <Width>
объекта CityDatabaseTable следующим кодом:
Перезапустите проект. Убедитесь, что форма успешно загружена. Проверьте расположение объектов на форме.
Кнопка "Сохранить" будет располагаться в нижней части формы (Footer). Давайте создадим новую панель, которую назовем FootPanel. Так же визуально отделим FootPanel от ContentPanel с помощью разделителя, в качестве которого будет выступать Panel с высотой в один пиксель. Добавьте следующий код следом за ContentPanel.
Теперь нам необходимо скорректировать высоту ContentPanel, чтобы FootPanel вместе с сепаратором поместились на форме. Заменим описание тэга <Height>
объекта ContentPanel следующим кодом:
Добавим в FootPanel кнопку "Сохранить", скопировав код:
Перезапустите проект. Убедитесь, что форма успешно загружена. Проверьте расположение объектов на форме.
Перейдем в программу для управления СУБД PostgreSQL (например, pgAdmin III). И для нашей базы данных template_project откроем окно выполнения пользовательских SQL-скриптов и выполним следующий скрипт:
Последовательность в PostgreSQL обычно используется для автоинкремента первичных ключей в таблицах.
Все имена в базе данных должны задаваться строчными буквами.
Вернемся в Eclipse и перейдем в файл описания работы серверной части приложения (Template.xml).
Добавим запрос для чтения данных из таблицы template.city в процесс. Для этого расположим в тэге <SqlQueries>
вложенный тэг <SqlQuery>
со следующими параметрами:
Далее этот запрос следует добавить в разрешение. Для этого расположим в тэге <Permissions>
вложенный тэг <Permission>
со следующими параметрами:
После чего данное разрешение следует добавить в роль для просмотра списка. Для этого расположим в тэге <Roles>
вложенный тэг <Role>
со следующими параметрами:
Затем роли необходимо распределить по группам. В тэге <Groups>
вложенные тэги <Group>
должны иметь названия, которые совпадают с названиями групп в таблице group. Т.к. в проекте пока не реализована авторизация, то в программе мы будем работать под анонимным пользователем (WS. Гость). Этот пользователь состоит в группе GuestGroup. Добавим в тэг <Groups>
вложенный тэг <Group>
для этой группы. И назначим группе роль для просмотра списка городов.
Перейдем в файл описания стартовой формы (TemplateStart.xml).
Добавим загружающее соединение с данными (объект типа PrimaryGetDataConnection). Для этого расположим в тэге <DataConnections>
вложенный тэг <DataConnection>
со следующими параметрами:
<SqlQuery>
- имя запроса для получения данных. Ожидается название одного из запросов, описанных в серверном XML-файле;
<Workflow>
- имя процесса, в рамках которого происходит запрос;
<Field>
- название поля запроса, которое будет использоваться на форме. Ожидается название одного из полей, возвращаемых запросом.
С помощью CityPrimaryGetDataConnection мы получим на форме виртуальную таблицу (двумерный массив данных с именованными столбцами) с определенными полями, которая будет соответствовать результатам запроса CitySelectSqlQuery, описанного в серверном XML-файле.
Названия полей в DataConnection должны полностью (вплоть до регистра!) совпадать с полями или их алиасами в запросе.
Теперь необходимо таблице CityDatabaseTable в качестве источника данных указать только что созданный CityPrimaryGetDataConnection. Для этого раскомментируем тэг <SourceDataConnection>
, и в качестве значения аргумента Name укажем имя DataConnection'а.
В итоге синтаксис таблицы CityDatabaseTable будет выглядеть так:
Перезапустите проект. Убедитесь, что форма успешно загружена, и в журнале нет никаких сообщений об ошибках.
Пришло время создать форму, на которой мы сможем редактировать информацию по городу.
Карточка города должна содержать текстовое поле "Наименование" и кнопку "Сохранить", по которой значение текстового поля "Наименование" должно передаваться на родительскую форму и добавляться в таблицу городов.
Но для начала подключим к проекту готовые шаблоны форм. Они ускорят процесс создания новых форм.
Скачаем архив с шаблонами и распакуем его в удобное место. Например, в папку проекта \Template\Projects\1. Template\Patterns.
Вернемся в Eclipse и перейдем в свойства проекта, используя контекстное меню. Кликнем правой кнопкой мыши по имени проекта в окне Project Explorer и в появившемся меню выберем пункт Properties. И в открывшемся окне свойств проекта в левой части перейдем к секции Patterns.
В правой части окна в поле Project patterns path укажем путь до папки с паттернами. Жмем OK.
Чтобы Eclipse корректно подтянул изменения паттернов, воспользуемся кнопкой Reload patterns, расположенной на панели инструментов.
Для создания формы с помощью шаблона используем пункт меню File -> New -> Apply Workflow Pattern.
В открывшемся диалоговом окне Workflow Editor отображается список шаблонов форм, в котором есть два шаблона: Empty Form (пустая форма с хэдером) и Empty Form With Footer (пустая форма с хэдером и футером).
Для задачи создания формы карточки города выберем шаблон Empty Form with Footer. Этот шаблон уже содержит код формы с основными элементами. И нам останется только добавить нужное поле и реализовать логику передачи данных на родительскую форму. Жмем Finish.
Появилось окно настроек паттерна:
Имя формы: это постфикс для имени файла, пишем "CityEdit". И т.к. процесс имеет название Template, то имя файла созданной формы будет TemplateCityEdit.xml;
Заголовок формы: текст, который будет отображаться в хэдере формы, пишем "Добавление города";
Имя кнопки в футере: имя объекта, которое будет использоваться на форме. Пишем SaveButton;
Текст кнопки в футере: видимый текст на кнопке. Оставим без изменения;
Иконка на кнопке в футере: путь до файла с графическим содержанием относительно папки \Forms\Images\24x24, которое будет отображаться на кнопке. Оставим без изменения. Значение по умолчанию соответствует ранее скачанному и размещенному в проекте файлу.
Жмем Finish.
Редактор создаст форму и сразу же ее откроет. В окне Project Explorer в ветке проекта увидим только что созданную форму TemplateCityEdit.xml:
Перейдем в файл стартовой формы (TemplateStart.xml) и реализуем команду открытия формы будущей карточки города. Для этого в тэг <Commands>
самой формы добавим команду FormShowCommand вида:
В качестве значения тэга <Xml>
укажем имя файла карточки редактирования города. В атрибуте Type
тэга <Show>
стоит значение Modal, это означает, что форма будет открываться в модальном режиме. Особенности работы с модальными формами рассмотрим на следующем уроке.
Теперь нужно на кнопке CityAddButton в тэге <Commands>
прописать вызов этой команды. Таким образом, синтаксис кнопки добавления города будет иметь вид:
Перезапустите проект. Убедитесь, что форма успешно загружена, и по кнопке "Добавить запись..." открывается форма будущей карточки города.
Вернемся в файл новой формы (TemplateCityEdit.xml).
Для быстрого перемещения к описанию объекта зажмите клавишу Ctrl и кликните левой кнопкой мыши по имени объекта, в месте его использования. Этот трюк работает с объектами MyObject, командами, файлами форм и именами SQL-запросов.
Добавим на форму в панель ContentPanel текстовое поле TextBox для ввода значения и Label с текстом "Наименование" для описания текстового поля.
Скорректируйте высоту и ширину формы.
Откройте заново форму добавления города, чтобы убедиться, что форма успешно загружена.
По кнопке "Сохранить" мы должны передавать на родительскую форму наименование города, которое ввел пользователь. При этом дочерняя форма должна закрываться. Для передачи данных между форм используем параметры формы, описанные в тэге <Parameters>
.
Создадим новый параметр:
Добавим на форму команду типа ValueSetCommand, которая будет параметру CityTitle присваивать значение из текстового поля TitleTextBox:
В файле уже описана команда FormCloseCommand, которая будет закрывать окно. Нам нужно лишь объединить ее с нашей командой в одну последовательность SequentialCommand. Добавим следующий код в тэг <Commands>
:
И затем на кнопке SaveButton пропишем вызов этой последовательности.
Перейдем в файл стартовой формы (TemplateStart.xml) и реализуем обработку результата выполнения команды CityAddFormShowCommand.
У таблицы DatabaseTable есть set-проперти AddRow, AddRows, UpdateRow, UpdateRows и DeleteRowsByIndices. Они позволяют редактировать строки в таблице. Чтобы воспользоваться этими свойствами необходима команда ValueSetCommand.
Ряд соединений с данными (например, PrimaryGetDataConnection, SecondaryGetDataConnection и ConvertDataConnection) так же обладают набором set-проперти (AddRow, AddRows, UpdateRow, UpdateRows и DeleteRowsByIndices) для редактирования своих записей.
Давайте создадим такую команду для добавления записи в таблицу:
Параметр ColumnNames ожидает линейный массив названий столбцов таблицы. Чтобы создать такой линейный массив используем структуру данных Structure типа List. Т.к. дочерняя форма возвращает только наименование города, то в списке колонок будет лишь одно имя колонки с наименованием:
Параметр Values так же ожидает линейный массив значений, которые будут добавляться в колонки, указанные в параметре ColumnNames. Нам нужно значение, которое мы сохранили в параметр формы CityTitle. К параметрам формы, которую открывали через команду FormShowCommand, можно обратиться через атрибут Parameter
тэга <Command>
:
Параметр SelectAfterAdd определяет, будет ли выделена новая строка после ее добавления. Ожидает логическое значение. Присвоим ему значение True.
В итоге общий синтаксис команды будет иметь вид:
Теперь нужно на кнопке CityAddButton в тэге <Commands>
добавить вызов этой команды сразу за вызовом команды CityAddFormShowCommand. Таким образом, синтаксис кнопки добавления записи в таблицу будет выглядеть так:
Перезапустите проект. Убедитесь, что формы успешно загружаются, и в таблицу добавляются новые записи.
Перейдем в файл серверной xml (Template.xml). И добавим запрос на вставку данных в таблицу template.city в базе данных:
Этот запрос будет использоваться на форме в соединении с данными для отправки DatabaseTableSetDataConnection, которое будет описано ниже.
Обратите внимание, что параметры, передаваемые с формы, указываются в фигурных скобках, например, {Title}
. Причем названия этих параметров должны совпадать с названиями параметров, указанных в DatabaseTableSetDataConnection.
Создадим новое разрешение для этого запроса. Т.к. разрешение связано с редактированием записей в таблице, то отметим этот момент в имени разрешения, указав после имени сущности "Edit":
После чего создадим новую роль для редактирования городов. И дополнительно этой роли выдадим разрешение на получение списка городов. Тем самым предоставив роли полный набор прав для возможности редактирования списка.
Затем у группы GuestGroup заменим роль:
Перейдем в файл описания стартовой формы (TemplateStart.xml).
Реализация отправки данных делится на два этапа:
описание соединения с данными для отправки: SetDataConnection или DatabaseTableSetDataConnection;
описание и вызов команды, которая будет обращаться к соединению с данными для отправки: SaveCommand.
Загрузка данных через соединение с данными для загрузки (PrimaryGetDataConnection) происходит автоматически при открытии формы, а соединения с данными для отправки (SetDataConnection или DatabaseTableSetDataConnection) выполнятся при вызове. Поэтому и нужна команда SaveCommand, которая будет обращаться к соединению с данными для отправки, чтобы последнее осуществило отправку.
Создадим сохраняющее соединение типа DatabaseTableSetDataConnection, которое напрямую из таблицы берет измененные строки и отправляет на сервер:
Тэг <Workflow>
описывает имя процесса, в рамках которого происходит запрос.
В тэге <DatabaseTable>
в атрибуте Name
ожидается имя таблицы, описанной на форме. Для этой таблицы и будет происходит сохранение изменений. Укажем в этом тэге имя нашей таблицы:
Тэг <Parameters>
содержит список параметров, передаваемых в запросы. Каждый параметр представлен тэгом <Parameter>
. Имя параметра, указанное в атрибуте NativeName
, должно полностью совпадать с именем параметра, указанного в фигурных скобках в тексте SQL-запроса. В качестве значения тэга указывается колонка сохраняемой таблицы, из которой будут подставляться значения в соответствующий параметр запроса.
Так же в качестве значения тэга <Parameter>
можно указать любое универсальное значение: любой объект формы, значение условия, результат команды, параметр формы и другое. Полное описание универсальных значений можно прочитать по ссылке.
В тэге <SqlQueries>
описываются запросы для отправки данных. В атрибуте Name
тэга <SqlQuery>
указывается имя запроса, описанного в серверном файле. В качестве значения атрибута Type
ожидается значение Insert, Update или Delete. В insert-запросы передаются параметры новых строк таблицы, в update-запросы - измененных строк, в delete-запросы - удаленных строк. DatabaseTable сразу разделяет свои строки по соответствующим коллекциям. Потому DatabaseTableSetDataConnection знает для каких строк таблицы какой запрос использовать.
В серверном XML-файле ранее описали запрос на вставку новых данных в таблицу базы данных - CityInsertSqlQuery. Укажем его в соединении с данными:
Таким образом, общий синтаксис соединения с данными для отправки будет выглядеть так:
Как было сказано выше, для отправки данных на сервер используется команда типа SaveCommand. Создадим такую команду в тэге <Commands>
и укажем в ней описанное ранее соединение с данными:
А теперь вызов этой команды следует разместить в описании кнопки сохранения SaveButton в тэге <Commands>
. Таким образом, общий синтаксис кнопки сохранения изменений на форме в таблицу city в базе данных будет выглядеть так:
Перезапустите проект. Убедитесь, что формы успешно загружаются, а изменения корректно сохраняются в базе данных.
Как вы могли заметить, на данный момент есть несколько недочетов:
При закрытии карточки города по крестику (без сохранения), в таблицу добавляются пустые строки. Происходит лишнее срабатывание команды CityDatabaseTableAddRowValueSetCommand;
Нет проверки корректности заполнения текстового поля "Наименование".
Эти недочеты мы поправим в последующих уроках.
А пока подведем итог того, что мы узнали:
для загрузки данных из базы данных используем PrimaryGetDataConnection, который выполняется автоматически;
для сохранения данных из таблицы на форме в таблицу в базе данных используем DatabaseTableSetDataConnection. Для его выполнения нужно использовать команду SaveCommand;
как подключать и использовать паттерны форм;
как на родительской форме получать данные с дочерней формы;
как в таблицу на форме добавлять новые строки.
На следующем уроке мы:
добавим возможность редактировать и удалять записи в таблице на форме;
узнаем, как избежать лишнего срабатывания команд при работе с дочерними формами.
В архиве присутствуют xml-файлы форм и серверный xml-файл, так же лежит файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.