Работа с макетами типа ActiveDocument (традиционно это файлы Microsoft Word или Excel) часто вызывает затруднения у начинающих разработчиков 1С, особенно при переходе от файловой базы к клиент-серверному варианту работы. Основная сложность заключается в том, что серверная часть 1С не умеет напрямую взаимодействовать с графическим интерфейсом приложений MS Office. В этой статье мы подробно разберем, как правильно организовать печать Word-шаблона из внешней обработки, рассмотрим типичные ошибки и напишем отказоустойчивый код.
Разберем ситуацию: в файловом режиме код работает прекрасно, но как только обработка запускается в составе 1C:ERP на сервере, возникают ошибки. Выясним причину. Серверные процессы 1С работают в так называемой «нулевой сессии» Windows. В этой среде запуск приложений с графическим интерфейсом (GUI), таких как WINWORD.EXE, либо полностью запрещен, либо сильно ограничен политиками безопасности. Кроме того, у системного пользователя USR1CV8, под которым обычно запущена служба сервера, нет прав на инициализацию COM-объектов Microsoft Office.
Важное правило: формирование и заполнение макета ActiveDocument должно происходить исключительно на стороне клиента (&НаКлиенте). Роль сервера сводится к тому, чтобы извлечь двоичные данные макета из конфигурации (или внешней обработки) и передать их на клиентскую машину.
Проанализируем ситуацию, когда наш макет находится внутри внешней обработки. Просто вызвать ПолучитьМакет() в модуле формы не получится, так как этот метод принадлежит объекту обработки, а не форме. Рассмотрим, как правильно получить данные макета на сервере:
&НаСервере
Функция ПолучитьАдресМакетаВоВременномХранилище()
// Преобразуем данные формы (Реквизит "Объект") в прикладной объект обработки
ОбъектОбработки = РеквизитФормыВЗначение("Объект");
// Получаем макет по имени. Макет должен иметь тип ActiveDocument
Макет = ОбъектОбработки.ПолучитьМакет("МакетWord");
// Сохраняем макет во временный файл на сервере
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("docx");
Макет.Записать(ИмяВременногоФайла);
// Читаем файл в двоичные данные и помещаем во временное хранилище
ДанныеФайла = Новый ДвоичныеДанные(ИмяВременногоФайла);
АдресХранилища = ПоместитьВоВременноеХранилище(ДанныеФайла, ЭтаФорма.УникальныйИдентификатор);
// Удаляем временный файл на сервере после использования
УдалитьФайлы(ИмяВременногоФайла);
Возврат АдресХранилища;
КонецФункции
Обратите внимание на использование РеквизитФормыВЗначение("Объект"). Это критически важный момент для внешних обработок, так как только через него мы получаем доступ к методу ПолучитьМакет() на сервере.
Теперь рассмотрим, как принять эти данные на клиенте и открыть их в приложении MS Word. Посмотрим на алгоритм: мы должны забрать двоичные данные из хранилища, сохранить их в локальный временный файл на диске пользователя и передать путь к этому файлу в COM-объект Word.Application.
&НаКлиенте
Процедура НапечататьМакетWord(Команда)
// Получаем адрес с сервера
АдресМакета = ПолучитьАдресМакетаВоВременномХранилище();
Если ЭтоАдресВременногоХранилища(АдресМакета) Тогда
// Получаем двоичные данные на клиент
ДанныеМакета = ПолучитьИзВременногоХранилища(АдресМакета);
// Формируем путь к временному файлу на локальном компьютере
ПутьКЛокальномуФайлу = КаталогВременныхФайлов() + "Template_" + Формат(ТекущаяДата(), "ДФ=yyyyMMddHHmmss") + ".docx";
ДанныеМакета.Записать(ПутьКЛокальномуФайлу);
// Запускаем печать или редактирование
ВывестиДанныеВWord(ПутьКЛокальномуФайлу);
КонецЕсли;
КонецПроцедуры
Рассмотрим подробнее процедуру взаимодействия с MS Word. Нам необходимо инициализировать COM-соединение, открыть документ и сделать его видимым для пользователя. Проанализируем пример кода, который также включает безопасное завершение работы в случае ошибки:
&НаКлиенте
Процедура ВывестиДанныеВWord(ПутьКФайлу)
Попытка
// Пытаемся создать объект Word
WordПриложение = Новый COMОбъект("Word.Application");
Исключение
Сообщить("Не удалось запустить Microsoft Word. Убедитесь, что программа установлена.");
Возврат;
КонецПопытки;
Попытка
// Открываем документ
Документ = WordПриложение.Documents.Open(ПутьКФайлу);
WordПриложение.Visible = Истина;
WordПриложение.Activate();
// Здесь можно добавить код для поиска и замены тегов в макете
// Пример: Документ.Content.Find.Execute("{НомерДокумента}", , , , , , , , , "001", 2);
Исключение
// Если что-то пошло не так, закрываем Word, чтобы процесс не висел в памяти
Если WordПриложение <> Неопределено Тогда
WordПриложение.Quit(Ложь); // Закрыть без сохранения
WordПриложение = Неопределено;
КонецЕсли;
Сообщить("Произошла ошибка при работе с документом: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Разберем ситуацию, упомянутую в обсуждении: ошибка при обращении к функции ПолучитьМакет. Выясним причину: в 1С ПолучитьМакет является встроенным системным методом. Если вы называете свою функцию точно так же, возникает конфликт имен.
Рекомендация: Всегда давайте своим функциям уникальные префиксы, например, Satoshi_ПолучитьМакет или Сервер_ПолучитьМакетДляПечати. Это исключит путаницу с глобальным контекстом 1С.
Еще один важный момент связан с использованием Библиотеки стандартных подсистем (БСП). Если вы работаете в 1C:ERP, 1C:КА или 1С:УТ 11, в конфигурации уже есть готовые механизмы для печати документов в формате Word. Выясним преимущества использования БСП:
{v8 ИмяПоля} прямо в макете, и БСП автоматически заполнит их данными из вашей выборки.Рассмотрим, как это выглядит в БСП. Вместо ручного манипулирования COM-объектом, вы вызываете функции общего модуля УправлениеПечатьюMSWord. Это значительно сокращает объем кода в вашей внешней обработке и делает его более надежным.
Подведем итоги. Чтобы успешно вывести на печать макет ActiveDocument во внешней обработке, необходимо придерживаться следующей последовательности действий:
РеквизитФормыВЗначение.COMОбъект("Word.Application") для открытия этого файла.Попытка...Исключение, чтобы предотвратить "зависание" процессов WINWORD.EXE в диспетчере задач.Следуя этим правилам, вы сможете реализовать гибкое редактирование и печать документов Word в любых конфигурациях на платформе 1С:Предприятие 8.3.