В последнее время аномально часто мне в работе попадалось дерево значений, поэтому решил написать на эту тему статью.
Попробую рассмотреть способы решения основных задач связанных с деревом значений, при этом постараюсь писать «без воды».
Дерево значений
Из названия объекта понятно, что дерево значений служит для хранения/отображения какой-либо иерархической информации. Каждая строка дерева значений может иметь какое-то количество подчиненных строк, при этом такие операции как поиск, сортировка, подсчет итогов можно проводит с учетом уровня иерархии и подчиненных строк.
Кроме этого, каждая строка дерева значений имеет свойства «Родитель» и «Строки».
Дерево значений на форме
Визуальное представление дерева значений обеспечивает элемент «Табличное поле».
Заполнение дерева значений
При заполнении дерева значений нужно помнить, что сам объект «ДеревоЗначений» и все его строки имеют свойство «Строки»и добавление новых строк на любом уровне дерева осуществляется через это свойство.
Сам же объект «ДеревоЗначений» имеет еще и свойство «Колонки», которое ничем не отличается от аналогичного свойства у таблицы значений.
Небольшой пример программного заполнения таблицы значений для управляемых форм:
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 |ИЗ | Справочник.Тест КАК Тест"; Выгрузка = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам); тДерево = Выгрузка; ЗначениеВРеквизитФормы(тДерево, "Дерево"); КонецПроцедуры |
Причем полного совпадения колонок и типов не требуется — лишние колонки будут просто отброшены, а колонки с различными типами будут заполнены пустыми значениями.
Дерево значений в таблицу значений и обратно
Преобразовать дерево значений в таблицу значений и наоборот достаточно просто, ведь дерево значений это та же таблица значений, но с дополнительной колонкой — «Родитель». У меня есть отдельная статья о том как преобразовать дерево значений в таблицу значений и обратно.
Отбор в дереве значений
Стандартного отбора в дереве значений не предусмотрено. Так получилось потому, что непонятно как разрешать ситуацию, когда родительский элемент не удовлетворяет условию отбора, а подчиненные ему элементы удовлетворяют.
Таким образом, если Вам нужно реализовать отбор в дереве значений, то начать нужно с решения именно этой проблемы. А уже после этого можно придумать несколько способов реализовать задуманное.
Первый способ — накладывать отбор до вывода дерева значений (в запросе например). Это не классический отбор, но в тех случаях когда этот способ применим, то следует применять именно его, так как это почти всегда быстрее и правильнее чем что-либо другое.
Второй способ — перебор всех строк дерева значений. Описывать здесь особенно нечего, нужно просто взять обход дерева значений, проверять каждую строку на соответствие условию отбора и удалять лишние строки.
Еще один способ заключается в том, чтобы преобразовать дерево значений в таблицу значений, сделать отбор в таблице значений, проконтролировать результат (почистить «хвосты» — строки, родитель которых не удовлетворил условию отбора) и выполнить обратное преобразование в дерево значений.
На этом все, рассказал все, что знал, надеюсь мне удалось сэкономить Вам немного времени.
Спасибо за хорошую статью.
Спасибо, помогло!
спасибо за объяснение
Огромное спасибо!!! Очень помогла статья!!!
Добрый день!
Как можно поменять родителя в дереве значения, для того чтобы элемент дерева с одной группы перешел в другую группу дерева?
Отличный сайт!
Ошибка!
Откуда эта процедура взялась?
Свернутьв(тСтрЭлементы);
Я сначала задавал себе такой же вопрос. Потом разобрался.
Выкладываю решение для тех у кого будут аналогичные проблемы:
// Процедура которая реализует команду формы (кнопка на форме)
// Данная команда сворачивает дерево значений (реквизит формы)
// которое выведено в элемент формы типа «Таблица формы»
&НаКлиенте
Процедура СвернутьДерево(Команда)
ЭлементыДерева = ДеревоВидовНоменклатуры.ПолучитьЭлементы();
СвернутьРекурсия(ЭлементыДерева, «ДеревоВидовНоменклатуры»);
КонецПроцедуры
// Рекурсивная процедура, которая сворачивает элементы дерева на форме
// ЭлементыДерева — элементы дерева значений (реквизит формы)
// ИмяЭлементаФормы — имя элементы формы, в который выведено дерево (таблица формы)
&НаКлиенте
Процедура СвернутьРекурсия(ЭлементыДерева, ИмяЭлементаФормы)
Для Каждого Стр Из ЭлементыДерева Цикл
СтрЭлементы = Стр.ПолучитьЭлементы();
СвернутьРекурсия(СтрЭлементы, ИмяЭлементаФормы);
Элементы[ИмяЭлементаФормы].Свернуть(Стр.ПолучитьИдентификатор());
КонецЦикла;
КонецПроцедуры
Искал вариант организации выбора строки из дерева. Здесь не нашел.
Прекрасная статья, благодарю.
Отбор в дереве можно организовать через условное оформление, поле «Видимость», по условию добавленного служебного поля
Толково. Спасибо!
них не понятно. откуда что взялось. всё названо одним и тем словом дерево и сиди разбирайся что где отчего взялось.
очередная статься написанная для себя любимого ,а не для люедй
Евгений, а вы хотели чтобы вам тут все разжевали, разложили по полкам, предложили решение каждого уникального случая и предложили решение на все случаи жизни?
Такого не будет ни где, потому что тема достаточно сложная.
Программист на то такая профессия и есть чтобы искать решение в безвыходных ситуациях.
Автор предложил достаточно универсальные способы подходящие под большинство случаев в практике.
Эти способы необходимо преобразовать под ваш конкретный случай.
Евгений, ты имбицил
Ты прав, во первых такого нет на управляемых формах. А не управляемые никто уже не использует. Не пойму что тут толкового? Сложно было написать что табличное поле не существует в природе на УФ?
Большое спасибо за эту статью!
Она помогла мне со сворачиванием и разворачиванием Дерева формы на управляемой форме.