Урок 2. Редактирование таблицы
На прошлом уроке мы создали список городов и карточку города, с помощью которой добавляли записи в таблицу. На этом добавим возможность редактировать и удалять записи, а также разрешим одну из проблем, с которой столкнулись при добавлении записей.
В этом уроке мы:
познакомимся с паттерном Updated;
узнаем, в чем различие между событийными условиями и условиями сравнения.
Паттерн Updated
На прошлом уроке мы столкнулись с проблемой лишнего срабатывания команды CityDatabaseTableAddRowValueSetCommand на стартовой форме при закрытии карточки города по крестику (без сохранения). В результате чего происходило добавление пустой строки в таблицу со списком городов. Причина этого в том, что родительская форма не знала, было ли сохранено изменение на дочерней форме, или форму закрыли по крестику. Команда CityDatabaseTableAddRowValueSetCommand всегда выполнялась следом за командой CityAddFormShowCommand, которая завершала свое выполнение после закрытия дочерней формы.
Для решения этой проблемы есть паттерн Updated.
Дочерняя форма
Перейдем в файл карточки города (TemplateCityEdit.xml).
В параметры формы, тэг <Parameters>
, добавим параметр:
Этот параметр и будем использовать для уведомления родительской формы о сохраненных изменениях.
Создадим команду, которая будет присваивать параметру значение True в момент сохранения данных:
Добавим вызов этой команды в последовательность команд SaveSequentialCommand, которую вызываем по кнопке "Сохранить". В итоге общий синтаксис команды будет иметь вид:
Отлично! Теперь можно заняться родительской формой, на которой будем проверять этот параметр.
Родительская форма
Перейдем в файл описания стартовой формы (TemplateStart.xml).
В тэг <Conditions>
добавим описание условия типа EqualCondition, в котором будем проверять значение параметра Updated команды CityAddFormShowCommand:
Последовательность команд на кнопке CityAddButton нужно изменить так, чтобы вызов команды CityDatabaseTableAddRowValueSetCommand происходил только в том случае, если дочерняя форма в качестве параметра Updated вернула значение True. Для этого воспользуемся конструкцией <If>
:
Таким образом, синтаксис кнопки добавления записи в таблицу будет выглядеть так:
Перезапустите проект. Убедитесь, что формы успешно загружаются, и по кнопке "Добавить запись..." корректно добавляются новые строки.
Отлично! Теперь нет лишнего срабатывания команды CityDatabaseTableAddRowValueSetCommand, и в таблицу добавляются нужные строки.
И вроде бы все хорошо, но есть один момент. Когда открыта карточка города, мы не можем вернуться к форме со списком городов. Причина такого поведения в том, что окно карточки города открывается в модальном режиме. Модальное окно не позволяет выполнить другую задачу в приложении, пока оно не будет закрыто.
Модальные и немодальные окна
Режим открытия окна задается в команде FormShowCommand с помощью тэга <Show>
. И в команде CityAddFormShowCommand у атрибута Type
этого тэга задано значение Modal:
С модальными окнами есть небольшой плюс. Команда FormShowCommand на открытие модального окна будет держать процесс и передаст управление следующей команде только после закрытия дочернего окна. Эта особенность и позволяет нам указать в тэге <Commands>
кнопки CityAddButton последовательно вызов команды CityAddFormShowCommand и блок <If>
с вызовом команды CityDatabaseTableAddRowValueSetCommand.
Но использование модального окна не всегда оправдано. И в нашем случае оно создает неудобство. Давайте изменим режим открытия формы на None. В этом режиме форма откроется параллельно родительской.
Перезапустите проект. Убедитесь, что формы успешно загружаются, и попробуйте добавить новые строки в таблицу.
Изменив режим открытия формы с модального на параллельный, мы словили неприятную ситуацию с отложенным добавлением строк в таблицу. На самом деле проблема в том, что при открытии формы в немодальном режиме команда FormShowCommand не будет дожидаться закрытия этой формы, а сразу же передаст управление дальше. И т.к. мы не дождались результатов с дочерней формы, то параметры Updated и CityTitle команды CityAddFormShowCommand будут хранить старые значения. Что и создает эффект отложенного срабатывания команды добавления строк.
Следовательно, при работе с немодальными формами мы не можем в тэге <Commands>
последовательно вызывать команду FormShowCommand и обрабатывать ее результат. В этом случае обработку результатов с немодальных форм необходимо вынести в конструкцию Execution.
Execution отвечает за автоматическое выполнение последовательности команд при срабатывании условий, на которые этот Execution подписан.
С примером использования Execution вы уже знакомы. В файлах обеих форм описан Execution, который вызывает команду закрытия формы, когда пользователь нажимает на кнопку закрытия окна:
FormClosingCondition - системное условие, отслеживающее события попытки закрытия формы;
FormCloseCommand - системная команда закрытия текущей формы
Обработка результата немодальных форм
Давайте блок <If>
из последовательности команд на кнопке CityAddButton переделаем на <Execution>
. Для этого на стартовой форме (TemplateStart.xml) создадим новый <Execution>
, который будет вызывать команду CityDatabaseTableAddRowValueSetCommand, и подпишем его на условие CityAddFormUpdatedTrueEqualCondition:
В итоге синтаксис кнопки добавления записи в таблицу будет выглядеть так:
Перезапустите проект. Убедитесь, что формы успешно загружаются, и попробуйте добавить несколько строк в таблицу.
Теперь мы столкнулись с другой проблемой: если мы несколько раз подряд добавляем новые города, то в таблицу добавится результат только первого сохранения на дочерней форме, а все остальные попытки будут игнорироваться. Однако если карточку города открыть и закрыть без сохранения, то при следующем добавлении нового города, сохраненный результат добавится в таблицу. Причина этого заключается в характере условия, на которое подписан Execution.
Все условия можно поделить на две группы: условия сравнения и событийные условия.
Условия сравнения рассылают уведомления своим подписчикам, только если результат условия изменится. К условиям сравнения относятся все условия, в которых происходит сравнение значений: EqualCondition, IsNullCondition и подобные. Также к условиям сравнения относится условие NestedCondition.
Событийные условия рассылают уведомления каждый раз, когда происходит событие. К ним относятся условия CellDoubleClickCondition, FormClosingCondition и подобные.
В нашем случае, когда мы первый раз открыли карточку города и сохранили изменение, значение параметра Updated изменилось на True. И результат условия CityAddFormUpdatedTrueEqualCondition также изменился на True. Условие отправило своим подписчикам уведомление об изменении. Execution отловил это уведомление и выполнил свою последовательность команд. Когда мы открыли карточку города второй раз и сохранили новый город, то результат условия CityAddFormUpdatedTrueEqualCondition не изменился. Он по-прежнему остался True. Поэтому условие не разослало событие об изменении значения, и Execution не сработал. Когда открыли и закрыли дочернюю форму без сохранения, то значение параметра Updated осталось False (по умолчанию), а результат условия CityAddFormUpdatedTrueEqualCondition изменился на False.
В платформе есть возможность условие сравнения принудительно сделать событийным условием. Для этого у тэга <Condition>
есть необязательный тэг <AlwaysChange>
- признак, определяющий, будет ли условие рассылать событие изменения своего значения в том случае, если операнды условия были изменены, но само значение условия не изменилось.
Давайте наше условие CityAddFormUpdatedTrueEqualCondition сделаем принудительно событийным, добавив тэг <AlwaysChange>
со значение True:
Перезапустите проект. Убедитесь, что формы успешно загружаются, и новые строки корректно добавляются в таблицу.
Проблемы событийных условий в Execution
Рассмотрим следующую ситуацию. У нас есть два событийных условия:
Condition1 - например, проверка параметра Updated у команды открытия формы на создание записи;
Condition2 - например, проверка параметра Updated у команды открытия формы на редактирование записи.
И есть Execution, который при срабатывании одного из условий должен выполнять какую-то команду AnyCommand:
Когда мы открыли форму на создание записи и сохранили изменения, в параметре Updated команды будет значение True. Следовательно, условие Condition1 изменит свое значение на True и разошлет событие своим подписчикам. Execution поймает это событие и проверит свое условие: Condition1 (True) или Condition2 (False) - результат будет True. Команда AnyCommand выполнится.
Теперь мы откроем форму на редактирование записи и закроем без сохранения. В параметре Updated команды открытия формы будет значение False. А так как условие Condition2 событийное, то оно все равно разошлет уведомление своим подписчикам. Execution поймает это событие и проверит свое условие. Condition1 по-прежнему имеет значение True, а значение условия Condition2 равно False. И результат проверки Execution будет True. Что приведет к лишнему выполнению команды AnyCommand.
Чтобы избежать подобной проблемы данный Execution следует разбить на два отдельных Execution-а, которые будут подписаны на разные условия.
Форма на редактирование
С проблемой из прошлого урока разобрались. Теперь можно приступить к реализации возможности редактирования записей в таблице.
Карточка города сейчас используется только для создания записей. А чтобы можно было отредактировать имеющуюся запись, карточка должна открываться в режиме на редактирование (не путать с режимами форм - модальные/немодальный ) и получать от родительской формы данные из этой записи.
Давайте реализуем такую возможность. Для просмотра информации по существующей в таблице записи будем использовать ту же форму карточки города.
Режим формы Edit
Перейдем в файл карточки города (TemplateCityEdit.xml).
В каком режиме открыта карточка города: на редактирование или создание - мы будем определять через параметр Edit. Давайте в тэг <Parameters>
формы добавим этот параметр:
Подредактируем значение параметра Title, чтобы заголовок формы информировал пользователя о режиме карточки города:
Для передачи наименование города с родительской формы будем использовать существующий параметр CityTitle. И чтобы значение параметра можно было прочитать, укажем этот параметр в тэге <Text>
текстового поля TitleTextBox. Таким образом, синтаксис TitleTextBox будет иметь вид:
Передача данных на дочернюю форму
Вернемся в файл стартовой формы (TemplateStart.xml). Добавим на форму команду на открытие карточки города для редактирования:
Необязательный тэг <Parameters>
ожидает список параметров, которые будут передаваться на дочернюю форму. Для параметра Edit укажем значение True. А чтобы в параметре CityTitle передать наименование города, выбранного в таблице, воспользуемся get-проперти SelectedRowCellValueByColumnName объекта DatabaseTable:
Свойство SelectedRowCellValueByColumnName возвращает значение ячейки выделенной строки таблицы в столбце, имя которого указано в параметре ColumnName.
Общий синтаксис команды выглядит так:
Открытие формы редактирования записи
Для кнопок нам понадобятся иконки. Скачайте архив с изображениями и разархивируйте его в папку проекта \Template\Projects\1. Template\Forms\Images\24x24.
Создадим кнопку для редактирования города. Для этого разместим приведенный ниже код в ContentPanel следом за описанием объекта CityAddButton.
У объектов на форме есть признак Enabled, который определяет, будет ли объект активен. В нашем случае кнопка редактирования должна быть неактивной, если в таблице не выбрана запись. Для этого нам нужно создать условие типа GreaterCondition, в котором проверим количество выделенных строк в таблице. Для получения количества выделенных строк у объекта DatabaseTable воспользуемся get-проперти SelectedRowsCount.
В условии CitySelectedCondition можно проверять индекс выделенной строки, полученный через get-проперти SelectedRowIndex. Индексы строк в таблице начинаются с 0.
Укажем это условие в тэге <Enabled>
кнопки CityEditButton. Таким образом, синтаксис кнопки редактирования города будет иметь вид:
Перезапустите проект. Убедитесь, что форма успешно загружена, по кнопке "Редактировать запись..." успешно открывается форма карточки города, и на форму передается наименование города из выделенной строки.
Открывать карточку города на редактирование можно и по двойному клику по строке таблицы. Для отлавливания такого событие есть условие CellDoubleClickCondition. Добавим следующий код на стартовую форму:
Теперь создадим Execution, который подпишется на это условие и будет вызывать команду на открытие формы для редактирования записи:
Обновление данных в строке таблицы
На прошлом уроке мы узнали, что у таблицы DatabaseTable есть set-проперти AddRow, AddRows, UpdateRow, UpdateRows и DeleteRowsByIndices. Сейчас нам понадобится свойство UpdateRow. Создадим команду ValueSetCommand для использования этого свойства:
Помимо знакомых нам параметров ColumnNames и Values, свойство имеет параметр RowIndex - индекс строки, значения которой будут обновлены. Этот параметр ожидает целочисленное значение. Если этот параметр отсутствует, то обновятся все строки.
У объекта DatabaseTable есть get-проперти SelectedRowIndex, с его помощью и получим индекс редактируемой строки:
Готовый синтаксис команды обновления значений в выбранной строке будет иметь вид:
Теперь самостоятельно создайте условие EqualCondition для проверки параметра Updated у команды CityEditFormShowCommand. А затем создайте Execution, который по этому условию будет выполнять команду CityDatabaseTableUpdateRowValueSetCommand.
Перезапустите проект. Убедитесь, что форма успешно загружена, и по кнопке "Редактировать запись..." успешно обновляются данные выделенной строки.
Сохранение изменений в таблицу в базе данных
Отправка данных на сервер
На прошлом уроке мы создали сохраняющее соединение с данными CityDatabaseTableSetDataConnection. Давайте внесем в него пару изменений.
Первым делом добавим новый параметр CityId, по которому будем обновлять наименование в нужной записи:
Также в тэг <SqlQueries>
добавим update-запрос, который назовем, например, CityUpdateSqlQuery. Таким образом, общий синтаксис соединения с данными для отправки будет выглядеть так:
Запрос на обновление данных
Перейдем в файл серверной xml (Template.xml). И добавим запрос на обновление данных в таблице city в базе данных:
Добавим этот запрос в ранее созданный CityEditSqlQueryPermission:
Перезапустите проект. Убедитесь, что формы успешно загружаются, а изменения корректно сохраняются в базе данных.
Удаление записей
Самостоятельно реализуйте удаление записи из таблицы.
Для кнопки используйте иконку из файла \Images\24x24\delete.png. Не забудьте, что кнопка должна быть неактивной, если в таблице не выбрано строки. Для удаления строки из таблицы используйте свойство DeleteRowsByIndices самой таблицы. А для удаления записи из таблицы в базе данных расширьте сохраняющее соединение CityDatabaseTableSetDataConnection.
По завершению этого задания у вас должна получиться форма подобного вида:
Обновление данных из базы данных
Программа работает и выполняет те задачи, которые должна. Но есть одно некорректное поведение: если вы добавите новую запись в таблицу на форме и сохраните изменения в базу, и после попытаетесь удалить эту запись и вновь сохранить изменения в базу, то перезапустив стартовую формы, вы удивитесь, что удаленная запись существует.
Причина этого в том, что форма не получила из базы id новой записи, и в запрос на удаление таблица передала значение NULL. Хотя таблица на форме удалила у себя эту строку.
Чтобы на форме получить актуальные данные из таблицы в базе данных, нам необходимо обновить CityPrimaryGetDataConnection. Для этого создадим команду типа DataConnectionRefreshCommand, которая при вызове будет перезагружать наше соединение с данными:
И добавим вызов этой команды на кнопку SaveButton после команды CityDatabaseTableSaveCommand:
Перезапустите проект. Убедитесь, что формы успешно загружаются, и данные обновляются после сохранения их на сервер.
Итоги
В конце прошлого урока мы определили две проблемы. Напомню их:
При закрытии карточки города по крестику (без сохранения), в таблицу добавляются пустые строки. Происходит лишнее срабатывание команды CityDatabaseTableAddRowValueSetCommand;
Нет проверки корректности заполнения текстового поля "Наименование".
Первую мы исправили, применив паттерн Updated, суть которого заключается в том, чтобы уведомить родительскую форму о сохраненных изменениях. А вот вторая проблема у нас по-прежнему висит и техническим долгом переходит в следующие уроки.
В рамках паттерна Updated мы рассмотрели особенности использования модальных и немодальных форм (окон). Также узнали, что условия могут быть условиями сравнения и событийными условиями. И что в платформе есть возможность условия сравнения сделать принудительно событийными условиями.
На следующем уроке мы:
создадим еще один список, который будет хранить данные по клиентам;
рассмотрим пример выпадающего списка на форме;
рассмотрим пример использования SetDataConnection для сохранения данных.
Ответы
В архиве присутствуют xml-файлы форм и серверный xml-файл, а также бэкап базы данных - с помощью файлов можете проверить себя.
Last updated