Как реализовать форму выбора с иерархическим отбором, отметками элементов и сохранением выбора при повторном открытии?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В разработке на платформе 1С:Предприятие 8.3 часто возникают задачи по созданию нестандартных форм выбора. Стандартный динамический список отлично подходит для простых фильтров, но когда требуется реализовать сложную иерархическую структуру с отбором, который должен скрывать целые ветки справочника, и при этом позволять пользователю отмечать элементы флажками — стандартных механизмов становится недостаточно. В этой статье мы разберем, как построить эффективную форму выбора на основе Дерева значений, реализовать виртуальные колонки для пометок и обеспечить сохранение состояния выбора при повторном вызове формы.

Почему Динамический список не всегда подходит?

Прежде чем перейти к решению, проанализируем ситуацию. При использовании стандартного ДинамическогоСписка в иерархическом режиме мы часто сталкиваемся с тем, что если родительская группа не соответствует условию отбора, то и вложенные в нее элементы могут не отображаться (или наоборот, отображаются пустые группы). Кроме того, динамический список не позволяет "на лету" добавить колонку с типом Булево, значения в которой не привязаны к данным базы данных, а существуют только в рамках текущего сеанса выбора.

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

Шаг 1: Подготовка данных и построение дерева на сервере

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

Для начала опишем процедуру заполнения дерева значений. Важно понимать, что мы работаем в управляемом приложении, поэтому основные манипуляции с объектом ДеревоЗначений будут происходить на сервере.


&НаСервере
Процедура ЗаполнитьДеревоНаСервере(ВыбранныйПрайс)
    
    // Очищаем текущее дерево в реквизите формы
    ТоварыДеревоОбъект = РеквизитФормыВЗначение("ТоварыДерево");
    ТоварыДеревоОбъект.Строки.Очистить();
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Номенклатура.Ссылка КАК Ссылка,
        |   Номенклатура.Код КАК Код,
        |   Номенклатура.Наименование КАК Наименование,
        |   Номенклатура.Родитель КАК Родитель,
        |   ЛОЖЬ КАК Отм,
        |   0 КАК КолВо
        |ИЗ
        |   Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |   Номенклатура.Прайс = &Прайс
        |ИТОГИ ПО
        |   Родитель ИЕРАРХИЯ";
    
    Запрос.УстановитьПараметр("Прайс", ВыбранныйПрайс);
    
    РезультатЗапроса = Запрос.Выполнить();
    
    // Выгружаем результат в дерево значений
    ТоварыДеревоОбъект = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
    
    // Возвращаем данные в реквизит формы
    ЗначениеВРеквизитФормы(ТоварыДеревоОбъект, "ТоварыДерево");
    
КонецПроцедуры

Шаг 2: Реализация выбора с помощью флажков

Чтобы пользователь мог отмечать элементы, мы добавили в запрос виртуальную колонку Отм (тип Булево). В интерфейсе формы для таблицы, связанной с деревом значений, необходимо установить режим отображения этой колонки в виде флажка. Разберем, как сделать работу с этими отметками удобной.

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


&НаКлиенте
Процедура ТоварыДеревоОтмПриИзменении(Элемент)
    ТекущаяСтрока = Элементы.ТоварыДерево.ТекущаяДанные;
    Если ТекущаяСтрока <> Неопределено Тогда
        // Рекурсивно помечаем всех потомков
        УстановитьОтметкуПотомкам(ТекущаяСтрока, ТекущаяСтрока.Отм);
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура УстановитьОтметкуПотомкам(СтрокаДерева, ЗначениеОтметки)
    Для Каждого ДочерняяСтрока Из СтрокаДерева.ПолучитьЭлементы() Цикл
        ДочерняяСтрока.Отм = ЗначениеОтметки;
        УстановитьОтметкуПотомкам(ДочерняяСтрока, ЗначениеОтметки);
    КонецЦикла;
КонецПроцедуры

Шаг 3: Передача данных и повторное открытие формы

Одной из самых сложных задач является сохранение состояния выбора. Рассмотрим ситуацию: пользователь открыл форму, выбрал 5 товаров, закрыл ее, а потом решил добавить еще 2. Нам нужно, чтобы при втором открытии те 5 товаров уже были отмечены галочками.

Для этого выясним, как правильно передавать информацию между формой документа и формой выбора. Лучше всего использовать коллекцию Соответствие или Массив, где ключом будет ссылка на товар (или уникальный код), а значением — количество или просто признак выбора.

Проанализируем код процедуры, которая расставляет отметки при создании формы на сервере, основываясь на переданных параметрах:


&НаСервере
Процедура ОтметитьРанееВыбранныеТовары(ВыбрТовары)
    // Преобразуем реквизит формы (дерево) обратно в объект для манипуляций
    ДеревоОбъект = РеквизитФормыВЗначение("ТоварыДерево");
    
    Для Каждого ЭлементКоллекции Из ВыбрТовары Цикл
        // ЭлементКоллекции.Ключ содержит Код или Ссылку на товар
        // Вызываем рекурсивную функцию поиска и установки значений
        УстановитьЗначениеВДереве(ДеревоОбъект, ЭлементКоллекции.Ключ, ЭлементКоллекции.Значение);
    КонецЦикла;
    
    // Возвращаем измененное дерево в форму
    ЗначениеВРеквизитФормы(ДеревоОбъект, "ТоварыДерево");
КонецПроцедуры

&НаСервере
Процедура УстановитьЗначениеВДереве(УзелДерева, ИскомыйКод, КолВо)
    Для Каждого СтрокаДерева Из УзелДерева.Строки Цикл
        Если СтрокаДерева.Код = ИскомыйКод Тогда
            СтрокаДерева.Отм = Истина;
            СтрокаДерева.КолВо = КолВо;
        КонецЕсли;
        // Рекурсивный вызов для обхода всех уровней иерархии
        УстановитьЗначениеВДереве(СтрокаДерева, ИскомыйКод, КолВо);
    КонецЦикла;
КонецПроцедуры

Шаг 4: Возврат результата в документ

Когда выбор завершен, нам нужно собрать все помеченные строки и передать их обратно. Рассмотрим метод, который обходит дерево и формирует список выбранных позиций перед закрытием формы.


&НаКлиенте
Процедура Выбрать(Команда)
    РезультатВыбора = Новый Соответствие;
    СобратьВыбранныеТовары(ТоварыДерево.ПолучитьЭлементы(), РезультатВыбора);
    
    // Оповещаем владельца формы о выборе
    ОповеститьОВыборе(РезультатВыбора);
КонецПроцедуры

&НаКлиенте
Процедура СобратьВыбранныеТовары(СтрокиДерева, РезультатВыбора)
    Для Каждого Строка Из СтрокиДерева Цикл
        Если Строка.Отм Тогда
            // Если это элемент (а не группа), добавляем в результат
            РезультатВыбора.Вставить(Строка.Ссылка, Строка.КолВо);
        КонецЕсли;
        // Продолжаем поиск в глубину
        СобратьВыбранныеТовары(Строка.ПолучитьЭлементы(), РезультатВыбора);
    КонецЦикла;
КонецПроцедуры

Рекомендации по оптимизации

Разберем несколько важных моментов, которые помогут сделать решение более профессиональным:

  1. Индексация поиска: Если справочник содержит тысячи позиций, рекурсивный поиск в дереве значений может работать медленно. В этом случае рекомендуется выгрузить дерево в плоскую ТаблицуЗначений, найти нужные строки методом НайтиСтроки(), получить их идентификаторы и обновить дерево точечно.
  2. Визуальное оформление: Используйте Условное оформление формы, чтобы выделять жирным шрифтом или цветом те строки, которые уже были выбраны. Это повышает эргономику интерфейса.
  3. Использование ссылок: Старайтесь всегда использовать Ссылка (GUID) в качестве ключа поиска вместо Код, так как коды могут дублироваться или меняться, а ссылки уникальны и обеспечивают более быстрый поиск в базе данных.
  4. Паттерн "Подбор": Если предполагается, что пользователь будет очень часто открывать и закрывать форму, рассмотрите возможность реализации формы "Подбора". В ней можно сделать две таблицы: слева — справочник, справа — "Корзина" с уже выбранными товарами. Это избавит от необходимости передавать сложные структуры данных при каждом открытии.

Таким образом, мы рассмотрели полный цикл реализации сложной формы выбора. Использование ДереваЗначений дает разработчику гибкость, недоступную при работе с обычными динамическими списками, позволяя создавать по-настоящему удобные и функциональные интерфейсы для пользователей 1С.

← На главную