gifts2017

Рекурсивный обход дерева значений с пересчетом иерархических итогов группировок

Опубликовал V. L. (Vladal) в раздел Программирование - Практика программирования

Хочу поделиться своим опытом работы с деревом значений. Оно во многом напоминает таблицу значений, но в отличие от неё кроме строк имеет группировки, которые можно обходить иерархически на манер дерева справочника. Но что больше всего мне понравилось, так это возможность вычислять итоги группировок с учетом иерархии на манер группировок запроса. Этим и займёмся.

Хочу поделиться своим опытом работы с деревом значений. Оно во многом напоминает таблицу значений, но в отличие от неё кроме строк имеет группировки, которые можно обходить иерархически на манер дерева справочника. Но что больше всего мне понравилось, так это возможность вычислять итоги группировок с учетом иерархии на манер группировок запроса.

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

Для примера я взял демо-базу «Примеры ИТС» с диска ИТС.

В Конфигураторе создадим новый внешний отчет, назовём его ПримерИспользованияДереваЗначений.

Свойства отчета

Создадим форму отчета, нажав на лупу в поле Основная форма внешнего отчета. В конструкторе форм ничего менять не будем, нажмем Готово. Откроется новое окно с новой формой. На форму поместим табличное поле. Сделать это можно несколькими способами и каждый может использовать тот, какой ему удобно. Я буду описывать только один из них, конечный результат будет тот же.

Чтобы добавить на форму новое табличное поле, вызовем пункт Форма главного меню, в нём пункт Вставить элемент управления. Появится новый мастер, в котором сразу зададим осмысленный идентификатор этого поля как ТЗВыборка, т.к. в это поле поместим выборку результата запроса. Сменим Тип значения с таблицы значений на ДеревоЗначений.

Вставка и настройка табличного поля 

После поместим это поле на форму отчета и перейдем в модуль формы.

Там уже есть процедура КнопкаСформироватьНажатие(Кнопка), которая сгенерирована конструктором формы и является обработчиком нажатия кнопки Сформировать. Вместо строки // Вставить содержимое обработчика впишем свой обработчик.

Результат нашего запроса будет иметь три колонки – Склад, Номенклатура и Количество, мы программно добавим еще одну колонку – «Факт. остаток», данные которой и будем просчитывать. Обратите внимание, чтобы сформировать иерархическое дерево, запрос обязательно должен иметь раздел  ИТОГИ, который и определяет иерархию. Если в запросе объявить общие итоги и такой запрос выгрузить в дерево значений, то в первой строке будет пустая группировка, внутри которой будет продублирована вся остальная выборка. Пока нам это не нужно.

Процедура КнопкаСформироватьНажатие(Кнопка)

   
Запрос = Новый Запрос;
   
Запрос.Текст =
   
"ВЫБРАТЬ
    |   УчетНоменклатурыОстатки.Склад КАК Склад,
    |   УчетНоменклатурыОстатки.Номенклатура КАК Номенклатура,
    |   УчетНоменклатурыОстатки.КоличествоОстаток КАК Количество
    |ИЗ
    |   РегистрНакопления.УчетНоменклатуры.Остатки КАК УчетНоменклатурыОстатки
    |ИТОГИ
    |   СУММА(Количество)
    |ПО
    |   Склад,
    |   Номенклатура ИЕРАРХИЯ"
;

   
// Выполним запрос и поместим его результат в табличное поле на форме.
   
ЭлементыФормы.ТЗВыборка.Значение = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

   
// Для определения типа добавляемой колонки создадим описание типа Число 12.2
   
МассивТипов = Новый Массив;
   
МассивТипов.Добавить(Тип("Число"));
   
КЧ = Новый КвалификаторыЧисла(12, 2);
   
ОписаниеЧисло = Новый ОписаниеТипов(МассивТипов, КЧ);

   
// Добавляем новую колонку описанного типа
   
КолонкиВыборки.Добавить("ФактОстаток", ОписаниеЧисло, "Факт. остаток");

   
// Для того, чтобы были видны колонки, их надо создать
   
ЭлементыФормы.ТЗВыборка.СоздатьКолонки();

КонецПроцедуры

Сохраним и сформируем этот отчет. Можно поиграть с формой и поменять значения колонок:

Отчет сформирован 

Данные менять можно, но наша цель – пересчитывать итоги по группировкам после изменения значений в колонке ФактОстаток.

Чтобы не было группировок по каждой строке номенклатуры, в тексте запроса нужно изменить тип иерархии для номенклатуры как ТОЛЬКО ИЕРАРХИЯ.

Вернемся к нашему дереву значений. В конфигураторе откроем диалог формы, вызовем свойства поля ТЗВыборка и перейдём в раздел Обработчики. Т.к. никаких проверок на вводимые данные делать не будем, нас интересует событие ПриОкончанииРедактирования. Нажмём лупу этого обработчика и попадём в новую процедуру обработчика. Впишем свой текст процедуры.

 

Обработчики табличного поля

 

Процедура ТЗВыборкаПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования, Отказ)

   
// Пересчитаем итоги группировок в дереве
   
СчитатьСуммыДЗ(ТЗВыборка.Строки);

КонецПроцедуры

 

Ниже опишем процедуру СчитатьСуммыДЗ. Она в цикле перебирает таблицу и если текущая строка группировка, рассчитывается итог подчиненных строк. Группровка имеет несколько подчиненных ей строк, количество которых можно узнать методом Количество() коллекции строк. Если Строки.Количество()=0, то мы попали в обычную строку. Нас она пока не интересует.

Так как структура иерархическая, будем её обходить в рекурсии.

 

Процедура СчитатьСуммыДЗ(СтрокиДЗ)

    Для Каждого
СтрокаДерева Из СтрокиДЗ Цикл

       
// Если мы в строке, у неё нет итогов, пропустим
       
Если СтрокаДерева.Строки.Количество() = 0 Тогда
            Продолжить;
        КонецЕсли;

       
СчитатьСуммыДЗ(СтрокаДерева.Строки);

       
// Для текущей записи вычислим сумму строк колонки
       
СтрокаДерева.ФактОстаток = СтрокаДерева.Строки.Итог("ФактОстаток");

    КонецЦикла;

КонецПроцедуры
// СчитатьСуммыДЗ()

Сохраним и запустим отчет. Попробуем ввести данные в строке и увидим, что иерархические итоги пересчитались: сначала группировка выбранной строки «1С:Бухгалтерия ПРОФ версия 7.7», потом итоги в группе «Программное обеспечение» справочника Номенклатура и в самом корне нашего дерева, элементе справочника Склады «Склад отдела продаж».

Отчет с пересчетом

На данном этапе поставленную задачу считаем решенной.

Можно немного усложнить эту обработку, запретив менять колонки, отличные от Факт. остаток. Для этого задействуем обработчик ПередНачаломИзменения

Процедура ТЗВыборкаПередНачаломИзменения(Элемент, Отказ)

   
// Разрешим ввод только в колонке ФактОстаток
   
Если Элемент.ТекущаяКолонка.Имя <> "ФактОстаток" Тогда
       
Отказ = Истина;
    КонецЕсли;

КонецПроцедуры

Можно добавить и печать этого дерева:

Процедура ВывестиСекцию(Секция, Выборка)

   
Область = Макет.ПолучитьОбласть(пСекция);
   
Область.Параметры.Заполнить(пВыборка);

   
ЭлементыФормы.ТабДок.Вывести(Область, Выборка.Уровень()+1);

КонецПроцедуры

Процедура
ПечататьДерево(СтрокиДЗ)

   
// Уровни группировок:
    //    0 - Склад
    //    1 - Товар
    //    2 - Регистратор

   
Для Каждого СтрокаДерева Из СтрокиДЗ Цикл

        Если
СтрокаДерева.Строки.Количество() = 0 Тогда
            Если
ЗначениеЗаполнено(СтрокаДерева.Номенклатура) Тогда
               
ВывестиСекцию("Номенклатура", СтрокаДерева);
            КонецЕсли;
            Продолжить;
// Обошли строки, возврат к группировкам, иначе будет каша
       
КонецЕсли;

       
ВывестиСекцию(?(СтрокаДерева.Уровень()=0, "Склад", "Номенклатура"), СтрокаДерева);
       
ПечататьДерево(СтрокаДерева.Строки);

    КонецЦикла;

КонецПроцедуры
// СчитатьСуммыДЗ()

Теперь займёмся украшательствами.

Определим обработчик таблицы ПриВыводеСтроки и его код:

 

Процедура ТЗВыборкаПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)

    Попытка
       
ВСтрокеГруппа = ДанныеСтроки.Номенклатура.ЭтоГруппа;
    Исключение
       
ВСтрокеГруппа = Неопределено;
    КонецПопытки;
    Если
ВСтрокеГруппа=Неопределено Тогда
       
ОформлениеСтроки.ЦветФона = Новый Цвет(255, 240, 183);
       
ОформлениеСтроки.Шрифт = Новый Шрифт(ОформлениеСтроки.Шрифт , , , Истина, Ложь);
    ИначеЕсли
ВСтрокеГруппа Тогда
       
ОформлениеСтроки.Шрифт = Новый Шрифт(ОформлениеСтроки.Шрифт , , , Истина, Ложь);
       
ОформлениеСтроки.ЦветФона = Новый Цвет(255, 245, 207);
    Иначе
        
//ОформлениеСтроки.ЦветФона = Новый Цвет(255, 245, 207);
       
ОформлениеСтроки.Шрифт = Новый Шрифт(ОформлениеСтроки.Шрифт , , , Ложь, Ложь);
    КонецЕсли;

КонецПроцедуры

Вот всё в сборе - и покраска группировок, и выделение групп номенклатуры, и печать...

Готов к употреблению


Добавлено 16 июля 2010

В комментариях поступило предложение сделать двухуровневое дерево:

- Склад

--- Группа товара

----- Товар

 

Проанализировав колонку Номенклатура, я пришел к выводу, что в группировке по Складу, т.е. в самом верхнем уровне, номенклатура имеет значение NULL. Как раз это пустое значение и нужно заменить на итоговую строку по складу, что можно сделать в запросе в секции ИТОГИ:

 

|ИТОГИ
|   ВЫБОР
|       КОГДА Номенклатура ЕСТЬ NULL
|           ТОГДА УчетНоменклатурыОстатки.Склад
|       ИНАЧЕ Номенклатура
|   КОНЕЦ КАК Номенклатура,
|   СУММА(КоличествоОстаток)

 

Вроде бы всё, но остаётся первая колонка Склад, в которой находится иерархия и от неё нужно избавиться - это легко. Достаточно в описании полей запроса закомменитровать описание склада. В совокупоности с выбором по типу значений в итогах получится требуемое двухуровневое дерево:

ВЫБРАТЬ
//  УчетНоменклатурыОстатки.Склад КАК Склад,
   
УчетНоменклатурыОстатки.Номенклатура КАК Номенклатура,
   
УчетНоменклатурыОстатки.КоличествоОстаток КАК КоличествоОстаток
ИЗ
   
РегистрНакопления.УчетНоменклатуры.Остатки КАК УчетНоменклатурыОстатки
ИТОГИ
   
ВЫБОР
        КОГДА
Номенклатура ЕСТЬ NULL
            ТОГДА
УчетНоменклатурыОстатки.Склад
        ИНАЧЕ Номенклатура
    КОНЕЦ КАК Номенклатура,
   
СУММА(КоличествоОстаток)
ПО
   
УчетНоменклатурыОстатки.Склад,
   
Номенклатура ТОЛЬКО ИЕРАРХИЯ

 

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

Изменение макета 

Процедура КнопкаСформироватьНажатие(Кнопка)

    ...

   
// Визуализация дерева
    // ЭлементыФормы.ТЗВыборка.Колонки.Склад.Ширина = 40;
   
ЭлементыФормы.ТЗВыборка.Колонки.Номенклатура.Ширина = 60;
   
ЭлементыФормы.ТЗВыборка.Колонки.КоличествоОстаток.Ширина = 20;
   
ЭлементыФормы.ТЗВыборка.Колонки.ФактОстаток.Ширина = 20;

    ...

КонецПроцедуры

...

Процедура ТЗВыборкаПередНачаломИзменения(Элемент, Отказ)

   
// Разрешим изменять только колонку ФактОстаток
   
Если НЕ Элемент.ТекущаяКолонка.Имя = "ФактОстаток" Тогда

       
Отказ = Истина;
        Возврат;

    КонецЕсли;

   
// Теперь в разрешенной колонке разрешим менять только в строках,
    // т.к. группы и склады всеравно пересчитываются
   
СодержимоеНоменклатуры = Элемент.ТекущаяСтрока.Номенклатура;
   
ВСтрокеЕстьГруппа = (ТипЗнч(СодержимоеНоменклатуры) = Тип("СправочникСсылка.Склады"))
                        ИЛИ
СодержимоеНоменклатуры.ЭтоГруппа;

   
Отказ = ВСтрокеЕстьГруппа;

КонецПроцедуры

...

 

Процедура ТЗВыборкаПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)

   
ТекущиеДанные = ДанныеСтроки.Номенклатура;
    Если
ТипЗнч(ТекущиеДанные) = Тип("СправочникСсылка.Склады") Тогда
       
ОформлениеСтроки.ЦветФона = Новый Цвет(255, 240, 183);
       
ОформлениеСтроки.Шрифт = Новый Шрифт(ОформлениеСтроки.Шрифт , , , Истина, Ложь);
    ИначеЕсли
ТекущиеДанные.ЭтоГруппа Тогда
       
ОформлениеСтроки.Шрифт = Новый Шрифт(ОформлениеСтроки.Шрифт , , , Истина, Ложь);
       
ОформлениеСтроки.ЦветФона = Новый Цвет(255, 245, 207);
    Иначе
       
ОформлениеСтроки.Шрифт = Новый Шрифт(ОформлениеСтроки.Шрифт , , , Ложь, Ложь);
    КонецЕсли;

КонецПроцедуры


Поставленная задача решена.

Двухуровневое дерево 


Всё можно в готовом виде взять в прикрепленной обработке.

Выражаю благодарность Павлу Данилковичу за объяснение рекурсивного обхода.

При написании статьи использовались примеры из книги «А. П. Габец, Д. И. Гончаров. 1С:Предприятие 8.1 Простые примеры разработки»

Мне интересно ваше мнение и пожелания по этой обработке.

Скачать файлы

Наименование Файл Версия Размер Кол. Скачив.
Пример использования дерева значений
.erf 10,01Kb
16.07.10
413
.erf 10,01Kb 413 Скачать

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Игорь Исхаков (Ish_2) 02.07.10 10:07
Замечание.
"Приятней глазу" следующее дерево:
Склад
---- Группа товаров
------Товар

Т.е. в одной колонке Склад и товар.
Как в запросе получить такое дерево описано у Alex-is http://infostart.ru/public/71130/
Vladal; pashoid; +2 Ответить 2
2. vladal (Vladal) 05.07.10 14:23
3. Павел Данилкович (pashoid) 09.07.10 15:32
Молодец, что все так аккуратно формализовал.
4. Андрей Д. (detec) 16.07.10 09:54
Огромный плюс за качественное оформление.
5. vladal (Vladal) 16.07.10 20:25
(1) Ish_2, спасибо за идею. 2 недели бился, пока не закомментировал первую строчку запроса. Теперь всё красиво.
6. vladal (Vladal) 16.07.10 20:30
(4) Спасибо, что оценили старания. Для меня кроме красившести важно, чтобы было правильно и доступно описано.
марокко; +1 Ответить
7. Dmitry The Wing (wing) 17.11.10 10:19
(0) В порядке занудства: заметил странную семерку на первом дереве в группе ПО:
4 винды + 1 Бух + 1 Аспект = 7? В классической арифметике это будет 6 ... какая же используется у Вас? ;)
8. vladal (Vladal) 19.11.10 01:44
(7) Спасибо! Как доберусь до 1С, поправлю!
Действительно, арифметика не та...
По картинкам прошелся - вроде нирмально. На последней даже правильно ;)
9. rusrus rusrus (rusrus) 09.05.11 22:05
10. Михаил Ражиков (tango) 09.05.11 23:22
мдя. ну и цветы цветут бывает
- : информатику в школе учить надо было
11. vladal (Vladal) 11.05.11 20:34
(10) А что не так?

Инфматику в школе учили - алгоритмический язык, листок разлинеенный, для каждой переменной своя колонка. Каждый шаг программы - новая строчка и ручками писали новое значение переменной.

Я так понимаю, что-то Вам не понравись. Но что?
12. Михаил Ражиков (tango) 11.05.11 20:56
(11) не понравилось:
а) вся публикация в целом в духе:
"Для примера я взял демо-базу «Примеры ИТС» с диска ИТС.
В Конфигураторе создадим новый внешний отчет, назовём его ПримерИспользованияДереваЗначений.
Свойства отчета
Создадим форму отчета, нажав на лупу в поле Основная форма внешнего отчета."

ну, как бы "профессиональное" сообщество, а не "мастер-класс по нажиманию лупы для создания отчета"...

б) КолонкиВыборки в процедуре КнопкаСформироватьНажатие не определена

в)ЭлементыФормы.ТЗВыборка.Значение = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
vc
ТЗВыборка = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

а дольше уже ниасилил, мильпардон
13. Михаил Ражиков (tango) 11.05.11 21:19
(0) извини, ничего личного, но:
а) демпинг из-за множества нулевых 1снегов реально достал
из практики, собеседование, на з/п 80:
вопрос: зачем нужны оборотные регистры?
ответ (дословно): для учета взаиморасчетов

пс: наверное, ты не поймешь этот пост. если втянешься - поймешь потом
14. Илья Кравченко (hohmankia) 13.10.11 08:32
15. Игорь Дмитрич (Igortid) 15.10.11 18:12
Огромный плюс ...после прочтения "вырастил " первое дерево
16. vladal (Vladal) 17.10.11 18:09
(15) Igortid, спасибо, приятно знать, что не зря старался.
17. Eugeneer (Eugeneer) 15.12.11 19:08
Одна фигня только вылазит с деревом и итогами по узлам - это то что в подвале дерева итоги начинают затраиваться! Иначе бы их можно было и запросом заполнять. Но если стоит использовать итоги в подвале то начинается полная фигня.
18. vladal (Vladal) 26.12.11 10:48
(17) Eugeneer, не ставь. Используй итоги из запроса.
19. Eugeneer (Eugeneer) 26.12.11 10:59
(18) тоже как вариант. но если в дереве будут вводить свои данные то много кода все равно. Жалко что 1С с итогами по деревьям не учла итоги срок, а не узлов.
20. vladal (Vladal) 19.04.12 19:02
(19) Eugeneer, это можно самому сделать. При обходе смотреть - группировка это или строка.
21. Роман (Raminus) 02.10.12 10:11
Оформлено хорошо, ознакомился, спасибо :)
22. vladal (Vladal) 06.10.12 19:51
23. Галина Ивлева (galinka1c8) 12.02.13 23:53
Спасибо большое, статья очень понравилась. Все четко и просто изложено. До этого в деревьях вообще плавала, поиск по инету давал только отдельные не связанные обрывки информации. А тут все и сразу. Еще понравилось дополнение по двухуровневое дерево. Пригодилось.
24. vladal (Vladal) 15.02.13 14:11
(23) galinka1c8, спасибо за отзыв. Я тоже как столкнулся, только обрывками всё видел. Потом разобрался и поделился.
25. Юрий П (nano1c) 11.04.13 12:58
статья хорошая, не слушайте крутых шпицалистов - такие статьи нужны ибо бывает что и сами эти шпицалисты тупят и нужно что-то такое почитать. однако вот парадокс: у меня никак не хочет дерево прорисовывать веточки - только кружки с плюсиками. проблема описана много где но ничего не помогает. скачал ваш пример засунул туда свой запрос и все равно дерево не прорисовывается - вот если бы еще эту проблему разжевали!
26. vladal (Vladal) 11.04.13 16:30
(25) nano1c, "кружочки с плюсиками" - 8.2? )))
Т.е. само дерево рисуется с группировками, но справа строковое значение группировки не показано?
Не УФ случайно?
27. Юрий П (nano1c) 11.04.13 16:54
как на рисунке. это просто какаято жесть - я раньше отчеты сам рисовал в виде деревьев (даже не подозревал что они могут от отрисовываться), а теперь в конфе первобит-финанс хрень какаято - ну нивкакую деревья не рисуются. что только не перепробовал - только кружочки! но я правда сам не рисую рекурсией - рисую вашим методом но нифига не получается. хотя претензий к самим данным дерева нет - все там правильно. а на форме не дерево а хрень с кружками...
платформа последняя 8.2
Прикрепленные файлы:
28. Cemen82 17.04.13 07:18
Сори, очень нужны WM
Хочу поделиться своим опытом работы с деревом значений. Оно во многом напоминает таблицу значений, но в отличие от неё кроме строк имеет группировки, которые можно обходить иерархически на манер дерева справочника. Но что больше всего мне понравилось, так это возможность вычислять итоги группировок с учетом иерархии на манер группировок запроса.
29. vladal (Vladal) 17.04.13 14:15
(28) Если нужны стартмани - попроси, но не спамь.
Тебе нужна эта обработка? Попроси - я вышлю. Как она создаётся расписано пошагово.
30. vladal (Vladal) 17.04.13 14:17
(27) nano1c, я попробовал в 8.2 - рисует. А в обычной консоли запросов этот запрос в дерево выгружается? Если выгружается, то в каком виде?
________________________

Проблема в задваивании строк, как на картинке?
31. Юрий П (nano1c) 18.04.13 09:40
дело конечно же не в задвоении. в консолиЗапросов дерево имеет точно такой же вид "кружочки". см. рисунок
Прикрепленные файлы:
32. vladal (Vladal) 19.04.13 13:41
(31) nano1c, ну с версии 8.2 кружочки и рисуются. А в 8.0 и и 8.1 - квадратики. Что за безобразие!
33. Юрий П (nano1c) 19.04.13 14:43
кружочки, ладно еще. но вот что веточки не рисует - это плохо. не знал что в 8.2 их впринципе нельзя нарисовать..
34. link li (link_l) 04.03.14 10:41
Очень помогло, спасибо! =)))
35. V. L. (Vladal) 04.03.14 15:21
36. Юрий Еремин (potyomkin) 22.05.15 11:33
Скажите, есть тоже самое для управляемых форм?
37. Dexter Morgan (DexterMorgan777) 22.05.15 11:48
38. V. L. (Vladal) 26.05.15 14:13
(36) potyomkin, пока нет. Но недавно мне попадалась тема про дерево на УФ на этом форуме или другом.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа