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