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