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

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

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

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

Почему сбивается позиционирование? Выясняем причины

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

  1. Асинхронность метода Обновить(). При вызове Элементы.Список.Обновить() обновление данных на клиенте происходит не мгновенно. Если сразу после этой команды в коде идет переприсвоение строки, платформа еще «не знает» о новых данных в локальном кэше, не находит ключ и сбрасывает фокус на строку по умолчанию.
  2. Изменение ключевых полей (измерений) регистра. Если измененный вами реквизит (например, «Ответственный», «Организация», «Номенклатура») является измерением регистра сведений, то старый ключ записи (РегистрСведенийКлючЗаписи) физически перестает существовать в базе данных. Платформа пытается спозиционироваться на несуществующий ключ и закономерно перескакивает на соседнюю строку.
  3. Отсутствие основной таблицы в настройках динамического списка. Если список построен на базе произвольного запроса без указания свойства «Основная таблица», система использует для идентификации строк внутренние числовые индексы. При любом обновлении или изменении сортировки эти индексы пересчитываются, делая старый идентификатор невалидным.
  4. Влияние обработчиков событий формы. При потере и возврате фокуса (например, если для изменения реквизита открывалось модальное окно или вспомогательная форма выбора) могут повторно срабатывать события формы (ПриАктивизацииСтроки, ПослеЗаписи), которые перебивают ваши попытки установить позицию.

Рассмотрим подробнее, как решить каждую из этих проблем по шагам.

Решение 1. Использование встроенного механизма оповещений вместо принудительного обновления

Самая частая ошибка разработчиков — вызов тяжелого метода Обновить() с последующей попыткой присвоить значение свойству ТекущаяСтрока. Вместо этого платформа 1С предоставляет элегантный механизм оповещения системы об изменениях в базе данных.

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


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

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

Решение 2. Формирование нового ключа записи при изменении измерений

Что делать, если в процессе редактирования изменилось одно из измерений регистра (например, тот самый «Ответственный» составного типа)? В этом случае старый ключ записи аннулируется. Нам необходимо программно собрать новый ключ записи на сервере и позиционировать список уже на него.

Проанализируем ситуацию на примере. Допустим, наш регистр сведений имеет измерения ДокументЗаказа и Исполнитель. Мы поменяли исполнителя. Нам нужно вернуть на клиент обновленный ключ записи.


&НаСервереБезКонтекста
Функция ИзменитьИсполнителяНаСервере(СтарыйКлюч, НовыйИсполнитель)
    
    // Получаем структуру старых измерений из существующего ключа
    СтруктураИзмерений = Новый Структура;
    Для Каждого КлючИЗначение Из СтарыйКлюч Цикл
        СтруктураИзмерений.Вставить(КлючИЗначение.Ключ, КлючИЗначение.Значение);
    КонецЦикла;
    
    // Читаем запись, меняем данные и записываем
    МенеджерЗаписи = РегистрыСведений.ОперацииЗаказов.СоздатьМенеджерЗаписи();
    ЗаполнитьЗначенияСвойств(МенеджерЗаписи, СтруктураИзмерений);
    МенеджерЗаписи.Прочитать();
    
    Если МенеджерЗаписи.Выбран() Тогда
        // Удаляем старую запись (так как поменялось ключевое поле-измерение)
        МенеджерЗаписи.Удалить();
        
        // Создаем новую запись с измененным измерением
        НовыйМенеджер = РегистрыСведений.ОперацииЗаказов.СоздатьМенеджерЗаписи();
        ЗаполнитьЗначенияСвойств(НовыйМенеджер, СтруктураИзмерений);
        НовыйМенеджер.Исполнитель = НовыйИсполнитель; // Устанавливаем новое измерение
        // Заполняем ресурсы, если необходимо
        НовыйМенеджер.Записать();
        
        // Создаем и возвращаем клиенту НОВЫЙ ключ записи
        НовыеИзмерения = Новый Структура;
        НовыеИзмерения.Вставить("ДокументЗаказа", СтруктураИзмерений.ДокументЗаказа);
        НовыеИзмерения.Вставить("Исполнитель", НовыйИсполнитель);
        
        Возврат РегистрыСведений.ОперацииЗаказов.СоздатьКлючЗаписи(НовыеИзмерения);
    КонецЕсли;
    
    Возврат Неопределено;
    
КонецФункции

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

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

Решение 3. Настройка основной таблицы динамического списка

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

Разберем по шагам, как правильно настроить свойства списка:

  1. Откройте форму, на которой размещен динамический список.
  2. Перейдите в свойства реквизита списка (не элемента формы, а именно реквизита с типом ДинамическийСписок).
  3. Найдите свойство «Основная таблица» (Main Table).
  4. Если там пусто, выберите из списка ваш регистр сведений (например, РегистрСведений.ОперацииЗаказов).

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

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

Решение 4. Блокировка циклических событий формы

Иногда позиционирование слетает из-за того, что в модуле формы написана логика в обработчике события ПриАктивизацииСтроки. При программной записи фокус временно сбивается, событие срабатывает и переопределяет переменные или состояние формы, мешая корректной отработке кода позиционирования.

Для решения этой проблемы мы рекомендуем использовать простую флаговую переменную модуля формы:


&НаКлиенте
Переменная мБлокироватьАктивизацию;

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    мБлокироватьАктивизацию = Ложь;
КонецПроцедуры

&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
    
    // Если взведен флаг блокировки - игнорируем выполнение тяжелого кода
    Если мБлокироватьАктивизацию = Истина Тогда
        Возврат;
    КонецЕсли;
    
    // Ваш стандартный код при изменении строки...
    
КонецПроцедуры

&НаКлиенте
Процедура ИзменитьДанныеСБлокировкой()
    
    мБлокироватьАктивизацию = Истина;
    
    Попытка
        // Выполняем запись данных и обновление
        ТекСтрока = Элементы.Список.ТекущаяСтрока;
        ЗаписатьДанныеНаСервере(ТекСтрока);
        
        ОповеститьОбИзменении(ТекСтрока);
        Элементы.Список.ТекущаяСтрока = ТекСтрока;
    Исключение
        мБлокироватьАктивизацию = Ложь;
        ВызватьИсключение КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
    КонецПопытки;
    
    мБлокироватьАктивизацию = Ложь;
    
КонецПроцедуры

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

Применяя данные подходы в комплексе, вы сможете добиться идеального и плавного поведения динамических списков в ваших решениях на платформе 1С:Предприятие 8.3.

← На главную