В последнее время аномально часто мне в работе попадалось дерево значений, поэтому решил написать на эту тему статью.
Попробую рассмотреть способы решения основных задач связанных с деревом значений, при этом постараюсь писать «без воды».
Дерево значений
Из названия объекта понятно, что дерево значений служит для хранения/отображения какой-либо иерархической информации. Каждая строка дерева значений может иметь какое-то количество подчиненных строк, при этом такие операции как поиск, сортировка, подсчет итогов можно проводит с учетом уровня иерархии и подчиненных строк.
Кроме этого, каждая строка дерева значений имеет свойства «Родитель» и «Строки».
Дерево значений на форме
Визуальное представление дерева значений обеспечивает элемент «Табличное поле».


Заполнение дерева значений
При заполнении дерева значений нужно помнить, что сам объект «ДеревоЗначений» и все его строки имеют свойство «Строки»и добавление новых строк на любом уровне дерева осуществляется через это свойство.
Сам же объект «ДеревоЗначений» имеет еще и свойство «Колонки», которое ничем не отличается от аналогичного свойства у таблицы значений.
Небольшой пример программного заполнения таблицы значений для управляемых форм:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | &НаСервере Процедура ЗаполнитьТаблицу() тДерево = РеквизитФормыВЗначение("Дерево"); нСтр1 = тДерево.Строки.Добавить(); нСтр1.Колонка1 = "Колонка 1"; нСтр1.Колонка2 = "Колонка 2"; нСтр2 = нСтр1.Строки.Добавить(); нСтр2.Колонка1 = "Колонка 1"; нСтр2.Колонка2 = "Колонка 2"; нСтр3 = нСтр2.Строки.Добавить(); нСтр3.Колонка1 = "Колонка 1"; нСтр3.Колонка2 = "Колонка 2"; нСтр4 = нСтр3.Родитель.Родитель.Строки.Добавить(); нСтр4.Колонка1 = "Колонка 1"; нСтр4.Колонка2 = "Колонка 2"; ЗначениеВРеквизитФормы(тДерево, "Дерево"); КонецПроцедуры |
Обход дерева значений
Обход всех строк дерева значений делается при помощи рекурсии, вот так будет выглядеть код для обхода дерева созданного в примере выше:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | &НаСервере Процедура ОбходНаСервере() тДерево = РеквизитФормыВЗначение("Дерево"); ОбходДерева(тДерево); КонецПроцедуры &НаСервере Процедура ОбходДерева(Дерево) Для Каждого тСтр Из Дерево.Строки Цикл Сообщить(тСтр.Колонка1+", "+тСтр.Колонка2); Если тСтр.Строки.Количество()>0 Тогда ОбходДерева(тСтр); КонецЕсли; КонецЦикла; КонецПроцедуры |
Как свернуть и развернуть дерево значений
Сворачивается и разворачивается дерево значений очень просто.
Свернуть:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | &НаКлиенте Процедура Свернуть() Элементы.Дерево.Свернуть(Элементы.Дерево.ТекущаяСтрока); КонецПроцедуры //////////////////////////////////////////////// &НаКлиенте Процедура СвернутьВерхниеСтроки(Команда) тЭлементы = Дерево.ПолучитьЭлементы(); Для Каждого тСтр Из тЭлементы Цикл Элементы.Дерево.Свернуть(тСтр.ПолучитьИдентификатор()); КонецЦикла; КонецПроцедуры //////////////////////////////////////////////// &НаКлиенте Процедура СвернутьВсе(Команда) тЭлементы = Дерево.ПолучитьЭлементы(); СвернутьРекурсия(тЭлементы); КонецПроцедуры &НаКлиенте Процедура СвернутьРекурсия(тЭлементы) Для Каждого тСтр Из тЭлементы Цикл тСтрЭлементы = тСтр.ПолучитьЭлементы(); Свернутьв(тСтрЭлементы); Элементы.Дерево.Свернуть(тСтр.ПолучитьИдентификатор()); КонецЦикла; КонецПроцедуры |
Привел три примера: для сворачивания текущей строки, для сворачивания строк верхнего уровня, для сворачивания вообще всех строк (рекурсия).
Развернуть:
1 2 3 4 5 6 7 8 9 10 11 12 | &НаКлиенте Процедура Развернуть() Элементы.Дерево.Развернуть(Элементы.Дерево.ТекущаяСтрока, Истина); КонецПроцедуры //////////////////////////////////////////////// &НаКлиенте Процедура РазвернутьВсе(Команда) тЭлементы = Дерево.ПолучитьЭлементы(); Для Каждого тСтр Из тЭлементы Цикл Элементы.Дерево.Развернуть(тСтр.ПолучитьИдентификатор(), Истина); КонецЦикла; КонецПроцедуры |
Два примера: для разворачивания текущей строки и для разворачивания всех строк. У метода «Развернуть» есть дополнительный параметр, который позволяет указать нужно ли разворачивать подчиненные строки.
Как удалить строку и очистить дерево значений
Тут опять же все просто, нужно помнить, что при удалении/очистке строки, все подчиненные строки удаляются.
Очистить дерево значений:
1 2 3 4 5 6 7 8 | &НаСервере Процедура ОчиститьНаСервере() тДерево = РеквизитФормыВЗначение("Дерево"); тДерево.Строки.Очистить(); ЗначениеВРеквизитФормы(тДерево, "Дерево"); КонецПроцедуры |
Точно также можно очистить от подчиненных элементов другую другую строку.
Удалить строку дерева значений не сложнее — нужно только знать ее индекс:
1 2 3 4 5 6 7 8 9 10 11 12 13 | &НаКлиенте Процедура Удалить(Команда) УдалитьНаСервере(); КонецПроцедуры &НаСервере Процедура УдалитьНаСервере() тДерево = РеквизитФормыВЗначение("Дерево"); тДерево.Строки.Удалить(0); ЗначениеВРеквизитФормы(тДерево, "Дерево"); КонецПроцедуры |
Запрос и дерево значений
Результат выполнения запроса очень легко преобразовать в дерево значений, для этого нужно воспользоваться методом «Выгрузить» и указать параметр «ТипОбхода» отличным от того, что стоит по умолчанию, т.е. «ПоГруппировкам» или «ПоГруппировкамСИерархией».
Если на форме имеется реквизит «ДеревоЗначений» и связанный с ним визуальный элемент, то можно сделать примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | &НаСервере Процедура ЗаполнитьИзЗапрос() тДерево = РеквизитФормыВЗначение("Дерево"); Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | Тест.Колонка1 КАК Колонка1, | Тест.Колонка2 КАК Колонка2 |ИЗ | Справочник.Тест КАК Тест"; Выгрузка = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам); тДерево = Выгрузка; ЗначениеВРеквизитФормы(тДерево, "Дерево"); КонецПроцедуры |
Причем полного совпадения колонок и типов не требуется — лишние колонки будут просто отброшены, а колонки с различными типами будут заполнены пустыми значениями.
Дерево значений в таблицу значений и обратно
Преобразовать дерево значений в таблицу значений и наоборот достаточно просто, ведь дерево значений это та же таблица значений, но с дополнительной колонкой — «Родитель». У меня есть отдельная статья о том как преобразовать дерево значений в таблицу значений и обратно.
Отбор в дереве значений
Стандартного отбора в дереве значений не предусмотрено. Так получилось потому, что непонятно как разрешать ситуацию, когда родительский элемент не удовлетворяет условию отбора, а подчиненные ему элементы удовлетворяют.
Таким образом, если Вам нужно реализовать отбор в дереве значений, то начать нужно с решения именно этой проблемы. А уже после этого можно придумать несколько способов реализовать задуманное.
Первый способ — накладывать отбор до вывода дерева значений (в запросе например). Это не классический отбор, но в тех случаях когда этот способ применим, то следует применять именно его, так как это почти всегда быстрее и правильнее чем что-либо другое.
Второй способ — перебор всех строк дерева значений. Описывать здесь особенно нечего, нужно просто взять обход дерева значений, проверять каждую строку на соответствие условию отбора и удалять лишние строки.
Еще один способ заключается в том, чтобы преобразовать дерево значений в таблицу значений, сделать отбор в таблице значений, проконтролировать результат (почистить «хвосты» — строки, родитель которых не удовлетворил условию отбора) и выполнить обратное преобразование в дерево значений.
На этом все, рассказал все, что знал, надеюсь мне удалось сэкономить Вам немного времени.
Если Вы нашли ошибку или неточность, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
Спасибо за хорошую статью.
Спасибо, помогло!
спасибо за объяснение
Огромное спасибо!!! Очень помогла статья!!!
Добрый день!
Как можно поменять родителя в дереве значения, для того чтобы элемент дерева с одной группы перешел в другую группу дерева?
Отличный сайт!
Ошибка!
Откуда эта процедура взялась?
Свернутьв(тСтрЭлементы);
Я сначала задавал себе такой же вопрос. Потом разобрался.
Выкладываю решение для тех у кого будут аналогичные проблемы:
// Процедура которая реализует команду формы (кнопка на форме)
// Данная команда сворачивает дерево значений (реквизит формы)
// которое выведено в элемент формы типа «Таблица формы»
&НаКлиенте
Процедура СвернутьДерево(Команда)
ЭлементыДерева = ДеревоВидовНоменклатуры.ПолучитьЭлементы();
СвернутьРекурсия(ЭлементыДерева, «ДеревоВидовНоменклатуры»);
КонецПроцедуры
// Рекурсивная процедура, которая сворачивает элементы дерева на форме
// ЭлементыДерева — элементы дерева значений (реквизит формы)
// ИмяЭлементаФормы — имя элементы формы, в который выведено дерево (таблица формы)
&НаКлиенте
Процедура СвернутьРекурсия(ЭлементыДерева, ИмяЭлементаФормы)
Для Каждого Стр Из ЭлементыДерева Цикл
СтрЭлементы = Стр.ПолучитьЭлементы();
СвернутьРекурсия(СтрЭлементы, ИмяЭлементаФормы);
Элементы[ИмяЭлементаФормы].Свернуть(Стр.ПолучитьИдентификатор());
КонецЦикла;
КонецПроцедуры
Искал вариант организации выбора строки из дерева. Здесь не нашел.
Прекрасная статья, благодарю.
Отбор в дереве можно организовать через условное оформление, поле «Видимость», по условию добавленного служебного поля
Толково. Спасибо!
них не понятно. откуда что взялось. всё названо одним и тем словом дерево и сиди разбирайся что где отчего взялось.
очередная статься написанная для себя любимого ,а не для люедй
Евгений, а вы хотели чтобы вам тут все разжевали, разложили по полкам, предложили решение каждого уникального случая и предложили решение на все случаи жизни?
Такого не будет ни где, потому что тема достаточно сложная.
Программист на то такая профессия и есть чтобы искать решение в безвыходных ситуациях.
Автор предложил достаточно универсальные способы подходящие под большинство случаев в практике.
Эти способы необходимо преобразовать под ваш конкретный случай.
Евгений, ты имбицил
Ты прав, во первых такого нет на управляемых формах. А не управляемые никто уже не использует. Не пойму что тут толкового? Сложно было написать что табличное поле не существует в природе на УФ?
Большое спасибо за эту статью!
Она помогла мне со сворачиванием и разворачиванием Дерева формы на управляемой форме.