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