В процессе разработки конфигураций на платформе 1С:Предприятие 8.3 часто возникает задача хранения пользовательских макетов или отчетов непосредственно в базе данных. Типичный сценарий: пользователь выбирает файл .mxl на диске, программа сохраняет его в справочник, а при открытии карточки справочника этот документ должен отобразиться в окне предварительного просмотра на форме. Разберем детально, как реализовать этот механизм, избежав типичных ошибок новичков.
Проанализируем первый этап работы. Нам необходимо предоставить пользователю возможность выбрать файл на локальном компьютере. Для этого в управляемом приложении используется объект ДиалогВыбораФайла. Рассмотрим код процедуры, которая инициирует процесс загрузки на клиенте:
&НаКлиенте
Процедура ЗагрузитьМакет(Команда)
длгВыборФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
длгВыборФайла.Заголовок = "Выберите табличный документ...";
длгВыборФайла.Фильтр = "Табличный документ (*.mxl)|*.mxl";
длгВыборФайла.Расширение = "mxl";
длгВыборФайла.ПроверятьСуществованиеФайла = Истина;
Если Не длгВыборФайла.Выбрать() Тогда
Возврат;
КонецЕсли;
// Читаем файл в объект ДвоичныеДанные
ДвоичныеДанные = Новый ДвоичныеДанные(длгВыборФайла.ПолноеИмяФайла);
// Помещаем данные во временное хранилище для передачи на сервер
Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанные, ЭтаФорма.УникальныйИдентификатор);
// Вызываем серверную процедуру для обработки
ПрочитатьНаСервере(Адрес);
КонецПроцедуры
Здесь важно обратить внимание на использование функции ПоместитьВоВременноеХранилище. Мы передаем второй параметр — УникальныйИдентификатор формы. Это гарантирует, что данные будут «жить» в хранилище до тех пор, пока открыта форма, и будут автоматически удалены после её закрытия.
Теперь рассмотрим ситуацию на сервере. Получив адрес временного хранилища, нам нужно превратить двоичные данные в объект ТабличныйДокумент и сохранить его в реквизит объекта (справочника), имеющий тип ХранилищеЗначения. Проанализируем правильный подход к этой задаче:
&НаСервере
Процедура ПрочитатьНаСервере(Адрес)
// Создаем временное имя для записи файла на стороне сервера
ФайлПриемник = ПолучитьИмяВременногоФайла("mxl");
// Получаем данные из хранилища
ДанныеХранилища = ПолучитьИзВременногоХранилища(Адрес);
// Записываем двоичные данные во временный файл
ДанныеХранилища.Записать(ФайлПриемник);
// Создаем объект ТабличныйДокумент и читаем в него файл
ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ФайлПриемник);
// Преобразуем реквизит формы "Объект" в прикладной объект справочника
ЭлементСправочника = РеквизитФормыВЗначение("Объект");
// Помещаем сам объект ТабличныйДокумент в ХранилищеЗначения
// Платформа автоматически сериализует его
ЭлементСправочника.Макет = Новый ХранилищеЗначения(ТабДок, Новый СжатиеДанных(9));
// Записываем справочник в базу
ЭлементСправочника.Записать();
// Возвращаем данные обратно в реквизит формы для мгновенного отображения
ЗначениеВРеквизитФормы(ЭлементСправочника, "Объект");
// Если на форме есть реквизит типа ТабличныйДокумент (например, АдресКартинки)
ЭтаФорма.АдресКартинки = ТабДок;
// Не забываем удалить временный файл
УдалитьФайлы(ФайлПриемник);
КонецПроцедуры
В данном примере мы использовали объект СжатиеДанных со степенью 9. Так как ТабличныйДокумент внутри представляет собой XML-структуру, он очень хорошо сжимается, что позволяет существенно экономить место в базе данных при хранении больших макетов.
Выясним причину, по которой у многих разработчиков не получается отобразить сохраненный документ при повторном открытии карточки. Проблема заключается в том, что реквизит с типом ХранилищеЗначения не считывается автоматически в данные формы. Нам необходимо программно извлечь его в обработчике ПриСозданииНаСервере.
Предположим, на вашей форме есть реквизит с именем РезультатПросмотра, имеющий тип ТабличныйДокумент. Посмотрим на алгоритм его заполнения:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем, что это не новый элемент, и данные в макете есть
Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
// Получаем объект справочника из базы
ЭлементСправочника = Объект.Ссылка.ПолучитьОбъект();
// Извлекаем содержимое ХранилищаЗначения
СодержимоеХранилища = ЭлементСправочника.Макет.Получить();
// Проверяем, является ли извлеченное значение табличным документом
Если ТипЗнч(СодержимоеХранилища) = Тип("ТабличныйДокумент") Тогда
// Напрямую присваиваем объект реквизиту формы
ЭтаФорма.РезультатПросмотра = СодержимоеХранилища;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Обратите внимание: метод ПолучитьНавигационнуюСсылку, часто используемый для картинок, в данном случае не требуется. Объект ТабличныйДокумент является полноценным типом данных, который может существовать как в базе, так и непосредственно в реквизите формы.
Разберем более современный способ, доступный в версиях платформы 8.3.9 и выше. Использование временных файлов на диске — это всегда риск (ошибки прав доступа, заполнение места, медленная работа HDD). Мы можем заменить дисковые операции работой в оперативной памяти с помощью потоков.
Проанализируем пример чтения табличного документа через ПотокВПамяти:
&НаСервере
Процедура ЗагрузитьЧерезПоток(Адрес)
// Получаем двоичные данные из хранилища
ДвоичныеДанные = ПолучитьИзВременногоХранилища(Адрес);
// Создаем поток в памяти на основе двоичных данных
Поток = ДвоичныеДанные.ОткрытьПотокДляЧтения();
ТабДок = Новый ТабличныйДокумент;
// Читаем напрямую из потока, указывая формат
ТабДок.Прочитать(Поток, СпособЧтенияТабличногоДокумента.MXL);
Поток.Закрыть();
// Дальнейшее сохранение в объект аналогично предыдущим шагам
ОбъектДляЗаписи = РеквизитФормыВЗначение("Объект");
ОбъектДляЗаписи.Макет = Новый ХранилищеЗначения(ТабДок);
ОбъектДляЗаписи.Записать();
КонецПроцедуры
Этот метод работает значительно быстрее и надежнее, так как исключает взаимодействие с файловой системой сервера.
Рассмотрим несколько нюансов, которые помогут сделать ваше решение профессиональным:
СпособЧтенияТабличногоДокумента.MXL в методе Прочитать(). Это предотвратит ошибки, если временный файл будет иметь некорректное расширение.Новый ДвоичныеДанные(ПутьКФайлу) на клиенте может не сработать. В этом случае используйте асинхронный метод НачатьПомещениеФайлаНаСервер().Подведем итог: для успешной выгрузки и отображения данных необходимо правильно организовать цепочку: Файл -> ДвоичныеДанные -> ТабличныйДокумент -> ХранилищеЗначения на этапе сохранения, и обратную цепочку ХранилищеЗначения -> ТабличныйДокумент -> Реквизит формы при чтении. Следуя этим инструкциям, вы создадите надежный механизм работы с пользовательскими макетами.