Колонки дерева значений в 1С: добавление, типы, заполнение
Содержание
Колонку в дерево значений добавляет метод Колонки.Добавить(): первым параметром идёт имя, вторым описание типов, третьим заголовок, четвёртым ширина. Все параметры, кроме имени, необязательны. Дерево при этом должно существовать: сначала Новый ДеревоЗначений, потом колонки, потом строки.
Типичная ситуация: нужно вывести остатки по складам с группировкой по номенклатуре, а готового дерева нет. Вы создаёте его в коде, описываете колонки и наполняете строками. Ниже всё по порядку: от создания до обхода иерархии и особенностей формы.
Создание дерева и первая колонка
Дерево значений похоже на таблицу значений, но у каждой строки есть родитель и свои подчинённые строки. Колонки у всех строк общие. Поэтому структуру колонок задают один раз на всё дерево.
Минимальный пример:
Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("Наименование");
Дерево.Колонки.Добавить("Количество");
Дерево.Колонки.Добавить("Сумма");
Здесь у колонок нет типа, в них можно положить любое значение. Для простых расчётов этого хватает. Если дерево потом уходит в отчёт, на форму или в запрос, тип лучше указать.
Колонки добавляются именно в коллекцию Колонки, а не в строки. У строк колонок нет: строка только хранит значения.
Параметры метода Добавить
| Параметр | Что задаёт | Обязателен |
|---|---|---|
| Имя | Идентификатор колонки в коде | да |
| ТипЗначения | Объект ОписаниеТипов | нет |
| Заголовок | Подпись, которую увидит пользователь | нет |
| Ширина | Ширина колонки при показе | нет |
Метод возвращает созданную колонку. Это удобно, если свойства нужно задать сразу после добавления:
Колонка = Дерево.Колонки.Добавить("Сумма");
Колонка.Заголовок = "Сумма, руб.";
Колонка с типом, заголовком и шириной
Тип задают через ОписаниеТипов. Для строки, числа и даты код выглядит так:
Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100)), "Наименование", 40);
КвЧисла = Новый КвалификаторыЧисла(15, 2);
Дерево.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", КвЧисла), "Сумма, руб.", 15);
КвДаты = Новый КвалификаторыДаты(ЧастиДаты.Дата);
Дерево.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата", , , КвДаты), "Дата документа", 12);
Для ссылочных значений указывают имя типа ссылки:
Дерево.Колонки.Добавить("Контрагент", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));
Если в колонке может быть несколько типов, перечислите их через запятую: Новый ОписаниеТипов("Число, Строка").
Типизированная колонка приводит присваиваемое значение к своему типу. Запишете в числовую колонку строку «12» и получите число 12. Запишете «abc» и получите 0. Ошибки не будет, поэтому неверные данные легко пропустить.
Что выбрать для колонки
- 🔹 Без типа: быстро, но значение не проверяется. Подходит для временных расчётов.
- 🔹 Простой тип: строка, число, дата, булево. Колонка ведёт себя предсказуемо при сортировке и выводе.
- 🔹 Ссылочный тип: на форме значение покажется как ссылка, а при выводе в отчёт можно читать реквизиты.
- 🔹 Составной тип: нужен редко, например когда колонка хранит и документ, и справочник.
Добавление колонок в уже готовое дерево
Дерево не обязано быть пустым. Часто его получают из запроса, а недостающие колонки докидывают потом:
Результат = Запрос.Выполнить();
Дерево = Результат.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
Дерево.Колонки.Добавить("Процент", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(5, 2)));
Колонка появится у всех строк сразу, значение в ней будет пустым (для типизированной числовой колонки это 0). Затем вы проходите по дереву и считаете нужное.
Что ещё умеет коллекция колонок:
Найти("Имя")возвращает колонку илиНеопределено. Через неё проверяют, нет ли уже такой колонки.Количество()даёт число колонок.Индекс(Колонка)возвращает позицию колонки.Вставить(Индекс, "Имя", ТипЗначения, Заголовок, Ширина)добавляет колонку в нужное место, а не в конец.Удалить("Имя")убирает колонку вместе со значениями.Очистить()удаляет все колонки.
Защита от повторного добавления выглядит так:
Если Дерево.Колонки.Найти("Процент") = Неопределено Тогда
Дерево.Колонки.Добавить("Процент", Новый ОписаниеТипов("Число"));
КонецЕсли;
Без неё повторный вызов процедуры упадёт с ошибкой о том, что колонка с таким именем уже есть.
Имя колонки должно быть допустимым идентификатором: без пробелов и спецсимволов, не начинается с цифры. Для красивой подписи есть заголовок. Имя «Сумма по складу» не пройдёт, пишите СуммаПоСкладу и задавайте заголовок отдельно.
Заполнение колонок данными
Строки в дереве добавляют через Строки.Добавить(). Корневая строка создаётся у самого дерева, подчинённая у строки-родителя:
Группа = Дерево.Строки.Добавить();
Группа.Наименование = "Склад основной";
Группа.Сумма = 0;
Позиция = Группа.Строки.Добавить();
Позиция.Наименование = "Бумага А4";
Позиция.Сумма = 1250.50;
К значению обращаются через точку или по имени в квадратных скобках. Второй способ нужен, когда имя колонки лежит в переменной:
Для Каждого Колонка Из Дерево.Колонки Цикл
Позиция[Колонка.Имя] = Неопределено;
КонецЦикла;
Если нужно скопировать значения из другой строки, цикл по колонкам короче, чем перечисление полей вручную.
Заполнение новой колонки по существующим данным
Допустим, в дереве уже есть суммы, а нужна доля позиции в сумме группы. Сначала считаем итог группы, потом заполняем процент:
Для Каждого Группа Из Дерево.Строки Цикл
Итог = 0;
Для Каждого Позиция Из Группа.Строки Цикл
Итог = Итог + Позиция.Сумма;
КонецЦикла;
Группа.Сумма = Итог;
Для Каждого Позиция Из Группа.Строки Цикл
Позиция.Процент = ?(Итог = 0, 0, Окр(Позиция.Сумма / Итог * 100, 2));
КонецЦикла;
КонецЦикла;
Проверка на нулевой итог обязательна: деление на 0 остановит выполнение.
Пример с числами: в группе три позиции на 200, 300 и 500. Итог 1000, доли получатся 20, 30 и 50 процентов. Если сумма долей не равна 100 из-за округления, разницу относят на самую крупную позицию.
Работа с иерархией и группировками
Когда вложенность неизвестна, нужен рекурсивный обход. Колонки одинаковы на любом уровне, поэтому одна процедура обслуживает всё дерево:
Процедура ЗаполнитьПроцент(Строки, ОбщийИтог)
Для Каждого Строка Из Строки Цикл
Строка.Процент = ?(ОбщийИтог = 0, 0, Окр(Строка.Сумма / ОбщийИтог * 100, 2));
Если Строка.Строки.Количество() > 0 Тогда
ЗаполнитьПроцент(Строка.Строки, ОбщийИтог);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Вызов: ЗаполнитьПроцент(Дерево.Строки, ИтогПоДереву).
Полезные свойства строки дерева:
Строка.Родительуказывает на вышестоящую строку, у корневой этоНеопределено.Строка.Строкихранит подчинённые строки.Дерево.Строки.Найти(Значение, "Колонка", Истина)ищет значение в колонке; третий параметрИстинавключает поиск по всем уровням.
Если нужно получить уровень вложенности строки, его считают подъёмом по родителям:
Уровень = 0;
Тек = Строка.Родитель;
Пока Тек <> Неопределено Цикл
Уровень = Уровень + 1;
Тек = Тек.Родитель;
КонецЦикла;
Если колонку нужно только для промежуточного расчёта, добавьте её в начале процедуры и удалите перед возвратом дерева. Так дерево не тащит лишние данные дальше.
Колонки в дереве значений на форме
На управляемой форме всё сложнее. Реквизит формы типа ДеревоЗначений на клиенте нельзя изменить прямым вызовом Колонки.Добавить(): набор реквизитов формы меняется специальным методом ИзменитьРеквизиты, и вызывать его надо на сервере.
&НаСервере
Процедура ДобавитьКолонкуНаФорму()
Добавляемые = Новый Массив;
Добавляемые.Добавить(Новый РеквизитФормы("Процент", Новый ОписаниеТипов("Число"), "ДеревоДанных", "Процент, %"));
ЭтотОбъект.ИзменитьРеквизиты(Добавляемые);
КонецПроцедуры
Здесь "ДеревоДанных" это имя реквизита формы с деревом, а последний параметр это заголовок. Сама колонка в элементы формы попадает не сама: нужно создать поле через Элементы.Добавить(...) и привязать его к данным вида ДеревоДанных.Процент.
Если пересоздавать реквизиты, прежние значения в дереве формы теряются. Поэтому порядок такой: сохраните данные в обычное дерево значений, измените реквизиты, потом верните данные через ЗначениеВРеквизитФормы.
Полный пример: дерево остатков с новой колонкой
Соберём всё в одну процедуру. Задача: получить дерево «склад → номенклатура» с количеством и ценой, добавить колонку «Сумма» и посчитать итоги по складам.
Функция ПостроитьДерево(Данные)
Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"), "Наименование", 40);
Дерево.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)), "Кол-во", 10);
Дерево.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)), "Цена", 12);
Дерево.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)), "Сумма", 14);
Склады = Новый Соответствие;
Для Каждого Стр Из Данные Цикл
Склад = Склады[Стр.Склад];
Если Склад = Неопределено Тогда
Склад = Дерево.Строки.Добавить();
Склад.Наименование = Стр.Склад;
Склады.Вставить(Стр.Склад, Склад);
КонецЕсли;
Позиция = Склад.Строки.Добавить();
Позиция.Наименование = Стр.Номенклатура;
Позиция.Количество = Стр.Количество;
Позиция.Цена = Стр.Цена;
Позиция.Сумма = Стр.Количество * Стр.Цена;
Склад.Сумма = Склад.Сумма + Позиция.Сумма;
КонецЦикла;
Возврат Дерево;
КонецФункции
Здесь Данные это таблица значений с колонками Склад, Номенклатура, Количество, Цена. Соответствие хранит уже созданные строки складов, чтобы не искать их каждый раз.
Проверим на числах. Склад «Основной»: 10 пачек бумаги по 250 руб. и 4 картриджа по 1800 руб. Суммы позиций 2500 и 7200, итог склада 9700. Так как колонка «Сумма» типизирована числом, у новой строки склада стартовое значение 0 и сложение работает без дополнительной инициализации.
Если колонка не типизирована, в ней лежит Неопределено, и сложение упадёт с ошибкой. Это ещё один довод указывать тип числовым колонкам.
Какой способ добавления выбрать
| Ситуация | Что делать | Ограничение |
|---|---|---|
| Дерево строите в коде с нуля | Колонки.Добавить до заполнения | Нет |
| Дерево получено из запроса | Добавить недостающие колонки после выгрузки | Имена не должны совпадать с колонками запроса |
| Дерево на форме | ИзменитьРеквизиты на сервере | Нужны поля формы и перенос данных |
| Колонка нужна на время расчёта | Добавить и удалить в той же процедуре | Не забыть удалить до возврата |
Если сомневаетесь, начинайте с простого дерева в коде и проверяйте результат в отладчике: колонки видны в свойстве Колонки переменной.
Типичные ошибки и как их исправить
Разберём то, что реально встречается при работе с колонками.
| Сообщение или симптом | Причина | Решение |
|---|---|---|
| Колонка с таким именем уже существует | Повторное добавление | Проверьте Колонки.Найти("Имя") до добавления |
| Поле объекта не обнаружено | Обращение к колонке, которой нет в дереве | Сверьте написание имени, добавьте колонку до заполнения |
| Колонки добавлены, но в таблице на форме их нет | Не созданы поля формы | Добавьте элементы формы и привяжите к данным |
| В числовой колонке ноль вместо значения | Присвоена строка, не приводимая к числу | Проверяйте данные до записи |
| Рекурсия не заканчивается | Процедура вызывает сама себя для той же коллекции | Передавайте в вызов Строка.Строки, а не Дерево.Строки |
Ещё одна ловушка: имя колонки регистр не различает, но опечатка в букве даёт ту же ошибку «Поле объекта не обнаружено». Если в коде колонка добавляется под условием, а читается всегда, обращение к ней может упасть. Либо добавляйте колонку без условий, либо читайте через проверку Колонки.Найти.
Колонки лучше добавлять до заполнения строк: так код проще читать, а структура дерева видна в начале процедуры.
Частые вопросы
Можно ли добавить колонку в дерево значений после заполнения?
Как удалить колонку из дерева значений?
Дерево.Колонки.Удалить("Имя"). Значения в этой колонке пропадут у всех строк без возможности вернуть.Чем колонки дерева значений отличаются от колонок таблицы значений?
Как узнать, есть ли в дереве нужная колонка?
Дерево.Колонки.Найти("Имя"). Если колонки нет, метод вернёт Неопределено.Как поменять порядок колонок?
Вставить на нужную позицию, перенести значения и удалить старую колонку.