При работе с обычными формами в «1С:Предприятии 8» разработчики часто сталкиваются с ситуацией, когда дополнительные данные (например, остатки, суммы оплат или даты из регистров) рассчитываются динамически и выводятся в список. Однако при переключении между строками или скроллинге списка данные в предыдущих строках могут исчезать. Проанализируем, почему это происходит, и разберем корректную методику реализации таких расчетов.
Выясним основную причину проблемы. В обычных формах объект ТабличноеПоле, связанный с динамическим списком (например, ДокументСписок), не хранит значения в расчетных колонках. События ПриВыводеСтроки и ПриПолученииДанных вызываются системой каждый раз, когда строку нужно отрисовать на экране. Если вы программно установили текст в ячейку оформления ОформлениеСтроки.Ячейки.ИмяКолонки.Текст, это значение будет отображаться ровно до тех пор, пока строка видна и не требует перерисовки.
Разберем типичную ошибку в коде автора темы:
Если ФлагРасчета = 1 И ДанныеСтроки.Ссылка = ЭлементыФормы.Список.ТекущаяСтрока Тогда
// Расчет и вывод данных только для активной строки
КонецЕсли;
Как только пользователь перемещает курсор на другую строку, условие ДанныеСтроки.Ссылка = ЭлементыФормы.Список.ТекущаяСтрока перестает выполняться для всех предыдущих строк. Система перерисовывает их, видит, что условие ложно, и не заполняет текст в ячейках. В результате колонки становятся пустыми. Чтобы данные сохранялись, нам необходимо обеспечить их вывод для всех строк, которые уже были рассчитаны, а не только для текущей.
Рассмотрим подход, предложенный в обсуждении: использование переменной модуля для хранения списка документов, по которым расчет уже выполнен. Посмотрим на алгоритм реализации:
Перем СписокРассчитанныхДокументов;.СписокРассчитанныхДокументов = Новый СписокЗначений;.ПриАктивизацииСтроки будем добавлять текущую ссылку в этот список и инициировать обновление.Проанализируем пример кода для этого метода:
Процедура СписокПриАктивизацииСтроки(Элемент)
ТекДанные = Элемент.ТекущиеДанные;
Если ТекДанные <> Неопределено Тогда
// Добавляем ссылку в список, если её там еще нет
Если СписокРассчитанныхДокументов.НайтиПоЗначению(ТекДанные.Ссылка) = Неопределено Тогда
СписокРассчитанныхДокументов.Добавить(ТекДанные.Ссылка);
КонецЕсли;
// Заставляем список перерисовать текущую строку
ЭлементыФормы.Список.ОбновитьСтроки(ТекДанные);
КонецЕсли;
КонецПроцедуры
Теперь в событии ПриВыводеСтроки мы будем проверять наличие ссылки в нашем кэше:
Процедура СписокПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
Если СписокРассчитанныхДокументов.НайтиПоЗначению(ДанныеСтроки.Ссылка) <> Неопределено Тогда
// Здесь выполняется запрос к базе или расчет данных
// ОформлениеСтроки.Ячейки.Оплачено.Текст = ...
КонецЕсли;
КонецПроцедуры
Использование ПриВыводеСтроки для запросов к базе данных — плохая практика, так как это событие вызывается для каждой строки отдельно, что порождает «N+1» проблему запросов. Разберем более эффективный способ через ПриПолученииДанных. Это событие получает сразу массив строк, которые будут выведены на экран.
Посмотрим, как реализовать пакетный расчет данных:
Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
// Собираем ссылки на документы, которые попали в видимую область
СписокСсылокДляЗапроса = Новый СписокЗначений;
Для Каждого Оформление Из ОформленияСтрок Цикл
Если СписокРассчитанныхДокументов.НайтиПоЗначению(Оформление.ДанныеСтроки.Ссылка) <> Неопределено Тогда
СписокСсылокДляЗапроса.Добавить(Оформление.ДанныеСтроки.Ссылка);
КонецЕсли;
КонецЦикла;
Если СписокСсылокДляЗапроса.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Выполняем ОДИН запрос ко всем нужным документам
ТаблицаРезультатов = РассчитатьДанныеЗаказов(СписокСсылокДляЗапроса);
// Заполняем оформление строк из полученной таблицы
Для Каждого Оформление Из ОформленияСтрок Цикл
СтрокаДанных = ТаблицаРезультатов.Найти(Оформление.ДанныеСтроки.Ссылка, "Сделка");
Если СтрокаДанных <> Неопределено Тогда
Оформление.Ячейки.Оплачено.УстановитьТекст(Формат(СтрокаДанных.Оплачено, "ЧДЦ=2"));
Оформление.Ячейки.Баланс.УстановитьТекст(Формат(СтрокаДанных.Баланс, "ЧДЦ=2"));
// Настройка цвета фона в зависимости от баланса
Если СтрокаДанных.Баланс < 0 Тогда
Оформление.Ячейки.Баланс.ЦветФона = WebЦвета.СветлоРозовый;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Разберем несколько важных технических моментов, которые помогут сделать интерфейс отзывчивым:
Неопределено. Если в результате запроса данных по ссылке нет, обращение к свойствам (например, СтрокаДанных.Оплачено) вызовет ошибку «Значение не является значением объектного типа».ОтображатьТекст = Истина;.ОформлениеСтроки рекомендуется использовать объект Соответствие. Ключом будет Ссылка, а значением — сам объект оформления.Проанализируем пример использования Соответствие внутри ПриПолученииДанных:
СоответствиеОформлений = Новый Соответствие;
Для Каждого ТекОформление Из ОформленияСтрок Цикл
СоответствиеОформлений.Вставить(ТекОформление.ДанныеСтроки.Ссылка, ТекОформление);
КонецЦикла;
// После выполнения запроса:
Пока Выборка.Следующий() Цикл
Оформление = СоответствиеОформлений[Выборка.Сделка];
Если Оформление <> Неопределено Тогда
Оформление.Ячейки.Оплачено.Текст = Выборка.Оплачено;
КонецЕсли;
КонецЦикла;
Этот подход минимизирует количество итераций и делает код чище. Выясним также, что если расчет данных очень «тяжелый», стоит задуматься о добавлении этих данных в сам текст запроса динамического списка через левые соединения, если это позволяет структура конфигурации. Однако для оперативных данных из регистров накопления (взаиморасчеты) методы ПриПолученииДанных остаются стандартом де-факто для обычных форм.