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