При работе с иерархическими данными в системе 1С (где часто требуется отбор СКД по табличной части), такими как ДеревоЗначений, часто возникает необходимость не просто отобразить информацию, но и провести её анализ (например, используя универсальную функцию получения результата СКД для подготовки данных), например, подсчитать количество дочерних элементов для каждой строки. Эта задача требует особого подхода, поскольку ДеревоЗначений представляет собой вложенную структуру, где каждая строка может иметь свои подчиненные строки, образуя связь, похожую на главную и подчиненную таблицы в 1С. Рассмотрим подробнее, как мы можем решить эту задачу, используя рекурсивные процедуры и взаимодействуя с реквизитами формы на сервере.
Основная сложность заключается в том, чтобы пройти по всем уровням дерева (что в чем-то схоже с задачей получения значения реквизита по ссылке на произвольной глубине вложенности), от самого верхнего до самого глубокого, и для каждой строки определить, сколько у неё непосредственных потомков. Для этого нам потребуется создать специальную колонку в дереве, которая будет хранить это количество, и написать рекурсивную процедуру, способную обходить все ветви и узлы.
Прежде чем мы начнём заполнять данные, убедимся, что в нашем ДеревоЗначений есть колонка, куда мы будем записывать подсчитанное количество строк. Если такой колонки ещё нет, её необходимо добавить программно. Обычно для хранения количества используют числовой тип.
Предположим, у нас есть реквизит формы с именем ДеревоРабот, который представляет собой ДеревоЗначений. Если мы создаём дерево программно или хотим убедиться, что колонка существует, мы можем добавить её следующим образом:
// Предполагаем, что переменная Дерево уже содержит ДеревоЗначений
Если Дерево.Колонки.Найти("Количество") = Неопределено Тогда
Дерево.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
КонецЕсли;
Эта операция гарантирует, что у каждой строки нашего дерева будет доступно поле Количество, в которое мы сможем записывать число дочерних элементов.
Ключевым элементом решения является рекурсивная процедура. Рекурсия — это процесс, при котором функция или процедура вызывает саму себя для решения подзадач. В нашем случае, обход иерархического дерева является классическим примером, где рекурсия наиболее эффективна.
Наша процедура должна выполнять следующие шаги:
Дерево.Строки для верхнего уровня, либо СтрокаДерева.Строки для подчиненных уровней).Количество будут заполнены), заполнить поле Количество для текущей строки, присвоив ему число непосредственных дочерних элементов с помощью свойства СтрокаДерева.Строки.Количество().Рассмотрим пример такой процедуры:
&НаСервере
Процедура УстановитьКоличествоСтрокДерева(КоллекцияСтрок)
Для Каждого СтрокаДерева ИЗ КоллекцияСтрок Цикл
// Шаг 1: Сначала рекурсивно обрабатываем подчиненные строки текущей строки
// Это позволяет нам сначала опуститься до самого нижнего уровня
Если СтрокаДерева.Строки.Количество() > 0 Тогда
УстановитьКоличествоСтрокДерева(СтрокаДерева.Строки);
КонецЕсли;
// Шаг 2: После того как все подчиненные уровни текущей ветви обработаны,
// заполняем колонку "Количество" для текущей строки.
// Мы подсчитываем количество ее непосредственных потомков.
СтрокаДерева.Количество = СтрокаДерева.Строки.Количество();
КонецЦикла;
КонецПроцедуры
Обратите внимание на ключевой момент: рекурсивный вызов УстановитьКоличествоСтрокДерева(СтрокаДерева.Строки); происходит до присваивания значения полю Количество текущей строки. Такой порядок обхода (глубина сначала) гарантирует, что мы всегда работаем с уже обработанными подчиненными элементами, когда доходим до родителя.
Для того чтобы наша рекурсивная процедура сработала с ДеревоЗначений, являющимся реквизитом управляемой формы, необходимо соблюсти несколько правил:
ДеревоЗначений (как со сложным объектом) должны производиться на сервере. Для этого мы пометим нашу основную процедуру директивой &НаСервере.ДеревоЗначений из реквизита формы на сервере, мы используем метод РеквизитФормыВЗначение().ДеревоЗначений обратно в реквизит формы, чтобы изменения отобразились на клиенте, с помощью метода ЗначениеВРеквизитФормы().Рассмотрим пример процедуры, которая будет вызывать нашу рекурсивную функцию:
&НаСервере
Процедура ПолучитьИЗаполнитьКоличествоНаСервере()
// 1. Получаем текущее ДеревоЗначений из реквизита формы на сервер
// "ДеревоРабот" - это имя реквизита формы типа ДеревоЗначений
Перем Дерево;
Дерево = РеквизитФормыВЗначение("ДеревоРабот");
// 2. Добавляем колонку "Количество", если ее нет.
// Это важный шаг для предотвращения ошибок при первом запуске или отсутствии колонки.
Если Дерево.Колонки.Найти("Количество") = Неопределено Тогда
Дерево.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
КонецЕсли;
// 3. Вызываем нашу рекурсивную процедуру для строк верхнего уровня дерева.
// Она начнет обход и заполнение поля "Количество" для всех уровней.
УстановитьКоличествоСтрокДерева(Дерево.Строки);
// 4. Возвращаем измененное ДеревоЗначений обратно в реквизит формы.
// Это необходимо, чтобы обновленные данные отобразились на форме у пользователя.
ЗначениеВРеквизитФормы(Дерево, "ДеревоРабот");
КонецПроцедуры
В процессе разработки и отладки могут возникнуть некоторые моменты, которые важно учитывать:
СтрокаДерева.Строки), то будет заполнен только первый уровень дерева. Именно с такой ситуацией сталкивались пользователи в процессе обсуждения, когда заполнился только верхний уровень.УстановитьКоличество(Дерево) в цикле Для Каждого А ИЗ Дерево.Строки Цикл ... КонецЦикла;. Это в корне неверно. Процедуру, инициирующую обход, нужно вызвать один раз для корневой коллекции строк дерева (Дерево.Строки). Сама рекурсивная процедура позаботится о дальнейшем обходе.РеквизитФормыВЗначение() для получения текущих данных реквизита формы на сервер и ЗначениеВРеквизитФормы() для возврата измененных данных обратно. Без этого изменения не будут видны пользователю.Таким образом, комбинируя создание колонки, рекурсивный обход и правильное взаимодействие с реквизитами формы, мы получаем мощный и гибкий механизм для подсчета количества строк на каждом уровне ДеревоЗначений. Этот подход позволяет нам эффективно управлять иерархическими данными и предоставлять пользователям более полную информацию о структуре их данных.