Разработка интерактивных интерфейсов в среде 1С:Предприятие 8.3 часто требует вывода динамически меняющихся данных. Типичный сценарий — вывод списка задач, очереди заказов или лога событий, который должен обновляться каждые несколько секунд по таймеру. Однако при использовании стандартных методов обновления разработчики сталкиваются с неприятным поведением интерфейса: таблица на форме начинает заметно «дергаться» (мерцать), а курсор активной строки и положение прокрутки сбрасываются на самый верх.
В этой статье мы подробно проанализируем причины такого поведения платформы и разберем четыре профессиональных подхода, которые позволят реализовать плавное, «бесшовное» обновление данных без дискомфорта для пользователя.
Выясним причину проблемы. Главная ошибка при реализации динамического обновления заключается в использовании связки методов Очистить() и Загрузить() (или полной перезаписи коллекции ДанныеФормыКоллекция). Когда мы полностью очищаем таблицу значений на форме и заполняем её заново, платформа выполняет следующие действия:
Так как старые идентификаторы строк уничтожены, платформа теряет информацию о том, на какой строке стоял пользователь (свойство ТекущаяСтрока таблицы указывает на несуществующий идентификатор). В результате фокус сбрасывается на первую строку, а полоса прокрутки (скролл) возвращается в крайнее верхнее положение. Именно этот процесс перерисовки пользователь видит как мерцание и «дерганье» экрана.
Рассмотрим по шагам четыре эффективных метода борьбы с этой проблемой.
---
Этот способ идеален, если вы не можете отказаться от использования таблицы значений на форме. Вместо тотальной очистки таблицы мы применим алгоритм точечной синхронизации. Суть метода заключается в том, что мы сравниваем текущие строки на форме с новыми данными из базы данных по уникальному ключу (например, уникальному идентификатору, коду или GUID).
Мы выполняем три последовательных шага:
Посмотрим на пример реализации такого алгоритма на встроенном языке 1С:
&НаСервере
Процедура СинхронизироватьТаблицуНаФорме(НовыеДанныеТЗ)
// НовыеДанныеТЗ - это ТаблицаЗначений, полученная из БД или внешнего источника.
// Предположим, у нас есть уникальный ключ "ИдентификаторСтроки".
// Шаг 1: Удаляем строки, которых больше нет в новых данных
ИндексыДляУдаления = Новый Массив;
Для Каждого СтрокаФормы Из Объект.ИмяТаблицыФормы Цикл
ПараметрыПоиска = Новый Структура("ИдентификаторСтроки", СтрокаФормы.ИдентификаторСтроки);
НайденныеСтроки = НовыеДанныеТЗ.НайтиСтроки(ПараметрыПоиска);
Если НайденныеСтроки.Количество() = 0 Тогда
ИндексыДляУдаления.Добавить(СтрокаФормы);
КонецЕсли;
КонецЦикла;
Для Каждого СтрокаУдаления Из ИндексыДляУдаления Цикл
Объект.ИмяТаблицыФормы.Удалить(СтрокаУдаления);
КонецЦикла;
// Шаг 2 и 3: Обновляем существующие и добавляем новые строки
Для Каждого СтрокаНовая Из НовыеДанныеТЗ Цикл
ПараметрыПоиска = Новый Структура("ИдентификаторСтроки", СтрокаНовая.ИдентификаторСтроки);
СтрокиНаФорме = Объект.ИмяТаблицыФормы.НайтиСтроки(ПараметрыПоиска);
Если СтрокиНаФорме.Количество() > 0 Тогда
// Строка уже есть на форме, обновляем только изменившиеся реквизиты
ТекущаяСтрокаФормы = СтрокиНаФорме[0];
Если ТекущаяСтрокаФормы.Статус <> СтрокаНовая.Статус Тогда
ТекущаяСтрокаФормы.Статус = СтрокаНовая.Статус;
КонецЕсли;
Если ТекущаяСтрокаФормы.Сумма <> СтрокаНовая.Сумма Тогда
ТекущаяСтрокаФормы.Сумма = СтрокаНовая.Сумма;
КонецЕсли;
// Повторите сравнение для всех необходимых колонок...
Иначе
// Строки нет на форме, добавляем новую
НоваяСтрокаФормы = Объект.ИмяТаблицыФормы.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрокаФормы, СтрокаНовая);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
При таком подходе строки, которые не претерпели изменений, визуально остаются абсолютно неподвижными. Внутренний идентификатор строки формы сохраняется, благодаря чему фокус и прокрутка не сбиваются вовсе.
---
Если специфика логики или высокая частота кардинальных изменений данных заставляют вас полностью перезаполнять таблицу значений (например, через Загрузить()), то вам необходимо научить систему принудительно возвращать фокус на место.
Здесь важно избегать распространенной ошибки разработчиков — использования свойства ТекущаяСтрока напрямую до и после обновления. На управляемой форме свойство Элементы.ИмяТаблицы.ТекущаяСтрока содержит числовой идентификатор строки коллекции формы. После очистки таблицы все старые идентификаторы аннулируются, и попытка присвоить это число обратно ни к чему не приведет.
Проанализируем правильный алгоритм восстановления фокуса:
ТекущиеДанные. Также сохраняем имя активной колонки.ПолучитьИдентификатор().Рассмотрим пример кода, реализующий этот алгоритм:
&НаКлиенте
Процедура ОбновитьТаблицуССохранениемФокуса()
// 1. Запоминаем текущее положение
АктивныйЭлемент = Элементы.ТаблицаДанных.ТекущийЭлемент; // Имя колонки
ТекущиеДанные = Элементы.ТаблицаДанных.ТекущиеДанные;
СохраненныйКлюч = Неопределено;
Если ТекущиеДанные <> Неопределено Тогда
СохраненныйКлюч = ТекущиеДанные.УникальныйИдентификаторЗаписи;
КонецЕсли;
// 2. Вызываем серверный метод полной перезаписи данных
ОбновитьДанныеНаСервере();
// 3. Восстанавливаем фокус
Если СохраненныйКлюч <> Неопределено Тогда
ПараметрыПоиска = Новый Структура("УникальныйИдентификаторЗаписи", СохраненныйКлюч);
НайденныеСтроки = Объект.ТаблицаДанных.НайтиСтроки(ПараметрыПоиска);
Если НайденныеСтроки.Количество() > 0 Тогда
СтрокаДляФокуса = НайденныеСтроки[0];
// Получаем актуальный идентификатор строки для формы
ИдентификаторСтроки = СтрокаДляФокуса.ПолучитьИдентификатор();
// Позиционируем курсор обратно
Элементы.ТаблицаДанных.ТекущаяСтрока = ИдентификаторСтроки;
КонецЕсли;
КонецЕсли;
// Возвращаем фокус на ту же колонку, где стоял курсор
Если АктивныйЭлемент <> Неопределено Тогда
Элементы.ТаблицаДанных.ТекущийЭлемент = АктивныйЭлемент;
КонецЕсли;
КонецПроцедуры
---
Если ваши данные находятся в сторонней базе данных (MS SQL, PostgreSQL, Oracle), наиболее архитектурно правильным решением будет использование встроенного объекта ДинамическийСписок вместо таблицы значений.
Для реализации этого подхода разберем шаги:
ДинамическийСписок.Теперь для плавного обновления данных на форме вам достаточно вызвать простую команду на клиенте:
&НаКлиенте
Процедура ОбновитьДинамическийСписок()
Элементы.ИмяДинамическогоСписка.Обновить();
КонецПроцедуры
Платформа 1С выполнит этот запрос в асинхронном режиме. Обновление произойдет абсолютно незаметно для пользователя: фокус, активная строка и текущий скроллинг останутся на месте в автоматическом режиме.
---
При использовании обработчика ожидания (таймера) напрямую в интерфейсе пользователя кроется скрытая угроза. Если внешняя база данных или тяжелый запрос на сервере начнут отвечать с задержкой в 2–3 секунды, весь интерфейс программы у пользователя будет замирать (зависать) каждые 5–10 секунд.
Чтобы исключить зависания интерфейса, процесс получения данных необходимо перевести на рельсы асинхронных Фоновых заданий. Разберем эту схему работы:
ВременноеХранилище. Пользователь при этом спокойно продолжает работать с формой, интерфейс отзывчив.Посмотрим на схематичный пример реализации запуска и проверки фонового задания:
&НаКлиенте
Процедура ОбработчикОжиданияОбновления()
Если ЗначениеЗаполнено(ИдентификаторЗадания) Тогда
// Проверяем, завершено ли ранее запущенное задание
Если ФоновоеЗаданиеЗавершено(ИдентификаторЗадания) Тогда
// Получаем результат и обновляем форму
ДанныеИзХранилища = ПолучитьРезультатИзХранилища(АдресХранилища);
СинхронизироватьТаблицуНаФорме(ДанныеИзХранилища);
// Сбрасываем переменные для возможности нового запуска
ИдентификаторЗадания = Неопределено;
КонецЕсли;
Иначе
// Запускаем новое фоновое задание в асинхронном режиме
АдресХранилища = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
ИдентификаторЗадания = ЗапуститьПолучениеДанныхВФоне(АдресХранилища);
КонецЕсли;
КонецПроцедуры
Использование связки асинхронных фоновых заданий и мягкого обновления («синхронизации») гарантирует максимальную отзывчивость программы, отсутствие раздражающих зависаний и плавное обновление данных без мерцания интерфейса.