Работа с иерархическими структурами данных, такими как ДеревоЗначений, в управляемых формах 1С имеет свои особенности. В отличие от обычной таблицы, дерево требует особого подхода к поиску, удалению и отображению строк, особенно когда нужно отфильтровать данные по результатам запроса. Рассмотрим подробнее, как правильно реализовать отбор, сохранив при этом целостность структуры и производительность системы.
Часто начинающие разработчики сталкиваются с ошибкой "Поле объекта не обнаружено (Строки)" или отсутствием метода НайтиСтроки(). Это происходит потому, что реквизит формы имеет тип ДанныеФормыДерево, который является лишь "отображением" данных и не обладает всеми методами объектного типа ДеревоЗначений. Чтобы полноценно работать с коллекцией (искать, фильтровать, удалять), нам необходимо преобразовать реквизит формы в объектную сущность, выполнить операции и вернуть результат обратно.
Разберем по шагам классический способ фильтрации дерева через создание промежуточного объекта. Проанализируем ситуацию: у нас есть дерево на форме с номенклатурой и характеристиками, и нам нужно оставить только те строки, которые вернул запрос.
РеквизитФормыВЗначение, чтобы получить полноценное ДеревоЗначений.Соответствие) с теми данными, которые должны остаться.ЗначениеВРеквизитФормы для обновления интерфейса.Этот метод удобен, когда нужно быстро найти строки по точному совпадению значений в колонках. Рассмотрим пример кода, который демонстрирует этот подход:
// Переводим данные формы в объектное дерево
ДеревоОбъект = РеквизитФормыВЗначение("Товары");
// Допустим, у нас есть ТЗРезультатЗапроса с нужной номенклатурой
ПараметрыОтбора = Новый Структура;
Для Каждого СтрокаТЗ Из ТЗРезультатЗапроса Цикл
ПараметрыОтбора.Вставить("Номенклатура", СтрокаТЗ.Номенклатура);
ПараметрыОтбора.Вставить("Характеристика", СтрокаТЗ.Характеристика);
// Ищем рекурсивно по всему дереву
НайденныеСтроки = ДеревоОбъект.Строки.НайтиСтроки(ПараметрыОтбора, Истина);
// Дальнейшая логика по копированию найденных строк в новое дерево
КонецЦикла;
// Возвращаем результат на форму
ЗначениеВРеквизитФормы(ДеревоОбъект, "Товары");
Важный нюанс: в методе НайтиСтроки(ПараметрыОтбора, ИскатьРекурсивно) второй параметр Истина позволяет искать узлы на всех уровнях вложенности. Если его не указать, поиск ограничится только верхним уровнем.
Если задача стоит именно в удалении лишних данных из существующего дерева, проанализируем важную особенность: удалять строки нужно "снизу вверх". Если вы удалите родительский узел раньше дочернего, вы потеряете доступ к проверке вложенных элементов. Рассмотрим правильную рекурсивную функцию:
Процедура ФильтроватьВетки(КоллекцияСтрок, ТЗЭталон)
Индекс = КоллекцияСтрок.Количество() - 1;
Пока Индекс >= 0 Цикл
ТекущаяСтрока = КоллекцияСтрок[Индекс];
// Сначала идем вглубь (рекурсия)
ФильтроватьВетки(ТекущаяСтрока.Строки, ТЗЭталон);
// Проверяем, соответствует ли строка условию
ПараметрыПоиска = Новый Структура("Номенклатура, Характеристика", ТекущаяСтрока.Номенклатура, ТекущаяСтрока.Характеристика);
СтрокаНайдена = (ТЗЭталон.НайтиСтроки(ПараметрыПоиска).Количество() > 0);
// Условие удаления: строки нет в эталоне И у нее нет подходящих подчиненных строк
Если Не СтрокаНайдена И ТекущаяСтрока.Строки.Количество() = 0 Тогда
КоллекцияСтрок.Удалить(ТекущаяСтрока);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
КонецПроцедуры
В данном примере мы используем цикл Пока с уменьшением индекса, что позволяет безопасно удалять элементы коллекции, не вызывая смещения, которое могло бы привести к пропуску строк.
Когда в дереве тысячи строк, поиск методом НайтиСтроки или поиск по ТаблицеЗначений внутри цикла может существенно замедлить работу. Проанализируем ситуацию: каждый поиск — это перебор коллекции. Чтобы ускорить процесс, выясним причину медленной работы и применим Соответствие.
Перед началом обхода дерева выгрузим ключевые поля в Соответствие, где ключом будет уникальная комбинация номенклатуры и характеристики. Поиск в Соответствии работает практически мгновенно.
// Подготовка индекса для быстрого поиска
КлючиДоступности = Новый Соответствие;
Для Каждого Стр Из ТЗРезультатЗапроса Цикл
Ключ = Строка(Стр.Номенклатура.УникальныйИдентификатор()) + "_" + Строка(Стр.Характеристика.УникальныйИдентификатор());
КлючиДоступности.Вставить(Ключ, Истина);
КонецЦикла;
// Теперь при обходе дерева проверка будет мгновенной
КлючСтроки = Строка(ТекущаяСтрока.Номенклатура.УникальныйИдентификатор()) + "_" + Строка(ТекущаяСтрока.Характеристика.УникальныйИдентификатор());
Если КлючиДоступности[КлючСтроки] = Неопределено Тогда
// Строка не прошла фильтр
КонецЕсли;
Одной из главных проблем при использовании ЗначениеВРеквизитФормы является то, что 1С полностью перерисовывает элемент управления на форме. В результате все узлы, которые пользователь развернул вручную, "схлопываются". Чтобы этого избежать, выполним следующие шаги:
ДанныеФормыДерево.ПолучитьЭлементы()).Развернуть() для тех узлов, чьи идентификаторы (или ключи данных) сохранились в массиве.Важный момент: идентификаторы строк после преобразования РеквизитФормыВЗначение могут измениться. Поэтому лучше сохранять в массив не идентификаторы, а уникальные ключи данных (например, ссылки на номенклатуру).
Рассмотрим альтернативный метод. Если удаление строк не является обязательным требованием, и нам нужно просто скрыть лишнее от глаз пользователя, можно использовать Условное оформление управляемой формы.
Для этого добавим в дерево служебную колонку Видимость (тип Булево). Пройдем циклом по дереву и установим Ложь для тех строк, которые не прошли фильтр. В настройках условного оформления формы укажем правило: если Видимость = Ложь, то установить свойство Видимость (или Высота = 1, если полное скрытие работает некорректно в вашей версии платформы) для всей строки.
Этот способ работает быстрее всего, так как не требует сложных манипуляций с объектной моделью и не сбрасывает состояние развернутых веток дерева.
Выбор метода зависит от объема данных и требований к интерфейсу:
РеквизитФормыВЗначение и НайтиСтроки.Соответствие для индексации.