Как сохранить позицию текущей строки в дереве значений после обновления данных?

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

При работе с управляемыми формами в 1С часто возникает ситуация, когда дерево значений, отображаемое на форме, необходимо обновить. Традиционный подход через серверную процедуру и метод ЗначениеВРеквизитФормы приводит к неприятному побочному эффекту: дерево полностью перестраивается, все ветки сворачиваются, а фокус (текущая строка) сбрасывается на начало. Это крайне неудобно для пользователя, особенно если он работал с глубоко вложенными данными.

В данной статье мы подробно разберем, как программно запомнить текущую строку, выполнить обновление и вернуть курсор именно туда, где он находился. Мы проанализируем несколько подходов: от ручной рекурсии до использования стандартных функций БСП.

Почему происходит сброс текущей строки?

Разберем причину проблемы. Когда мы вызываем ЗначениеВРеквизитФормы(Дерево, "ДеревоДанных"), платформа фактически уничтожает старую коллекцию элементов на форме и создает новую. Даже если данные внутри дерева остались прежними, внутренние идентификаторы строк (которые использует платформа для позиционирования) меняются. Объект ТекущаяСтрока в элементе формы типа «Таблица» хранит именно числовой идентификатор, который после обновления становится недействительным.

Этап 1: Сохранение идентификационных данных

Прежде чем отправлять запрос на сервер для обновления дерева, нам нужно «запомнить», какая строка была активна. Просто сохранить идентификатор бесполезно — он изменится. Нам нужно сохранить уникальный набор данных, по которому мы сможем найти эту же строку в новом дереве.

Рассмотрим пример, где строкой является документ. В этом случае уникальным ключом будет ссылка на документ. Если же дерево строится по комбинации полей, нам потребуется структура.


&НаКлиенте
Процедура КомандаОбновитьДерево(Команда)
    // 1. Проверяем, есть ли выделенная строка
    ТекущиеДанные = Элементы.ДеревоДанных.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        _СформироватьДеревоНаСервере();
        Возврат;
    КонецЕсли;

    // 2. Запоминаем уникальный ключ (например, Ссылку или структуру полей)
    КлючСтроки = ТекущиеДанные.Ссылка; 
    
    // 3. Обновляем данные на сервере
    _СформироватьДеревоНаСервере();
    
    // 4. Переходим к поиску и позиционированию
    ВосстановитьПозициюДерева(КлючСтроки);
КонецПроцедуры

Этап 2: Рекурсивный поиск строки на клиенте

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

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


&НаКлиенте
Процедура НайтиСтрокуДерева(КоллекцияЭлементов, ЗначениеПоиска, ИдентификаторРезультат, ПрекратитьПоиск)
    Для Каждого СтрокаДерева Из КоллекцияЭлементов Цикл
        Если ПрекратитьПоиск Тогда 
            Возврат; 
        КонецЕсли;

        // Сравниваем по ключевому полю
        Если СтрокаДерева.Ссылка = ЗначениеПоиска Тогда
            ИдентификаторРезультат = СтрокаДерева.ПолучитьИдентификатор();
            ПрекратитьПоиск = Истина;
            Возврат;
        КонецЕсли;

        // Рекурсивный вызов для подчиненных элементов
        Подчиненные = СтрокаДерева.ПолучитьЭлементы();
        Если Подчиненные.Количество() > 0 Тогда
            НайтиСтрокуДерева(Подчиненные, ЗначениеПоиска, ИдентификаторРезультат, ПрекратитьПоиск);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Этап 3: Установка фокуса и развертывание веток

Мало просто найти идентификатор строки. Если дерево свернулось, пользователь не увидит выделенную строку, пока не развернет родителей. Рассмотрим, как правильно завершить процесс позиционирования.


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

Использование стандартных функций БСП

Если ваша конфигурация построена на базе БСП (Библиотека стандартных подсистем), вам не обязательно писать свою рекурсию. В общем модуле ОбщегоНазначенияКлиентСервер существует готовая процедура ПолучитьИдентификаторСтрокиДереваПоЗначениюПоля. Она выполняет тот же поиск, что мы описали выше, но более универсально. Для быстрого поиска готовых процедур библиотеки и их параметров подойдёт справочник по методам БСП с примерами использования.

Рассмотрим пример её вызова:


ОбщегоНазначенияКлиентСервер.ПолучитьИдентификаторСтрокиДереваПоЗначениюПоля(
    "Ссылка",                           // Имя поля для поиска
    Элементы.ДеревоДанных.ТекущаяСтрока, // Куда записать результат
    ДеревоДанных.ПолучитьЭлементы(),     // Коллекция для поиска
    КлючСтроки,                         // Что искать
    Ложь                                // Прекратить поиск (флаг)
);

Тонкости работы в предопределенных событиях

Выясним причину, по которой установка ТекущаяСтрока иногда не работает (например, в событии ПриОкончанииРедактирования). Платформа 1С выполняет собственные алгоритмы обновления интерфейса сразу после завершения стандартных процедур. В итоге ваше программное изменение фокуса «перетирается» стандартным поведением платформы.

Решение: Использование обработчика ожидания. Это позволяет выполнить код позиционирования через доли секунды после того, как все системные процессы обновления формы завершатся.

  1. Создадим реквизит формы ИдентификаторДляВосстановления (тип Число).
  2. В процедуре обновления запишем туда найденный ID и подключим обработчик.

&НаКлиенте
Процедура ПослеОбновленияДанных()
    // ... (код поиска идентификатора) ...
    ИдентификаторДляВосстановления = НайденныйID;
    ПодключитьОбработчикОжидания("УстановитьФокусНаСтроку", 0.1, Истина);
КонецПроцедуры

&НаКлиенте
Процедура УстановитьФокусНаСтроку()
    Элементы.ДеревоДанных.ТекущаяСтрока = ИдентификаторДляВосстановления;
КонецПроцедуры

Оптимизация: поиск по полному пути

Посмотрим на ситуацию, когда в дереве могут быть дублирующиеся элементы (например, одна и та же номенклатура в разных папках). Поиск просто по «Ссылке» найдет первое вхождение, которое может оказаться не тем. В таких случаях рекомендуется сохранять массив ключей от корня до текущей строки. При поиске мы будем сверять не только саму строку, но и её родителя, что гарантирует 100% точность позиционирования.

Резюме

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

← На главную