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