Колонки дерева значений в 1С: добавление, типы, заполнение

· 8 мин

Содержание

Колонку в дерево значений добавляет метод Колонки.Добавить(): первым параметром идёт имя, вторым описание типов, третьим заголовок, четвёртым ширина. Все параметры, кроме имени, необязательны. Дерево при этом должно существовать: сначала Новый ДеревоЗначений, потом колонки, потом строки.

Типичная ситуация: нужно вывести остатки по складам с группировкой по номенклатуре, а готового дерева нет. Вы создаёте его в коде, описываете колонки и наполняете строками. Ниже всё по порядку: от создания до обхода иерархии и особенностей формы.

Создание дерева и первая колонка

Дерево значений похоже на таблицу значений, но у каждой строки есть родитель и свои подчинённые строки. Колонки у всех строк общие. Поэтому структуру колонок задают один раз на всё дерево.

Минимальный пример:

Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("Наименование");
Дерево.Колонки.Добавить("Количество");
Дерево.Колонки.Добавить("Сумма");

Здесь у колонок нет типа, в них можно положить любое значение. Для простых расчётов этого хватает. Если дерево потом уходит в отчёт, на форму или в запрос, тип лучше указать.

Колонки добавляются именно в коллекцию Колонки, а не в строки. У строк колонок нет: строка только хранит значения.

Параметры метода Добавить

ПараметрЧто задаётОбязателен
ИмяИдентификатор колонки в кодеда
ТипЗначенияОбъект ОписаниеТиповнет
ЗаголовокПодпись, которую увидит пользовательнет
ШиринаШирина колонки при показенет

Метод возвращает созданную колонку. Это удобно, если свойства нужно задать сразу после добавления:

Колонка = Дерево.Колонки.Добавить("Сумма");
Колонка.Заголовок = "Сумма, руб.";

Колонка с типом, заголовком и шириной

Тип задают через ОписаниеТипов. Для строки, числа и даты код выглядит так:

Дерево = Новый ДеревоЗначений;

Дерево.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(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 и сложение работает без дополнительной инициализации.

Если колонка не типизирована, в ней лежит Неопределено, и сложение упадёт с ошибкой. Это ещё один довод указывать тип числовым колонкам.

Какой способ добавления выбрать

СитуацияЧто делатьОграничение
Дерево строите в коде с нуляКолонки.Добавить до заполненияНет
Дерево получено из запросаДобавить недостающие колонки после выгрузкиИмена не должны совпадать с колонками запроса
Дерево на формеИзменитьРеквизиты на сервереНужны поля формы и перенос данных
Колонка нужна на время расчётаДобавить и удалить в той же процедуреНе забыть удалить до возврата

Если сомневаетесь, начинайте с простого дерева в коде и проверяйте результат в отладчике: колонки видны в свойстве Колонки переменной.

Типичные ошибки и как их исправить

Разберём то, что реально встречается при работе с колонками.

Сообщение или симптомПричинаРешение
Колонка с таким именем уже существуетПовторное добавлениеПроверьте Колонки.Найти("Имя") до добавления
Поле объекта не обнаруженоОбращение к колонке, которой нет в деревеСверьте написание имени, добавьте колонку до заполнения
Колонки добавлены, но в таблице на форме их нетНе созданы поля формыДобавьте элементы формы и привяжите к данным
В числовой колонке ноль вместо значенияПрисвоена строка, не приводимая к числуПроверяйте данные до записи
Рекурсия не заканчиваетсяПроцедура вызывает сама себя для той же коллекцииПередавайте в вызов Строка.Строки, а не Дерево.Строки

Ещё одна ловушка: имя колонки регистр не различает, но опечатка в букве даёт ту же ошибку «Поле объекта не обнаружено». Если в коде колонка добавляется под условием, а читается всегда, обращение к ней может упасть. Либо добавляйте колонку без условий, либо читайте через проверку Колонки.Найти.

Колонки лучше добавлять до заполнения строк: так код проще читать, а структура дерева видна в начале процедуры.

Перед запуском кода с деревом значений
Выполнено: 0 / 5

Частые вопросы

Можно ли добавить колонку в дерево значений после заполнения?
Да. Колонка появится у всех существующих строк с пустым значением. Но на больших деревьях это дороже, чем создать колонки заранее.
Как удалить колонку из дерева значений?
Вызовом Дерево.Колонки.Удалить("Имя"). Значения в этой колонке пропадут у всех строк без возможности вернуть.
Чем колонки дерева значений отличаются от колонок таблицы значений?
Принципом работы они похожи: имя, тип, заголовок, ширина. Разница в строках: в дереве у строки есть родитель и подчинённые строки.
Как узнать, есть ли в дереве нужная колонка?
Через Дерево.Колонки.Найти("Имя"). Если колонки нет, метод вернёт Неопределено.
Как поменять порядок колонок?
Отдельного метода перестановки у колонок нет. Рабочий способ: создать колонку через Вставить на нужную позицию, перенести значения и удалить старую колонку.