Как в 1С 7.7 обновить ссылочные данные на форме документа без её переоткрытия?

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

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

Разберем детально, почему так происходит, как устроена внутренняя работа с кэшем объектов в 1С 7.7 и какими способами можно гарантированно обновить отображение информации без переоткрытия окна.

Выясним причину: почему Форма.Обновить() не меняет данные?

Проанализируем механику работы платформы 1С 7.7 с открытыми документами:

  1. Локальный кэш реквизитов строки: Когда документ открывается в диалоге, все объекты и ссылки, находящиеся в строках его табличной части, считываются в оперативную память один раз. Ссылка на зависимый документ хранит слепок (снимок) данных на момент открытия. Если мы в коде создаем новый экземпляр через СоздатьОбъект("Документ..."), находим и меняем реквизиты связанного документа, изменения записываются в базу данных, но контекст уже открытой формы о них не знает.
  2. Назначение метода Форма.Обновить(): Данный метод лишь перерисовывает элементы диалога на основе тех данных, которые уже загружены в память текущей формы (а параметр задает сброс или сохранение флага модифицированности). Повторного считывания реквизитов связанных ссылочных объектов из базы данных при этом не происходит.

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

Способ 1. Использование метода ТекущийДокумент() с проверкой вида документа

Первый способ заставить платформу прочитать актуальные данные — обратиться к объекту через метод ТекущийДокумент(). Этот метод заставляет 1С перечитать состояние объекта из базы данных.

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

Рассмотрим, как правильно реализовать функцию отображения статуса для текстовой колонки с проверкой заполненности и вида документа:


// Функция прописывается в формуле текстовой колонки табличной части
Функция ВернутьСтатусЗаявки(ЗаявкаССайта)
    Если ЗаявкаССайта.Выбран() = 1 Тогда 
        // Проверяем конкретный вид документа, если реквизит составного типа
        Если ЗаявкаССайта.Вид() = "ЗаявкаССайта" Тогда
            Возврат ЗаявкаССайта.ТекущийДокумент().СтатусЗаявки;  
        ИначеЕсли ЗаявкаССайта.Вид() = "ЗаявкаССайта2" Тогда
            Возврат ЗаявкаССайта.ТекущийДокумент().СтатусЗаявки;  
        КонецЕсли;
    КонецЕсли;    
    Возврат "";
КонецФункции

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


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

Способ 2. Принудительный сброс кэша переприсваиванием реквизита строки

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

Существует классический способ платформы 1С 7.7, позволяющий сбросить кэш ссылки в строке: переприсвоить значение реквизита самому себе в цикле обработки. Когда мы перезаписываем значение ЗаявкаССайта = ЗаявкаССайта;, платформа сбрасывает внутренний буфер для этой строки и при следующем обращении считывает реквизиты объекта заново.

Посмотрим на пример реализации:


Процедура ОбновитьСтатусыСПереприсвоением()
    Перем ДокЗаявка;
    
    НовыйСтатус = СокрЛП(СтатусЗаявкиСписок.ПолучитьЗначение(СтатусЗаявкиСписок.ТекущаяСтрока()));
    
    ВыбратьСтроки();
    Пока ПолучитьСтроку() = 1 Цикл
        Если ЗаявкаССайта.Выбран() = 1 Тогда
            ДокЗаявка = СоздатьОбъект("Документ." + ЗаявкаССайта.Вид());
            Если ДокЗаявка.НайтиДокумент(ЗаявкаССайта) = 1 Тогда
                Если СокрЛП(ДокЗаявка.СтатусЗаявки) <> НовыйСтатус Тогда
                    ДокЗаявка.СтатусЗаявки = НовыйСтатус;
                    ДокЗаявка.Записать();
                    
                    // Переприсваиваем реквизит строки для обновления внутреннего кэша
                    ЗаявкаССайта = ЗаявкаССайта;
                КонецЕсли;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    Форма.Обновить(0);
КонецПроцедуры

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

Способ 3. Использование промежуточного кэша (СписокЗначений) и транзакций

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

Разберем по шагам создание такой схемы:

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

Посмотрим на реализацию процедуры обновления:


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

Подведем итоги:

Для небольших документов достаточно использовать вызов метода ТекущийДокумент() с обязательной проверкой вида через Вид(), либо применить переприсваивание реквизита строки самому себе (Реквизит = Реквизит;). Если же табличная часть содержит большой объем данных, оптимальным решением является хранение отображаемых реквизитов в локальной структуре (например, в СпискеЗначений) с пакетной записью в транзакции.

← На главную