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