Как в управляемой форме 1С заполнить реквизит с типом "ХранилищеЗначений"

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

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

Выясним причину проблемы: почему данные обнуляются?

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


ЭлементОбъекта = РеквизитФормыВЗначение("Объект"); 
ЭлементОбъекта.Схема = Новый ХранилищеЗначений(МояСхема);
ЗначениеВРеквизитФормы(ЭлементОбъекта, "Объект");

На первый взгляд, алгоритм логичен: мы получили прикладной объект из формы, заполнили его реквизит новым экземпляром ХранилищеЗначений и попытались вернуть измененный объект обратно в данные формы. Однако при последующем чтении реквизита Схема мы обнаружим там значение Неопределено.

Это происходит из-за ключевой особенности архитектуры управляемого приложения 1С. Данные на клиенте хранятся в виде специальных типов (таких как ДанныеФормыСтруктура), которые оптимизированы для передачи по каналам связи между сервером и тонким клиентом. Платформа строго ограничивает типы данных, которые могут существовать в контексте формы на клиенте. Тип ХранилищеЗначений не входит в этот список допустимых типов. Он физически не умеет «жить» на форме. Поэтому в момент вызова функции ЗначениеВРеквизитФормы платформа принудительно отсекает (очищает) любые несовместимые типы данных, очищая наше поле.

Правильное решение: использование временного хранилища

Единственно верным и стандартным решением для обхода этого ограничения является использование механизма временного хранилища. Мы не будем передавать само ХранилищеЗначений на клиент. Вместо этого мы сохраним данные во временной оперативной памяти сервера, а на форме будем держать лишь текстовую строку — уникальный адрес, по которому эти данные можно будет забрать перед физической записью объекта в базу данных.

Разберем этот процесс по шагам.

Шаг 1. Создание вспомогательного реквизита на форме

Прежде всего, нам нужен контейнер для хранения адреса. Откроем редактор управляемой формы и выполним следующие действия:

  1. Перейдем на вкладку Реквизиты в правой верхней части редактора формы.
  2. Добавим новый реквизит формы (не путать с реквизитом самого объекта). Назовем его, например, АдресДанныхВХранилище.
  3. Установим тип данных для этого реквизита — Строка. Длину можно оставить неограниченной.
  4. Выносить этот реквизит на рабочую область формы в виде элемента интерфейса не нужно — он будет использоваться исключительно в коде.

Шаг 2. Помещение данных во временное хранилище

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


&НаКлиенте
Процедура ЗагрузитьФайлИлиСхему(ПолученныеДанные)
    
    // Помещаем объект во временное хранилище и связываем его с уникальным идентификатором текущей формы
    АдресДанныхВХранилище = ПоместитьВоВременноеХранилище(ПолученныеДанные, ЭтаФорма.УникальныйИдентификатор);
    
КонецПроцедуры

Важное правило: Обратите внимание на второй параметр функции ПоместитьВоВременноеХранилище. Мы передаем туда ЭтаФорма.УникальныйИдентификатор. Это гарантирует, что платформа свяжет время жизни помещенных данных с временем жизни самой формы. Как только пользователь закроет форму (даже если он откажется от сохранения изменений и просто нажмет крестик), платформа автоматически очистит оперативную память сервера от этих временных данных. Если пренебречь этим параметром, данные будут «висеть» в памяти сервера до окончания сеанса пользователя, что быстро приведет к утечкам памяти, особенно при работе с тяжелыми файлами.

Шаг 3. Запись данных в объект (событие "ПередЗаписьюНаСервере")

Поскольку автор темы хотел иметь возможность отказаться от записи (сообщение 4), перенос данных из временного хранилища непосредственно в реквизит объекта нужно делать в самый последний момент — при непосредственной записи объекта в базу данных. Для этого идеально подходит стандартный обработчик события формы ПередЗаписьюНаСервере.

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


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

Этот подход полностью безопасен: если пользователь решит отменить запись или при проверке заполнения возникнут ошибки (и сработает флаг Отказ = Истина), транзакция записи не начнется, данные в БД не изменятся, а временное хранилище останется доступным для последующих попыток сохранения.

Шаг 4. Чтение существующих данных при открытии формы

Если мы открываем уже существующий элемент справочника или документ, нам необходимо проделать обратную операцию — извлечь данные из реквизита объекта базы данных, поместить их во временное хранилище и передать адрес на форму. Лучше всего это делать в обработчике события ПриЧтенииНаСервере:


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

Оптимизация: Принцип ленивой загрузки (Lazy Loading)

Проанализируем сценарий, когда в реквизитах типа ХранилищеЗначений сохраняются большие объемы данных (например, файлы документов весом по 50-100 Мб). Если мы будем считывать эти файлы из базы данных в событии ПриЧтенииНаСервере при каждом открытии карточки объекта, это приведет к катастрофическому падению производительности системы.

В таких случаях рекомендуется применять подход «ленивой загрузки»: при открытии формы мы оставляем реквизит АдресДанныхВХранилище пустым, а реальное чтение файла из базы данных и перенос во временное хранилище выполняем только тогда, когда пользователь непосредственно нажмет кнопку «Просмотреть файл» или «Скачать».

Посмотрим на пример реализации ленивой загрузки:


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

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

Используя этот паттерн, мы экономим оперативную память сервера приложений 1С и снижаем нагрузку на сеть, обеспечивая мгновенное открытие карточек документов для пользователей.

← На главную