При разработке информационных систем на платформе 1С:Предприятие 8.3 часто возникает задача по отображению иерархических данных на управляемых формах. Одним из мощных инструментов для этого является ДеревоЗначений. В этой статье мы подробно рассмотрим, как правильно заполнять ДеревоЗначений из результата запроса, чтобы избежать повторений и корректно построить иерархию, а также обсудим нюансы работы с ним на управляемых формах.
Наш читатель столкнулся с задачей: необходимо вывести на форму список наименований мебели и её составных частей. При этом мебель берется из справочника, а составные части — из регистра сведений. После выполнения запроса и получения выборки, задача заключается в корректном заполнении ДереваЗначений, чтобы данные отображались иерархически и без повторяющихся элементов.
Давайте разберем эту ситуацию по шагам, предложив наиболее эффективные и корректные подходы.
В платформе 1С:Предприятие 8.3 существует несколько способов заполнения ДереваЗначений, особенно если речь идет об управляемых формах и данных из запроса. Мы рассмотрим два основных:
Прежде чем перейти к деталям, напомним, что для работы с ДеревомЗначений на управляемых формах необходимо использовать специальные функции для передачи данных между клиентом и сервером: РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы().
Прежде чем приступать к заполнению, нам нужно подготовить реквизит формы и запрос, который будет извлекать необходимые данные.
На управляемой форме необходимо создать реквизит с типом "ДеревоЗначений". Например, назовем его ДеревоМебели. Затем в свойствах этого реквизита добавьте колонки, которые будут отображать данные. В нашем случае это могут быть колонки ПредметМебели (тип "СправочникСсылка.Мебель" или "Строка", если это наименование) и СоставныеЧасти (тип "Строка"). Колонки должны быть созданы для реквизита формы, а не для самого элемента формы ДеревоЗначений.
Например, в модуле формы, в обработчике ПриСозданииНаСервере или отдельной серверной процедуре, мы можем определить структуру дерева:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Инициализация колонок ДереваЗначений
Если ДеревоМебели.Колонки.Найти("ПредметМебели") = Неопределено Тогда
ДеревоМебели.Колонки.Добавить("ПредметМебели", Новый ОписаниеТипов("Строка"));
КонецЕсли;
Если ДеревоМебели.Колонки.Найти("СоставныеЧасти") = Неопределено Тогда
ДеревоМебели.Колонки.Добавить("СоставныеЧасти", Новый ОписаниеТипов("Строка"));
КонецЕсли;
// Вызов процедуры для заполнения дерева
ЗаполнитьДеревоМебелиНаСервере();
КонецПроцедуры
Запрос должен извлекать данные о мебели и её составных частях. Важно, чтобы запрос был структурирован таким образом, чтобы его результат можно было легко преобразовать в иерархию. Для этого часто используются левые соединения (ЛЕВОЕ СОЕДИНЕНИЕ) и группировки.
Предположим, у нас есть справочник "Мебель" с наименованием и регистр сведений "СоставМебели" с измерениями "Мебель" (ссылка на справочник "Мебель") и "НаименованиеЧасти" (строка).
&НаСервере
Функция ПолучитьДанныеДляДерева()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Мебель.Наименование КАК ПредметМебели,
| СоставМебели.НаименованиеЧасти КАК СоставныеЧасти,
| Мебель.Ссылка КАК СсылкаМебели
|ИЗ
| Справочник.Мебель КАК Мебель
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СоставМебели КАК СоставМебели
| ПО Мебель.Ссылка = СоставМебели.Мебель
|УПОРЯДОЧИТЬ ПО
| ПредметМебели,
| СоставныеЧасти"; // Важно для правильной группировки
РезультатЗапроса = Запрос.Выполнить();
Возврат РезультатЗапроса;
КонецФункции
В этом запросе мы соединяем справочник "Мебель" с регистром сведений. Обратите внимание на УПОРЯДОЧИТЬ ПО – это критически важно для автоматического формирования иерархии методом Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) и для корректного программного обхода.
Это самый эффективный и рекомендуемый способ. Платформа 1С позволяет автоматически построить иерархию ДереваЗначений на основе результата запроса, если в запросе используются группировки или данные упорядочены по иерархическому признаку.
ДеревоЗначений, например, ДанныеДереваМебели.ПредметМебели (Строка), СоставныеЧасти (Строка).
&НаСервере
Процедура ЗаполнитьДеревоМебелиНаСервере()
// 1. Получаем результат запроса
РезультатЗапроса = ПолучитьДанныеДляДерева();
// 2. Создаем новое ДеревоЗначений для передачи на форму
НовоеДерево = Новый ДеревоЗначений;
НовоеДерево.Колонки.Добавить("ПредметМебели", Новый ОписаниеТипов("Строка"));
НовоеДерево.Колонки.Добавить("СоставныеЧасти", Новый ОписаниеТипов("Строка"));
// 3. Выгружаем результат запроса в ДеревоЗначений с иерархией
// Используем обход по группировкам, указывая поля для группировки
// В нашем случае, 'ПредметМебели' будет верхним уровнем, 'СоставныеЧасти' - подчиненным
// Для этого в запросе нужно обеспечить сортировку, как было показано ранее.
// Если нам нужна жесткая группировка, то запрос должен быть таким:
/*
Запрос.Текст =
"ВЫБРАТЬ
| Мебель.Наименование КАК ПредметМебели,
| СоставМебели.НаименованиеЧасти КАК СоставныеЧасти
|ИЗ
| Справочник.Мебель КАК Мебель
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СоставМебели КАК СоставМебели
| ПО Мебель.Ссылка = СоставМебели.Мебель
|
|УПОРЯДОЧИТЬ ПО
| ПредметМебели,
| СоставныеЧасти
|
|ИТОГИ ПО
| ПредметМебели"; // Ключевое отличие для автоматической иерархии
*/
// Если запрос содержит ИТОГИ ПО, то можем выгрузить так:
ВыборкаПоГруппировкам = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
// В данном сценарии, с учетом нашего запроса БЕЗ ИТОГОВ, но с УПОРЯДОЧИТЬ ПО,
// мы можем использовать другой подход для автоматической иерархии,
// либо сделать ручное заполнение.
// Если мы хотим автоматическую иерархию, запрос должен быть с ИТОГАМИ ПО.
// Пример выгрузки с ИТОГАМИ ПО:
// НовоеДерево.Загрузить(ВыборкаПоГруппировкам);
// Однако, текущий запрос без ИТОГОВ ПО.
// Поэтому рассмотрим ручной обход, как наиболее приближенный к попыткам автора.
// Но для начала - идеальный случай с автоматической загрузкой (если запрос с ИТОГАМИ).
// Если бы запрос был с ИТОГАМИ ПО:
// Например, запрос с ИТОГАМИ ПО ПредметМебели и СоставныеЧасти
// НовоеДерево.Загрузить(РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией));
// Это автоматически сформировало бы дерево.
// В нашем случае, поскольку запрос простой, без ИТОГОВ, нам придется делать обход.
// Но сначала давайте представим, как бы выглядел запрос с ИТОГАМИ:
ЗапросДляАвтоматическойИерархии = Новый Запрос;
ЗапросДляАвтоматическойИерархии.Текст =
"ВЫБРАТЬ
| Мебель.Наименование КАК ПредметМебели,
| СоставМебели.НаименованиеЧасти КАК СоставныеЧасти
|ИЗ
| Справочник.Мебель КАК Мебель
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СоставМебели КАК СоставМебели
| ПО Мебель.Ссылка = СоставМебели.Мебель
|
|ИТОГИ ПО
| ПредметМебели ИЕРАРХИЯ,
| СоставныеЧасти"; // Ключевое слово ИЕРАРХИЯ для ДереваЗначений
РезультатЗапросаИерархия = ЗапросДляАвтоматическойИерархии.Выполнить();
// Теперь, с таким запросом, мы можем выгрузить результат в ДеревоЗначений очень просто:
НовоеДерево = РезультатЗапросаИерархия.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
// Здесь платформа сама создаст колонки, если их нет, но лучше определить их заранее.
// Мы уже определили их, так что платформа будет использовать существующие.
// Важно: имена колонок в ДеревеЗначений должны совпадать с именами полей запроса.
// После заполнения ДереваЗначений на сервере, передаем его на форму
ЗначениеВРеквизитФормы(НовоеДерево, "ДеревоМебели");
КонецПроцедуры
Важные моменты для автоматической иерархии:
ИТОГИ ПО с ключевым словом ИЕРАРХИЯ для полей, по которым должна строиться иерархия.ИТОГИ ПО определяет уровни иерархии.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) автоматически создает строки и подчиненные строки в ДеревеЗначений.Читатель изначально пытался реализовать именно этот подход, используя рекурсивную процедуру. Давайте проанализируем его код и предложим корректную реализацию. Основная проблема в коде автора из сообщения 2 заключается в некорректном использовании Пока Выборка.Следующий внутри цикла для добавления составных частей, что приводит к пропуску данных или неверной логике обхода.
Для программного заполнения мы будем использовать результат запроса, который мы уже написали (без ИТОГОВ ПО, но с УПОРЯДОЧИТЬ ПО ПредметМебели, СоставныеЧасти).
Пример корректного программного заполнения:
&НаСервере
Процедура ЗаполнитьДеревоМебелиРекурсивноНаСервере()
// 1. Получаем результат запроса
РезультатЗапроса = ПолучитьДанныеДляДерева(); // Используем запрос без ИТОГОВ ПО, но с УПОРЯДОЧИТЬ ПО
Выборка = РезультатЗапроса.Выбрать();
// 2. Создаем новое ДеревоЗначений
НовоеДерево = Новый ДеревоЗначений;
НовоеДерево.Колонки.Добавить("ПредметМебели", Новый ОписаниеТипов("Строка"));
НовоеДерево.Колонки.Добавить("СоставныеЧасти", Новый ОписаниеТипов("Строка"));
// Передаем выборку и текущий уровень дерева в рекурсивную функцию
// Создадим вспомогательную функцию, чтобы не передавать НовоеДерево напрямую в рекурсию,
// а работать со строками текущего уровня
ЗаполнитьУровеньДерева(НовоеДерево.Строки, Выборка);
// Передаем заполненное дерево на форму
ЗначениеВРеквизитФормы(НовоеДерево, "ДеревоМебели");
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьУровеньДерева(СтрокиДерева, Выборка)
ТекущийПредметМебели = "";
НовСтрокаМебель = Неопределено;
Пока Выборка.Следующий() Цикл
Если Выборка.ПредметМебели <> ТекущийПредметМебели Тогда
// Обнаружен новый предмет мебели, создаем новую родительскую строку
НовСтрокаМебель = СтрокиДерева.Добавить();
НовСтрокаМебель.ПредметМебели = Выборка.ПредметМебели;
НовСтрокаМебель.СоставныеЧасти = ""; // Для родительского элемента эта колонка пуста
ТекущийПредметМебели = Выборка.ПредметМебели;
КонецЕсли;
// Добавляем составную часть к текущему предмету мебели, если она есть
Если ЗначениеЗаполнено(Выборка.СоставныеЧасти) Тогда
Если НовСтрокаМебель <> Неопределено Тогда
// Добавляем подчиненную строку для составной части
НовСтрокаСоставнаяЧасть = НовСтрокаМебель.Строки.Добавить();
НовСтрокаСоставнаяЧасть.ПредметМебели = ""; // Для подчиненной строки эта колонка пуста
НовСтрокаСоставнаяЧасть.СоставныеЧасти = Выборка.СоставныеЧасти;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Проанализируем изменения и почему это работает:
Пока Выборка.Следующий() Цикл.ТекущийПредметМебели используется для отслеживания того, когда мы переходим от одного предмета мебели к другому. Это позволяет нам создавать новую родительскую строку только тогда, когда встречается новый предмет мебели, тем самым избегая повторений.Выборка.ПредметМебели <> ТекущийПредметМебели, это означает, что мы начали обрабатывать новый предмет мебели, и для него создается новая родительская строка в СтрокиДерева.СоставныеЧасти для текущего предмета мебели, они добавляются как подчиненные строки к только что созданной или уже существующей родительской строке НовСтрокаМебель.ПредметМебели пустым, чтобы не было дублирования информации. Аналогично, для родительской строки ПредметМебели, поле СоставныеЧасти может быть пустым.Такой подход обеспечивает корректную иерархию и отсутствие повторений, поскольку родительский элемент создается только один раз для каждого уникального предмета мебели.
Как уже упоминалось, ДеревоЗначений (объект Новый ДеревоЗначений) существует на сервере. Для его отображения на управляемой форме, оно должно быть "преобразовано" в реквизит формы типа ДанныеФормыДерево. Для этого используются функции:
ЗначениеВРеквизитФормы(СерверноеЗначение, "ИмяРеквизитаФормы"): Используется на сервере для записи данных из серверного объекта (например, НовоеДерево = Новый ДеревоЗначений) в реквизит формы.РеквизитФормыВЗначение("ИмяРеквизитаФормы", ТипСерверногоЗначения): Используется на сервере для чтения данных из реквизита формы обратно в серверный объект.Мы уже использовали ЗначениеВРеквизитФормы(НовоеДерево, "ДеревоМебели"); в примерах выше. Этот шаг является обязательным для того, чтобы заполненное дерево отобразилось пользователю.
После того как ДеревоЗначений заполнено, важно правильно настроить его отображение на форме:
ДеревоМебели на форму. Платформа автоматически создаст элемент формы с тем же именем.ПредметМебели, СоставныеЧасти). Вы можете настроить их ширину, заголовок, порядок.
// Пример условного оформления (настраивается в конфигураторе)
// Условие:
// - Для строки дерева, если ПредметМебели пустой
// Оформление:
// - Видимость колонки "ПредметМебели" = Ложь
// - Шрифт = Полужирный (для СоставныеЧасти)
Это может помочь сделать иерархию более читаемой, если вы не хотите, чтобы в колонке "ПредметМебели" были пустые значения напротив составных частей.
В ходе обсуждения был задан вопрос: "А точно уверены что нужно дерево на форме, а не динамический список?". Это очень важный вопрос, который стоит рассмотреть:
В вашем случае, поскольку условие задачи явно указывает на "построить дерево руками", мы сосредоточились на ДеревеЗначений. Но всегда держите в уме ДинамическийСписок как мощную альтернативу.
Мы подробно рассмотрели два основных подхода к заполнению ДереваЗначений на управляемых формах 1С:Предприятия 8.3 для отображения иерархических данных без повторений. Автоматический метод с использованием ИТОГИ ПО ИЕРАРХИЯ в запросе и Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) является наиболее эффективным. Если же требуется полный контроль или специфическая логика, программное рекурсивное заполнение, как мы показали, требует аккуратности в обходе выборки для избежания повторений и корректного построения иерархии. Не забывайте о функциях ЗначениеВРеквизитФормы() и РеквизитФормыВЗначение() для корректной работы с реквизитами форм.
Применяя эти знания, вы сможете успешно решать задачи по отображению сложных иерархических структур данных в ваших 1С-решениях.