На этом уроке мы познакомимся с объектом DatabaseTree, который отображает данные в виде дерева значений, что позволяет лучше воспринимать иерархическую структуру данных. Для этого создадим форму списка товарно-материальных ценностей (ТМЦ), которые будут разделяться на категории.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье .
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по .
Новый список
Для начала создадим простенький список единиц измерений, который пригодится при создании списка ТМЦ.
База данных
Для списка единиц измерения создайте таблицу template.unit с колонками:
unit_id smallint NOT NULL - первичный ключ;
title character varying NOT NULL - наименование;
short_title character varying NOT NULL - сокращенное обозначение;
archive boolean NOT NULL DEFAULT false - признак архивной записи.
Создайте формы для редактирования списка единиц измерения:
Для этого можете воспользоваться паттерном ArchiveList из архива:
Пример заполнение настроек паттерна для списка единиц измерения:
Редактор автоматически создаст формы списка (TemplateUnitList.xml) и карточки сущности (TemplateUnitEdit.xml), а также в серверный xml-файл добавит все необходимые запросы.
После применения паттерна перейдем в xml-файл серверной части и перенесем роль UnitEditRole в группу GuestGroup, а также добавим нужные поля на форму карточки сущности, колонки в таблицу на форме списка и скорректируем запросы.
На главной форме в меню добавьте пункт Списки -> Единицы измерения..., по которому будет открываться новая формаTemplateUnitList.xml.
Дерево значений
Нам необходимо создать форму списка для создания и редактирования новой сущности - товарно-материальных ценностей.
Но в этот раз помимо таблицы товарно-материальных ценностей на форме будет список категорий ценностей, который будет представлен в виде дерева, чтобы иметь возможность разбивать категории на подкатегории.
Должна получиться форма вида:
База данных
Создадим в базе данных таблицы:
template.material_category - категории товарно-материальных ценностей;
CREATE SEQUENCE template.material_category_id_seq;
CREATE TABLE template.material_category
(
material_category_id bigint NOT NULL DEFAULT nextval('template.material_category_id_seq'::regclass),
parent_material_category_id bigint,
title character varying NOT NULL,
archive boolean NOT NULL DEFAULT false,
CONSTRAINT pk_material_category_id PRIMARY KEY (material_category_id),
CONSTRAINT fk_parent_material_category_id FOREIGN KEY (parent_material_category_id)
REFERENCES template.material_category (material_category_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
Поле parent_material_category_id будем использовать для хранения идентификатора родительской категории.
CREATE SEQUENCE template.material_id_seq;
CREATE TABLE template.material
(
material_id bigint NOT NULL DEFAULT nextval('template.material_id_seq'::regclass),
material_category_id bigint NOT NULL,
title character varying NOT NULL,
unit_id bigint NOT NULL,
unit_price numeric NOT NULL,
archive boolean NOT NULL DEFAULT false,
CONSTRAINT pk_material_id PRIMARY KEY (material_id),
CONSTRAINT fk_material_category_id FOREIGN KEY (material_category_id)
REFERENCES template.material_category (material_category_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_unit_id FOREIGN KEY (unit_id)
REFERENCES template.unit (unit_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
Подготовка формы
Создайте пустую форму TemplateMaterialList.xml с заголовком "Товарно-материальные ценности".
В главном меню на стартовой форме (TemplateStart.xml) добавьте пункт меню Списки -> ТМЦ..., по которому будет открываться новая форма со списком товарно-материальных ценностей.
Перейдем в файл TemplateMaterialList.xml, где в ContentPanel добавим три панели:
MaterialCategoryPanel - левая панель, в которой будет описываться дерево категорий товарно-материальные ценностей. Ей можно задать ширину в 400 пикселей;
MaterialPanel - правая панель, в которой будет описываться таблица товарно-материальных ценностей. Эта панель будет занимать все оставшееся место;
MaterialSeparatePanel - разделитель между панелей.
Дерево значений
Займемся левой частью формы, а именно созданием дерева категорий для товарно-материальных ценностей. В итоге у вас должна получится форма подобного вида:
В панели MaterialCategoryPanel создайте:
Надпись "Категории" (MaterialCategoryLabel), которая будет выступать заголовком для дерева значений;
Кнопки редактирования дерева категорий (добавить, редактировать и удалить).
Создайте самостоятельно команду MaterialCategoryDeleteMessageBoxCommand для кнопки удаления записи в дереве категорий.
Конструкции <Execution> на эти команды можете оставить пустыми.
Карточка редактирования узла дерева
Создадим форму для редактирования категории (TemplateMaterialCategoryEdit.xml), используя паттерн EntityForm:
Добавьте на форму объекты ParentMaterialCategoryLabel и ParentMaterialCategoryComboBox, которые будут описывать родительскую категорию. Тэги <ValueList> и <Value> для выпадающего списка оставьте пустыми - их заполним позже.
Таким образом, у вас должна получиться форма вида:
Перейдем в xml-файл серверной части (Template.xml) и создадим запрос для получения списка категорий:
Template.xml
<SqlQuery Name="ParentMaterialCategorySelectSqlQuery">
<Text>
WITH RECURSIVE tree_tmp (material_category_id, parent_material_category_id, title, path) AS (
SELECT material_category_id, parent_material_category_id, title, ARRAY[material_category_id]::bigint[]
FROM parent_mc
UNION ALL
SELECT MC.material_category_id, MC.parent_material_category_id, MC.title, T.path || ARRAY[MC.material_category_id]::bigint[]
FROM
mc_tmp MC
JOIN tree_tmp T ON T.material_category_id = MC.parent_material_category_id
), mc_tmp AS (
SELECT *
FROM template.material_category
WHERE material_category_id IS DISTINCT FROM {MaterialCategoryId} OR {WithChild}
), parent_mc AS (
SELECT *
FROM mc_tmp
WHERE parent_material_category_id IS NULL
)
SELECT
T.material_category_id AS "MaterialCategoryId",
CASE WHEN array_length(T.path, 1) > 1 THEN COALESCE(repeat('—', (array_length(T.path, 1) - 1)), '') || ' ' ELSE '' END || T.title AS "Title"
FROM
tree_tmp T
ORDER BY T.path, T.material_category_id;
</Text>
</SqlQuery>
Этот запрос будем использовать в двух местах: в карточке категории ТМЦ для выбора родительской категории и в карточке ТМЦ для выбора категории товарно-материальной ценности. Поэтому сразу добавим в него параметр {WithChild}, которым будем разделять место использования запроса: из карточки категории будем передавать false, а из карточки ТМЦ - true.
Таким образом, для карточки категории в параметр {MaterialCategoryId} будем передавать идентификатор редактируемой категории, чтобы исключить ее из списка возможных родительских категорий. Тем самым мы избежим того, что бы в качестве родительской категории была выбрана сама редактируемая категория или ее дочерние категории.
Добавьте запрос ParentMaterialCategorySelectSqlQuery в разрешение MaterialCategoryViewSqlQueryPermission, которое создалось автоматически при выполнении паттерна EntityForm.
Скорректируем запрос MaterialCategoryByIdSelectSqlQuery, чтобы он возвращал идентификатор родительской категории:
Template.xml
<SqlQuery Name="MaterialCategoryByIdSelectSqlQuery">
<Text>
SELECT
MC.title AS "Title",
MC.parent_material_category_id AS "ParentMaterialCategoryId"
FROM
template.material_category MC
WHERE
MC.material_category_id = {MaterialCategoryId};
</Text>
</SqlQuery>
Скорректируйте самостоятельно запросы MaterialCategoryInsertSqlQuery и MaterialCategoryUpdateSqlQuery, чтобы они сохраняли значение ParentMaterialCategoryId, переданное с формы.
Вернемся в файл TemplateMaterialCategoryEdit.xml и скорректируем MaterialCategoryPrimaryGetDataConnection:
Самостоятельно измените MaterialCategoryInsertSetDataConnection и MaterialCategoryUpdateSetDataConnection, добавив параметр ParentMaterialCategoryId, в который будет передаваться значение из ParentMaterialCategoryComboBox.
Создадим соединения с данными для загрузки списка родительских категорий:
Как вы помните, на форме TemplateMaterialList.xml в команде MaterialCategoryAddFormShowCommand на дочернюю форму передавался параметр ParentMaterialCategoryId, давайте создадим этот параметр:
TemplateMaterialCategoryEdit.xml
<Parameter Name="ParentMaterialCategoryId" />
Теперь можем доделать ParentMaterialCategoryComboBox, заполнив его тэги <ValueList> и <Value>:
Откройте приложение, проверьте загрузку форм и попробуйте создать несколько категорий, одна из которых не будет иметь родительской категории (например, категория "Бумага"), а остальные категории будут ссылаться на нее. Выпадающий список будет иметь вид:
Отлично! Теперь можем продолжить работать с деревом значений на форме списка.
Запрос для DatabaseTree
Перейдем в xml-файл серверной части (Template.xml) и добавим запрос для получения списка категорий (MaterialCategoryListSelectSqlQuery) и запрос для получения взаимосвязей элементов дерева (MaterialCategoryRelationSelectSqlQuery):
Template.xml
<SqlQuery Name="MaterialCategoryListSelectSqlQuery">
<Text>
SELECT
MC.material_category_id AS "MaterialCategoryId",
MC.title AS "Title"
FROM
template.material_category MC
ORDER BY MC.title, MC.material_category_id;
</Text>
</SqlQuery>
<SqlQuery Name="MaterialCategoryRelationSelectSqlQuery">
<Text>
SELECT
material_category_id AS "MaterialCategoryId",
parent_material_category_id AS "ParentMaterialCategoryId"
FROM
template.material_category;
</Text>
</SqlQuery>
Не забудем добавить их в MaterialCategoryViewSqlQueryPermission.
Вернемся в файл формы списка ТМЦ (TemplateMaterialList.xml) и создадим загружающее соединение с данными:
Обязательный тэг <Items> объекта MaterialCategoryDatabaseTree ожидает соединение с данными с двумя таблицами:
Первая таблица должна описывать линейный список элементов дерева и иметь два поля, соответствующие идентификатору элемента и его отображаемое значение;
Вторая таблица должна описывать взаимосвязи элементов дерева и иметь поля: идентификатор элемента и идентификатор родительского элемента.
Таким образом, в тэге <Items> пропишем следующее значение:
Вызов команды UpdatedTrueValueSetCommand позволит при закрытии формы списка ТМЦ уведомить родительскую форму о наличии изменений.
Запустите приложение и попробуйте добавить пару категорий, чтобы проверить работу <Execution>.
Удаление категории
Сложность удаления категории ТМЦ заключается в том, что мы не можем удалить категорию, если она содержит подкатегории и/или позиции ТМЦ. Для проверки этих условий создадим функции.
Первая будет на проверку наличия вложенных позиций ТМЦ:
CREATE OR REPLACE FUNCTION template.material_category_has_materials(in_material_category_id bigint)
RETURNS boolean AS
$BODY$
BEGIN
IF (EXISTS (SELECT * FROM template.material WHERE material_category_id = in_material_category_id))
THEN
RETURN TRUE;
END IF;
RETURN
bool_or(template.material_category_has_materials(material_category_id))
FROM template.material_category
WHERE parent_material_category_id = in_material_category_id;
END;
$BODY$
LANGUAGE plpgsql;
Вторая будет на попытку удаления категории с проверкой на наличие подкатегорий и вложенных позиций ТМЦ:
CREATE OR REPLACE FUNCTION template.material_category_try_delete(in_material_category_id bigint)
RETURNS character varying AS
$BODY$
DECLARE
_has_child_categories boolean = FALSE;
_has_child_materials boolean = FALSE;
_result character varying;
BEGIN
IF (SELECT NOT EXISTS(SELECT * FROM template.material_category WHERE material_category_id = in_material_category_id))
THEN
RETURN NULL;
END IF;
IF (SELECT used FROM template.is_used('material_category', 'material_category_id', ARRAY[]::text[], ARRAY[]::text[], in_material_category_id))
THEN
_has_child_categories = EXISTS (SELECT * FROM template.material_category WHERE parent_material_category_id = in_material_category_id);
_has_child_materials = template.material_category_has_materials(in_material_category_id);
_result = 'Выбранная категория ТМЦ ' ||
CASE
WHEN _has_child_categories AND _has_child_materials
THEN 'содержит вложенные категории и позиции ТМЦ.'
WHEN _has_child_categories
THEN 'содержит вложенные категории ТМЦ.'
WHEN _has_child_materials
THEN 'содержит вложенные позиции ТМЦ.'
ELSE 'используется в программе.'
END;
RETURN _result;
ELSE
DELETE FROM template.material_category
WHERE material_category_id = in_material_category_id;
RETURN NULL;
END IF;
END;
$BODY$
LANGUAGE plpgsql;
Если мы не можем удалить категорию с подкатегориями и/или вложенными позициями ТМЦ, то будем возвращать сообщение с причиной невозможности удаления и предлагать пользователю сделать каскадное удаление категорий, подкатегорий и вложенных позиций ТМЦ.
Создадим функцию для каскадного удаления:
CREATE OR REPLACE FUNCTION template.material_category_delete_cascade(in_material_category_id bigint)
RETURNS void AS
$BODY$
BEGIN
IF (SELECT NOT EXISTS(SELECT * FROM template.material_category WHERE material_category_id = in_material_category_id)) THEN
RETURN;
END IF;
PERFORM template.material_try_delete(array_agg(material_id))
FROM template.material
WHERE material_category_id = in_material_category_id;
PERFORM template.material_category_delete_cascade(material_category_id)
FROM template.material_category
WHERE parent_material_category_id = in_material_category_id;
IF (SELECT used FROM template.is_used('material_category', 'material_category_id', ARRAY[]::text[], ARRAY[]::text[], in_material_category_id))
THEN
UPDATE template.material_category
SET archive = TRUE
WHERE material_category_id = in_material_category_id AND NOT archive;
ELSE
DELETE FROM template.material_category
WHERE material_category_id = in_material_category_id;
END IF;
END;
$BODY$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION template.material_try_delete(in_material_id_array bigint[])
RETURNS character varying AS
$BODY$
DECLARE
_material_id bigint;
_deleted_count integer := 0;
_archived_count integer := 0;
_archived_array varchar[];
BEGIN
FOR i IN 1..COALESCE(array_length(in_material_id_array, 1), 0)
LOOP
_material_id = in_material_id_array[i];
raise notice '_material_id: %', _material_id;
IF (SELECT used FROM template.is_used('material', 'material_id', ARRAY[]::text[], ARRAY[]::text[], _material_id))
THEN
UPDATE template.material
SET archive = True
WHERE material_id = _material_id AND NOT archive;
_archived_count = _archived_count + 1;
_archived_array = _archived_array || (SELECT title FROM template.material WHERE material_id = _material_id);
ELSE
DELETE FROM template.material
WHERE material_id = _material_id;
_deleted_count = _deleted_count + 1;
END IF;
END LOOP;
RETURN CASE WHEN _archived_count > 0 THEN
'Удалено ТМЦ: ' || _deleted_count ||
E'.\rПеремещено в архив: ' || _archived_count ||
E'.\r\rСписок ТМЦ, перемещенных в архив:\r' || array_to_string(array_sort(_archived_array), E',\r') ELSE NULL END;
END;
$BODY$
LANGUAGE plpgsql;
Функция template.material_try_delete(bigint[]) возвращает сообщение о количестве удаленных и перемещенных в архив записей, а также наименования ТМЦ, отправленных в архив. Но функция template.material_category_delete_cascade(bigint), которая ее вызывает, никак не обрабатывает возвращаемое сообщение и не передает его в результат SQL-запроса, чтобы можно было получить сообщение на форме и отобразить его пользователю. Вы можете самостоятельно реализовать данную функциональность.
В функции template.material_try_delete(bigint[]) используется функция сортировки массива:
CREATE OR REPLACE FUNCTION public.array_sort(anyarray)
RETURNS anyarray AS
$BODY$
SELECT ARRAY(SELECT unnest($1) ORDER BY 1);
$BODY$
LANGUAGE sql;
Перейдем в серверный xml-файл и создадим запросы на удаление категорий:
Теперь можем заняться правой частью формы и создать сам список товарно-материальных ценностей.
На панель MaterialPanel добавим таблицу с кнопками редактирования. Для этого воспользуйтесь паттерном Table:
Выполним следующие настройки паттерна:
После применения паттерна перейдем в xml-файл серверной части и перенесем роль MaterialEditRole в группу GuestGroup, а также скорректируем текст запроса:
Template.xml
<SqlQuery Name="MaterialSelectSqlQuery">
<Text>
SELECT
M.material_id AS "MaterialId",
M.material_category_id AS "MaterialCategoryId",
M.title AS "Title",
M.unit_id AS "UnitId",
M.unit_price AS "UnitPrice"
FROM
template.material M
ORDER BY M.title;
</Text>
</SqlQuery>
Таким образом форма списка (TemplateMaterialList.xml) примет вид:
Добавим на форму соединение с данными для загрузки списка единиц измерений:
В нем используем запрос ParentMaterialCategorySelectSqlQuery, который писали для карточки редактирования категории ТМЦ.
Самостоятельно создайте все необходимые элементы формы, чтобы форма имела вид:
Если форма открыта на редактирование, то в поле "Категория" (MaterialCategoryComboBox) должна отображаться категория редактируемого ТМЦ, иначе должен подставляться идентификатор из параметра MaterialCategoryId.
Скорректируйте MaterialInsertSetDataConnection и MaterialUpdateSetDataConnection.
Запустите приложение и попробуйте создать несколько позиций для списка товарно-материальный ценностей с разными категориями.
Как видите, независимо от того, какую категорию выбрали, в списке отображаются все записи.
Реализуйте с помощью SecondaryGetDataConnection фильтрацию данных для таблицы ТМЦ на основе выбранной в дереве DatabaseTree категории.
Самостоятельно
Как вы могли заметить, в таблицах template.material_category и template.material есть колонки archive, которые мы используем в функциях на удаление категорий и ТМЦ.
Ваша задача реализовать на форме списка ТМЦ фильтр архивных и актуальных записей и кнопки для работы с архивом категорий и ТМЦ.
Для этого вам понадобятся функции:
Функция для отправки в архив ветки категорий и ТМЦ, принадлежащих этим категориям
CREATE OR REPLACE FUNCTION template.material_category_archive_child(in_material_category_id bigint)
RETURNS void AS
$BODY$
DECLARE
_archive boolean;
_material_category_id bigint;
BEGIN
_archive = archive FROM template.material_category WHERE material_category_id = in_material_category_id;
IF (NOT _archive) THEN RETURN; END IF;
UPDATE template.material
SET archive = _archive
WHERE material_category_id = in_material_category_id;
FOR _material_category_id IN (SELECT material_category_id FROM template.material_category WHERE parent_material_category_id = in_material_category_id)
LOOP
UPDATE template.material_category
SET archive = _archive
WHERE material_category_id = _material_category_id;
PERFORM template.material_category_archive_child(_material_category_id);
END LOOP;
END;
$BODY$
LANGUAGE plpgsql;
Функция восстановления всех родительских категорий у выбранной категории
CREATE OR REPLACE FUNCTION template.material_category_unarchive_parent(in_material_category_id bigint)
RETURNS void AS
$BODY$
DECLARE
_parent_material_category_id bigint;
_archive boolean;
BEGIN
SELECT parent_material_category_id, archive
INTO _parent_material_category_id, _archive
FROM template.material_category
WHERE material_category_id = in_material_category_id;
IF (_archive) THEN RETURN; END IF;
IF (_parent_material_category_id IS NOT NULL)
THEN
UPDATE template.material_category
SET archive = _archive
WHERE material_category_id = _parent_material_category_id;
PERFORM template.material_category_unarchive_parent(_parent_material_category_id);
END IF;
END;
$BODY$
LANGUAGE plpgsql;
Последовательность запросов для архивации категорий ТМЦ:
Template.xml
<SqlQuery Name="MaterialCategoryArchiveSqlQuery">
<Text>
UPDATE template.material_category
SET archive = {Archive}
WHERE material_category_id = {MaterialCategoryId};
SELECT template.material_category_archive_child({MaterialCategoryId})
WHERE {Archive};
SELECT template.material_category_unarchive_parent({MaterialCategoryId})
WHERE NOT {Archive};
</Text>
</SqlQuery>
Отправив категорию в архив, необходимо отправить в архив все дочерние категории и ТМЦ, принадлежащие выбранной категории и дочерним категориям. Восстановив категорию из архива, необходимо восстановить и все родительские категории. Это позволит корректно отображать дерево категорий на форме.
Последовательность запросов для архивации ТМЦ:
Template.xml
<SqlQuery Name="MaterialArchiveSqlQuery">
<Text>
UPDATE template.material
SET archive = {Archive}
WHERE material_id = {MaterialId};
UPDATE template.material_category MC
SET archive = False
FROM template.material M
WHERE
MC.material_category_id = M.material_category_id AND
material_id = {MaterialId} AND
NOT M.archive;
SELECT template.material_category_unarchive_parent(M.material_category_id)
FROM template.material M
WHERE
M.material_id = {MaterialId} AND
NOT M.archive;
</Text>
</SqlQuery>
При восстановлении ТМЦ из архива необходимо восстановить ее категорию и все родительские категории.
Запросы MaterialCategoryArchiveSqlQuery и MaterialArchiveSqlQuery используйте на соответствующих кнопках на форме списка ТМЦ.
Переделаем запрос на построение списка категорий, чтобы в дереве отображался признак архивной записи:
MaterialCategoryListSelectSqlQuery
Template.xml
<SqlQuery Name="MaterialCategoryListSelectSqlQuery">
<Text>
SELECT
MC.material_category_id AS "MaterialCategoryId",
MC.title || CASE WHEN archive THEN ' (арх.)' ELSE '' END AS "Title",
MC.title AS "OriginalTitle",
MC.archive AS "Archive",
MC2.material_category_id NOTNULL AS "ArchiveForFilter"
FROM
template.material_category MC
LEFT JOIN
(
WITH RECURSIVE archive_tree (material_category_id, parent_material_category_id) AS(
SELECT
material_category_id,
parent_material_category_id
FROM
template.material_category MC
WHERE
MC.archive AND NOT EXISTS (SELECT * FROM template.material_category MC2 WHERE MC2.parent_material_category_id = MC.material_category_id) OR
EXISTS (SELECT * FROM template.material M WHERE M.archive AND M.material_category_id = MC.material_category_id)
UNION
SELECT
MC.material_category_id,
MC.parent_material_category_id
FROM
template.material_category MC
JOIN archive_tree T ON (T.parent_material_category_id = MC.material_category_id)
)
SELECT DISTINCT material_category_id FROM archive_tree
) MC2 ON (MC2.material_category_id = MC.material_category_id)
ORDER BY MC.title, MC.material_category_id;
</Text>
</SqlQuery>
В соединение с данными MaterialCategoryPrimaryGetDataConnection для списка категорий добавим фильтр архивных записей:
Для дерева MaterialCategoryDatabaseTree используйте вложенный тэг <Sorted> со значением True, чтобы при изменении фильтра узлы дерева сохраняли сортировку.
Итоги
На уроке мы познакомились с объектом DatabaseTree, который отображает древовидную структуру данных. Также создали пару списков, которые будем использовать в следующем уроке для расширения возможностей карточки заказа.
Ответы
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.
Дерево категорий (MaterialCategoryDatabaseTree) - объект типа . Тэг <Items> оставьте пустым - его заполним позже;
Для проверки наличия выделенного узла дерева DatabaseTree будем использовать get-проперти , которое будем проверять в условии типа :
В командах используем тэг , чтобы иметь возможность открывать несколько экземпляров формы одновременно.
Для отслеживания двойного клика по узлу дерева используйте условие . Создайте <Execution>, который будет отслеживать это условие.
В запросе стоит обратить внимание на условие , которое для значений не NULL работает так же, как оператор <>. Однако, если оба сравниваемых значения NULL, результат будет false, и только если одно из значений NULL, возвращается true. Следовательно, если параметр {MaterialCategoryId} будет иметь значение NULL, то условие вернет true.
В этом запросе используется вычисление рекурсивного запроса, с помощью указания служебного слова RECURSIVE. Подробнее про рекурсивные запросы можете почитать по .
Обратите внимание, что у MaterialCategoryPrimaryGetDataConnection появился тэг - признак определяет, будет ли загрузка данных происходить вместе с загрузкой формы или только после ручного обновления соединения с данными. Значение тэга обратно значению параметра Edit. Когда форма открыта на редактирование (значение параметра Edit = True), значение тэга <ManualLoad> будет False - DataConnection будет обновляться вместе с загрузкой формы. А если форма будет открыта на создание, то значение тэга станет True, и DataConnection будет обновляться только по команде. Таким образом, мы можем исключать лишнее обращение к базе данных, если уверены, что там нет нужных данных.
Сразу создайте команду для его обновления.
Создадим команду, в которой через set-проперти объекта DatabaseTree будем выделять нужный элемент в нашем дереве категорий:
Создадим условие для проверки результата выполнения запроса на удаление категории:
Для создания выпадающего списка единиц измерений (UnitComboBox) используйте материал прошлого урока, где мы реализовывали логику редактирования выпадающего списка клиентов и на форме списка клиентов. Также для UnitComboBox используйте соединение с данными: