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