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

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

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

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

Ограничения платформы: почему нет простого решения

Прежде чем приступать к кодированию, давайте разберемся с архитектурой. В объектной модели управляемого приложения (на клиенте) у элементов формы типа ТаблицаФормы отсутствуют свойства, отвечающие за прямую координату прокрутки (например, аналог ScrollTop или позиции в пикселях). Это означает, что мы не можем просто запомнить число «150 пикселей сверху» и восстановить его.

Единственный штатный инструмент управления видимостью строк, который предоставляет нам платформа 1С — это свойство ТекущаяСтрока. Когда мы программно устанавливаем текущую строку, платформа автоматически прокручивает таблицу так, чтобы эта строка стала видимой.

Таким образом, задача «восстановить прокрутку» сводится к задаче «умного позиционирования курсора на нужную строку».

Стратегия 1: Позиционирование по идентификатору

Самый простой и надежный сценарий — это когда обновляемый элемент гарантированно остается в дереве. Рассмотрим алгоритм действий:

  1. Перед началом обновления данных запоминаем идентификатор текущей строки.
  2. Выполняем обновление дерева (перезаполняем коллекцию).
  3. Ищем строку с сохраненным идентификатором в новой коллекции.
  4. Если строка найдена, устанавливаем её идентификатор в свойство ТекущаяСтрока элемента формы.

Посмотрим на пример реализации этого подхода:


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

КонецПроцедуры

Важное замечание: Если вы полностью очищаете дерево (Дерево.ПолучитьЭлементы().Очистить()) и заполняете его заново, внутренние идентификаторы строк формы изменятся. В этом случае "запоминать" нужно не ТекущаяСтрока (числовой ID формы), а ключевые данные строки (например, значение реквизита "Ссылка" или "Код"), а после обновления — искать строку с этими данными и получать её новый ID.

Стратегия 2: Алгоритм «Поиск соседа» при удалении строк

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

В этом случае нам необходимо реализовать логику поиска «ближайшего соседа», чтобы визуально прокрутка осталась примерно на том же месте. Разберем, какие данные нам потребуются для этого:

  1. Ключ текущей строки: уникальное значение, идентифицирующее строку данных.
  2. Ключ родителя: чтобы понимать, в какой ветке мы находились.
  3. Индекс строки: порядковый номер строки внутри родительской ветки.

Алгоритм восстановления будет следующим:

  1. Пытаемся найти строку по Ключу. Если нашли — отлично, встаем на неё.
  2. Если не нашли, ищем родителя по Ключу родителя.
  3. Если родитель найден, смотрим его подчиненные элементы.
  4. Пытаемся встать на строку с тем же Индексом, который был у удаленной строки (следующий элемент сдвинулся на место удаленного).
  5. Если элементов стало меньше и такого индекса нет, встаем на последний элемент в ветке (индекс - 1).
  6. Если ветка пуста, встаем на самого Родителя.

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


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

КонецФункции

Стратегия 3: Мягкое обновление (Sync/Merge)

Самый продвинутый способ сохранить положение прокрутки, не прибегая к сложным вычислениям позиций — это не пересоздавать дерево полностью. Когда вы делаете Очистить(), а затем заполняете дерево заново, платформа видит это как создание абсолютно новых объектов. Это и вызывает "прыжок" скролла.

Вместо этого мы можем использовать алгоритм синхронизации данных. Рассмотрим идею:

  1. Получаем новые данные (например, таблицу значений) из базы.
  2. Обходим текущее дерево значений на форме рекурсивно.
  3. Сравниваем строки по ключевым полям.
  4. Если строка есть в старых и в новых данных — просто обновляем значения её реквизитов (Цены, Количество и т.д.).
  5. Если строки нет в новых данных — удаляем её из дерева.
  6. Если в новых данных есть строки, которых нет в дереве — добавляем их в нужные места.

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

Да, написание такого алгоритма (Merge) сложнее, чем простого "Очистить/Заполнить", но это дает наилучший пользовательский опыт (User Experience).

Резюме

Подытожим возможные решения проблемы:

  1. Простое позиционирование: Запоминаем ключевое поле текущей строки, после обновления ищем его и делаем Элементы.Дерево.ТекущаяСтрока = НовыйИД.
  2. Умное позиционирование: Если строка удалена, используем сохраненный индекс строки и идентификатор родителя, чтобы встать на "соседнюю" строку. Это имитирует сохранение прокрутки.
  3. Мягкое обновление: Вместо пересоздания дерева обновляем существующие строки. Это технически сложнее, но решает проблему визуальных скачков и сброса скролла на уровне платформы.

Используйте метод "Умного позиционирования" для списков, где строки часто удаляются, и метод "Мягкого обновления" для сложных деревьев, где важно сохранять состояние развернутых веток и фокус ввода.

← На главную