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

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

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

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

Почему происходит мерцание и сброс фокуса?

Выясним причину проблемы. Главная ошибка при реализации динамического обновления заключается в использовании связки методов Очистить() и Загрузить() (или полной перезаписи коллекции ДанныеФормыКоллекция). Когда мы полностью очищаем таблицу значений на форме и заполняем её заново, платформа выполняет следующие действия:

  1. Полностью уничтожает существующие строки на уровне интерфейса.
  2. Генерирует новые внутренние идентификаторы для всех добавляемых строк.
  3. Перерисовывает элемент управления «Таблица» с нуля.

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

Рассмотрим по шагам четыре эффективных метода борьбы с этой проблемой.

---

Решение 1. Алгоритм «мягкой» синхронизации коллекций

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

Мы выполняем три последовательных шага:

  1. Удаление устаревших данных: обходим строки на форме и удаляем те, которых больше нет в новом наборе данных.
  2. Обновление изменившихся данных: для строк, которые есть и на форме, и в новых данных, мы обновляем значения только в тех колонках, где данные действительно изменились.
  3. Добавление новых данных: добавляем в конец таблицы новые строки, которых раньше не было.

Посмотрим на пример реализации такого алгоритма на встроенном языке 1С:


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

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

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

---

Решение 2. Программное сохранение и восстановление фокуса

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

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

Проанализируем правильный алгоритм восстановления фокуса:

  1. Перед очисткой получаем бизнес-ключ строки (например, уникальный код или GUID) через свойство ТекущиеДанные. Также сохраняем имя активной колонки.
  2. Очищаем и заново заполняем таблицу значений.
  3. Находим строку по сохраненному бизнес-ключу в обновленной коллекции.
  4. Получаем новый внутренний идентификатор найденной строки с помощью метода ПолучитьИдентификатор().
  5. Устанавливаем фокус на строку и активную колонку.

Рассмотрим пример кода, реализующий этот алгоритм:


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

---

Решение 3. Использование Динамического списка с Внешним источником данных (ВИД)

Если ваши данные находятся в сторонней базе данных (MS SQL, PostgreSQL, Oracle), наиболее архитектурно правильным решением будет использование встроенного объекта ДинамическийСписок вместо таблицы значений.

Для реализации этого подхода разберем шаги:

  1. В конфигураторе 1С добавьте объект метаданных Внешний источник данных и настройте подключение к сторонней таблице СУБД.
  2. Добавьте таблицу из этой СУБД в состав внешнего источника данных в 1С.
  3. На управляемой форме создайте реквизит с типом ДинамическийСписок.
  4. Установите флаг Произвольный запрос в свойствах динамического списка и напишите SELECT-запрос к таблице внешнего источника.
  5. Критически важно: в свойствах динамического списка обязательно укажите поле Основная таблица. Выберите там вашу внешнюю таблицу. Без указания основной таблицы динамический список лишится своих главных преимуществ: считывания данных порциями и автоматического удержания фокуса.

Теперь для плавного обновления данных на форме вам достаточно вызвать простую команду на клиенте:


&НаКлиенте
Процедура ОбновитьДинамическийСписок()
    Элементы.ИмяДинамическогоСписка.Обновить();
КонецПроцедуры

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

---

Решение 4. Асинхронное получение данных через Фоновые задания

При использовании обработчика ожидания (таймера) напрямую в интерфейсе пользователя кроется скрытая угроза. Если внешняя база данных или тяжелый запрос на сервере начнут отвечать с задержкой в 2–3 секунды, весь интерфейс программы у пользователя будет замирать (зависать) каждые 5–10 секунд.

Чтобы исключить зависания интерфейса, процесс получения данных необходимо перевести на рельсы асинхронных Фоновых заданий. Разберем эту схему работы:

  1. По таймеру на клиенте запускается процедура, которая проверяет, выполняется ли фоновое задание по сбору данных.
  2. Если задание не запущено, клиент инициирует его запуск на сервере. Фоновое задание в этот момент самостоятельно подключается к внешней БД, забирает информацию и кладет результат во ВременноеХранилище. Пользователь при этом спокойно продолжает работать с формой, интерфейс отзывчив.
  3. При следующем срабатывании таймера клиентская процедура проверяет статус фонового задания. Если задание успешно завершилось, клиент забирает результат из хранилища и вызывает процедуру «мягкого» обновления таблицы значений на форме (как описано в Решении 1).

Посмотрим на схематичный пример реализации запуска и проверки фонового задания:


&НаКлиенте
Процедура ОбработчикОжиданияОбновления()
    
    Если ЗначениеЗаполнено(ИдентификаторЗадания) Тогда
        // Проверяем, завершено ли ранее запущенное задание
        Если ФоновоеЗаданиеЗавершено(ИдентификаторЗадания) Тогда
            // Получаем результат и обновляем форму
            ДанныеИзХранилища = ПолучитьРезультатИзХранилища(АдресХранилища);
            СинхронизироватьТаблицуНаФорме(ДанныеИзХранилища);
            
            // Сбрасываем переменные для возможности нового запуска
            ИдентификаторЗадания = Неопределено;
        КонецЕсли;
    Иначе
        // Запускаем новое фоновое задание в асинхронном режиме
        АдресХранилища = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
        ИдентификаторЗадания = ЗапуститьПолучениеДанныхВФоне(АдресХранилища);
    КонецЕсли;
    
КонецПроцедуры

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

← На главную