Передача данных из 1с в word. Работа с ActiveDocument в клиент-серверном варианте

Итак, в чем суть моей статьи?

1. Есть сервер (x64), с установленным Office 2016 (x64), есть 1С 8.3.8 также x64 (имеется в виду сервер 1С). Пользователи подключаются как через тонкого клиента, так и через веб клиент.

2. Есть куча типовых договоров и накладных счет-фактур, оформленных (подготовленных) отделом продаж. Там шрифт, абзац и т.д., естественно, все оформлено в Word,Excel 2016 (x64), естественно, нет желания все это рисовать в 1С, а есть желание засунуть в макеты формата ActiveDocument

Решение банальное, вроде бы :) , не буду описывать весь механизм, как это делается, как заполняются данные из 1С в макет ActiveDocument - думаю, вы это знаете прекрасно и без меня. Но в ходе работы выяснилось, что не все так гладко в царстве COM объектов, а именно:

2. Какой вариант лучше выбрать: ActiveDocument или ДвоичныеДанные? Хотя для меня это звучит примерно как, что выбрать Водку С Пивом или Пиво С Водкой :) . Один фиг, надо забить документы данными из 1С и передать их Клиенту.

Ну да ладно пропустим лирику, я выбираю ActiveDocument, не буду описывать весь алгоритм, просто перечислю "подводные" камни и их решение. Все нижеизложенное это мои личные измышления и в никакой мере не претендуют на истину в последней инстанции. Возможно, вы решили эти проблемы или решите по другому.

1. Камень "первый ". Не работает метод SaveAs (как для MSWord, так и для MSExcel). При попытке записать ДвоичныеДанные 1С просто вылетает. Смотрим фрагмент листинга:

MSWord = Макет.Получить(); Попытка Документ = MSWord.Application.Documents(1); Документ.Activate(); //Далее каким-либо образом получаем данные и заполняем Word-овский документ //Получим путь во временной директории для сохранения туда файла ИмяВрем = ПолучитьИмяВременногоФайла(".docx"); Документ.SaveAs(ИмяВрем); //здесь начинается камень МойДокументВДвоичныхДанных = Новый ДвоичныеДанные(ИмяВрем); //ну а здесь этот камень нафиг выбивает 1С:) MSWord.Application.Quit();

Спасибо огромное.

Создание папок
C:\Windows\SysWOW64\config\systemprofile\Desktop
C:\Windows\System32\config\systemprofile\Desktop
проблему решило. Тема закрыта.

В чем причина? Причина в том, что код

MSWord = Макет.Получить();

Всегда вызывает экземпляр объекта COM (x32) независимо от того какой разрядности Office установлен. Вы никогда не задумывались, почему в макет ActoveDocument нельзя вставить файлы с расширением docx,xlsx

это можно проверить и через ДиспетчерЗадач, но факт есть факт - макет ActiveDocument вызывает неявно экземпляр COM (x32) и поэтому все дальнейшие манипуляции нужно делать учитывая это особенность.

1. Либо сервер и все ПО должно быть x32. Тогда ничего делать (в смысле переписывать код) не надо

2. Либо переписать код, таким образом

// получаем имя временного файла ВремФайл = ПолучитьИмяВременногоФайла("doc"); // этот код точно вызовет экземпляр COM нужной разрядности, в нашем случае x64 Word = Новый COMОбъект("Word.Application"); Word.Displayalerts = 0; ДокументН = Word.Application.Documents.Add(); ДокументН.SaveAs(ВремФайл,0); Word.Quit(); // далее все по старому Макет = УправлениеПечатью.МакетПечатнойФормы("Документ.АктПередачиОборудования."+НазваниеМакета); MSWord = Макет.Получить(); Попытка Документ = MSWord.Application.Documents(1); Документ.Activate(); // здесь что-то делаем, заполняем данные // здесь мы пересохраним наш файл из COM x62 в COM x64 MSWord.Application.Selection.WholeStory(); MSWord.Application.Selection.Copy(); ДокументН = MSWord.Application.Documents.Open(ВремФайл); ДокументН.Activate(); MSWord.Application.Selection.Paste(); ДокументН.SaveAs(ВремФайл,0); ДокументН.Close(); MSWord = Неопределено; Исключение // Если произойдет ошибка выводятся данные об ошибке и объект закрывается. Информация = ИнформацияОбОшибке(); ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка - "+Информация.Описание+" код ошибки - "+СокрЛП(Информация.ИсходнаяСтрока)); MSWord.Application.Quit(); КонецПопытки;

Думаю, тут все понятно, сначала мы создали экземпляр COM нужной разрядности, создали пустой файл и сохранили во временную папку, далее работает с COM x32, заполняем данными и напоследок копируем содержимое всего документа и сохраняем в ранее подготовленный файл.

Все то же самое, но только для Excel

ВремФайл = ПолучитьИмяВременногоФайла("xls"); Excel = Новый COMОбъект("Excel.Application"); Excel.Displayalerts = 0; КнигаН = Excel.WorkBooks.Add(); ЛистН = КнигаН.WorkSheets(1); КнигаН.SaveAs(ВремФайл, -4143); Excel.Quit(); Макет = УправлениеПечатью.МакетПечатнойФормы("Документ.НакладнаяОборудования."+НазваниеМакета); MSExcel = Макет.Получить(); КнигаН = MSExcel.Application.Workbooks.Open(ВремФайл); ЛистН = КнигаН.WorkSheets(1); Попытка WBook = MSExcel.Application.Workbooks(1); Лист = WBook.WorkSheets(1); Лист.Activate(); // что-то делаем, заполняем данными из 1С MSExcel.Application.WorkBooks(1).WorkSheets(1).Cells.Copy(ЛистН.Cells); КнигаН.Save(); КнигаН.Close(); Исключение // Если произойдет ошибка выводятся данные об ошибке и объект закрывается. Информация = ИнформацияОбОшибке(); ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка - "+Информация.Описание+" код ошибки - "+СокрЛП(Информация.ИсходнаяСтрока)); MSExcel.Application.Quit(); КонецПопытки;

ну вот "первый " камень я решил, на сервере x64 с Office x64, все работает точно как часы, без ошибок и не надо создавать никаких папок и все прочее.

Камень "второй ". фрагмент кода

ВремФайл = ПолучитьИмяВременногоФайла("xls");

есть не очень хорошо, потому как идет запись в папку: "c:\Users\ че там....", вообще эта папка всегда в черном списке всех фаерволов, антивирусов и прочее, прочее, хотя бы открыть центр управления безопасностью Word или Excel. Глянем и мы туда

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

1. Открываем Конфигуратор и добавляем новый РегистрСведений

здесь мы будем хранить наши готовые Word, Excel файлы уже заполненные, конечно:

НазваниеМакета - Идентификатор макета

ДокументOffice - ХранилищеЗначений, здесь мы и держим наш готовый файл

2. Дописываем выше написанный код следующим образом:

МЗ = РегистрыСведений.ВременноеХранилищеOffice.СоздатьМенеджерЗаписи(); МЗ.Объект = Выборка.Ссылка; МЗ.НазваниеМакета = НазваниеМакета; МЗ.Прочитать(); МЗ.Объект = Выборка.Ссылка; МЗ.НазваниеМакета = НазваниеМакета; МЗ.ДокументOffice = Новый ХранилищеЗначения(Новый ДвоичныеДанные(ВремФайл)); МЗ.Записать(); УдалитьФайлы(ВремФайл);

Что мы делаем, мы записываем готовый файл в регистр сведений и затем удаляем сам временный файл, решаем проблему "Центра безопасности Word,Excel". Осталось только одно показать этот готовый файл Клиенту (клиент тонкий и веб)

3. Камень "третий " - передача файла клиенту, тут просто выложу весь код, что-то взял из БСП, что-то из Демонстрационная конфигурация "Управляемое приложение", что-то из Инета, но в общем вот код (целиком)

//////////////////////////////////////////////////////////////////////////////// // СЛУЖЕБНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ БСП &НаСервере Функция ПолучитьМакет() ОбъектСсылка = СтруктураДанных.Объект; НазваниеМакета = СтруктураДанных.НазваниеМакета; КлючЗаписи = РегистрыСведений.ВременноеХранилищеOffice.СоздатьКлючЗаписи(Новый Структура("Объект,НазваниеМакета",ОбъектСсылка,НазваниеМакета)); // Адрес = ПолучитьНавигационнуюСсылку(КлючЗаписи,"ДокументOffice"); Возврат Адрес; КонецФункции // &НаКлиенте Процедура ПослеЗапускаПриложения(КодВозврата, ИмяПриложения) Экспорт; // КонецПроцедуры &НаКлиенте Процедура ПослеПолученияФайлов(ПереданныеФайлы, ДополнительныеПараметры) Экспорт Если НЕ ПереданныеФайлы=Неопределено Тогда Для каждого Описание Из ПереданныеФайлы Цикл ОпПослеЗапускаПриложения = Новый ОписаниеОповещения("ПослеЗапускаПриложения", ЭтотОбъект, Описание.Имя); НачатьЗапускПриложения(ОпПослеЗапускаПриложения, Описание.Имя); КонецЦикла; КонецЕсли; КонецПроцедуры &НаКлиенте Процедура ПослеВыбораКаталога(ВыбранныеФайлы, ИмяКоманды) Экспорт Если ВыбранныеФайлы = Неопределено Тогда Возврат; КонецЕсли; Каталог = ВыбранныеФайлы; ОбщегоНазначенияВызовСервера.СохранитьРабочийКаталог(Каталог); Если ИмяКоманды = "Накладная" Тогда НазваниеМакета = "Накладная" КонецЕсли; СтруктураДанных.Вставить("Каталог", Каталог); ПодключитьОбработчикОжидания("Подключаемый_ПередатьФайлКлиенту",5,Истина); КонецПроцедуры &НаКлиенте Процедура ОткрытьФайлыЧерезРасширение(ИмяКоманды) ОпПослеВыбораКаталога = Новый ОписаниеОповещения("ПослеВыбораКаталога", ЭтотОбъект, ИмяКоманды); Каталог = ОбщегоНазначенияВызовСервера.ПолучитьРабочийКаталог(); Если Каталог = Неопределено ИЛИ Каталог = "" Тогда Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога); Диалог.Заголовок = НСтр("ru = "Выбор каталога временного хранения файлов"", "ru"); Диалог.Показать(ОпПослеВыбораКаталога); Иначе ВыбранныеФайлы = Новый Массив; ВыбранныеФайлы.Добавить(Каталог); ВыполнитьОбработкуОповещения(ОпПослеВыбораКаталога, ВыбранныеФайлы); КонецЕсли; КонецПроцедуры &НаКлиенте Процедура ОбработатьПодключениеРасширенияРаботыСФайлами(РасширениеПодключено,ДополнительныеПараметры) Экспорт Если РасширениеПодключено Тогда ОткрытьФайлыЧерезРасширение(ДополнительныеПараметры.ИмяКоманды); КонецЕсли; КонецПроцедуры &НаКлиенте Процедура Подключаемый_ПередатьФайлКлиенту() Адрес = ПолучитьМакет(); Если Адрес<>Неопределено Тогда ОтключитьОбработчикОжидания("Подключаемый_ПередатьФайлКлиенту"); НомерДокумента = СтруктураДанных.НомерДокумента; Каталог = СтруктураДанных.Каталог; НазваниеМакета = СтруктураДанных.НазваниеМакета; ПутьКфайлу = Каталог+"\"+НазваниеМакета+"_№"+НомерДокумента+".xls"; Описание = Новый ОписаниеПередаваемогоФайла(ПутьКфайлу, Адрес); ПередаваемыеФайлы = Новый Массив; ПередаваемыеФайлы.Добавить(Описание); НачатьПолучениеФайлов(Новый ОписаниеОповещения("ПослеПолученияФайлов", ЭтотОбъект), ПередаваемыеФайлы, "", Ложь); КонецЕсли; КонецПроцедуры &НаСервере Процедура ВыполнитьПечатьСервер() ОбъектСсылка = СтруктураДанных.Объект; НазваниеМакета = СтруктураДанных.НазваниеМакета; СтруктураДанных.Вставить("НомерДокумента", ОбъектСсылка.Номер); МассивОбъектов = Новый Массив; МассивОбъектов.Добавить(ОбъектСсылка); Документы.НакладнаяОборудования.ПечатьНакладная(МассивОбъектов,НазваниеМакета,Истина); КонецПроцедуры &НаКлиенте Процедура Подключаемый_ВыполнитьПечать() ВыполнитьПечатьСервер(); КонецПроцедуры // СтандартныеПодсистемы.Печать &НаКлиенте Процедура Подключаемый_ВыполнитьКомандуПечати(Команда) Ссылка = Элементы.Список.ТекущиеДанные.Ссылка; СтруктураДанных = Новый Структура; СтруктураДанных.Вставить("Объект", Ссылка); СтруктураДанных.Вставить("НазваниеМакета", "Накладная"); ПодключитьОбработчикОжидания("Подключаемый_ВыполнитьПечать", 1, Истина); ОписаниеКоманды = УправлениеПечатьюКлиент.ОписаниеКомандыПечати(Команда.Имя,ИмяФормы); НачатьУстановкуРасширенияРаботыСФайлами(); НачатьПодключениеРасширенияРаботыСФайлами(Новый ОписаниеОповещения("ОбработатьПодключениеРасширенияРаботыСФайлами",ЭтотОбъект,Новый Структура("ИмяКоманды",ОписаниеКоманды.Идентификатор))); КонецПроцедуры // Конец СтандартныеПодсистемы.Печать

Немного пояснений:

1. Во-первых, клиент у нас работает как через Тонкий, так и через Веб режимы, поэтому заранее в свойствах Конфигуратор ставим следующие значения:

Чтобы не было проблем при работе с браузером

2. Используем обработчики ожидания, чтобы избежать проблем с синхронностью вызов (это касается только режим Веб)

3. И последнее, подключаем Расширение для работы с Файлами (помним что в режиме Тонкий клиент, это расширение включено всегда). И через код:

передаем файл Клиенту используя механизм НавигационнаяСсылка, получаем следующие сообщения в браузере (Тонкий само собой работает):

ну вот, кажется, все. Надеюсь, это поможет кому-нибудь...

По поводу Word, Excel файлы вставлять в виде ДвоичныхДанные? проблема-то в чем?

1. Мы либо должны вытащить из макета эти ДвоичныеДанные и заполнить данными из 1С и ВНИМАНИЕ снова записать в виде ДвоичныхДанных (Водка С Пивом или Пиво С Водкой)

2. Либо мы должно получить макет ДвоичныеДанные на стороне Клиента и там заполнить его, НО COM объект поддерживается только браузером IE и то с танцами с настройками ActiveX, другие браузеры давно отказались от использования ActiveX

Публикация - своего р ода памятка, содержащая примеры кода для:

1. заполнение шаблона Word данными из 1С;
2. заполнение колонтитулов Word данными из 1С;
3. заполнение таблицы в Word данными из 1С;

Начало работы

В большинстве случаев перед нами ставится следующая задача:
Нужно открыть документ Word, заполненный данными из 1С.

Для этого нам нужно подготовить шаблон документа Word. Не путайте это с Word Template, специальные файлы Word, которые содержат настройки документов для многократного использования. Нам нужен обычный вордовский документ с расширениеми *.docx или *.doc. А далее поместить этот документ в макет с двоичными данными.

Попытка //Получаем макет из двоичных данных Шаблон = ПолучитьМакет(ДополнительныеПараметры.ВидДоговора); ИмяФайла = ПолучитьИмяВременногоФайла(".docx"); Шаблон.Записать(ИмяФайла); //Создаем COM-объект для работы с Word ОбъектВорд = Новый COMОбъект("Word.Application"); ОбъектВорд.Documents.add(ИмяФайла); //В Word можно открывать в одном приложении несколько документов, поэтому ОбъектВорд.Application.Documents - это коллекция открытых документов. //В нашем случае документ открыт всегда один ДокументВорд = ОбъектВорд.Application.Documents(1); ДокументВорд.Activate(); Исключение Сообщить("Ошибка при запуске приложения "+ОписаниеОшибки()); КонецПопытки; //Разумеется, получение COM-объекта нужно поместить в попытку. Мало ли, что-то пойдет не так.

Читатель может справедливо заметить, что используется модальный вызов, и погрозить автору пальцем. И будет прав.

//Создадим структуру параметров документа ПараметрыДокумента = ПодготовитьСтрукутруПараметров(); //Заполним структуру параметров документа ЗаполнитьСтруктуруПараметров(ПараметрыДокумента);

Углубимся немножко в принципы работы Word...

Каждый документ Word разделен на разделы, которые состоят из страниц.

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

Каждая страница Word разделена на несколько областей:

  • Верхний колонтитул
  • Основной текст
  • Нижний колонтитул

Нужно заметить, что в каждом разделе может быть уникальный колонтитул для первой страницы.

//Объект, содержит весь основный текст из всех разделов ДокументВорд.Content //Объект содержит коллекцию разделов документа ДокументВорд.Sections //В каждом разделе есть своя коллекция для верхних колонтитулов ДокументВорд.Sections(1).Headers //И своя коллекция для нижних колонтитулов ДокументВорд.Sections(1).Footers //При этом, если стоит галочка "Уникальный колонтитул для первой страницы", то коллекции Headers и Footers будут содержать два элемента

Заполнение пользовательских параметров

При обращении к этим коллекциям мы можем выполнять в них поиск и получать встроенные объекты, например, таблицы.

Теперь мы более-менее поняли, как обращаться к областям Word, можем в них пошуровать и выполнить замену наших параметров:

//Переберем все параметры и заменим их в документе Для каждого Параметр Из ПараметрыДокумента Цикл ВыполнитьЗамену(ДокументВорд.Content, Параметр.Ключ, Параметр.Значение); //Ищим вхождения параметра в верхнем колонтитуле ВыполнитьЗамену(ДокументВорд.Sections(1).Headers.Item(1).Range(), Параметр.Ключ, Параметр.Значение); //Ищим вхождения параметра в нижнем колонтитуле первой и последующих страниц ВыполнитьЗамену(ДокументВорд.Sections(1).Footers.Item(1).Range(), Параметр.Ключ, Параметр.Значение); ВыполнитьЗамену(ДокументВорд.Sections(1).Footers.Item(2).Range(), Параметр.Ключ, Параметр.Значение); КонецЦикла; //Выполнить поиск и замену Функция ВыполнитьЗамену(знач Object, Параметр, Значение) Object.Find.Execute(Параметр,,Значение,2) КонецФункции

Рассмотри подробнее метод Execute. Его параметры идентичны диалоговуму окну при замене/поиске непоседресвенно из MS Word:

А вот и основные параметры (вольный перевод справки MSDN):

  1. Искомый текст - Строка - Текст для замены. Текст может содержать специальные параметры. Например, ^p - абзац, ^t - табуляция
  2. Чувствительность к регистру - Булево - Если истина, то поиск будет осуществляться с учетом регистра
  3. Слова целиком - Булево - Если истина, то ищутся слова целиком. Вхождение слов не учитываются. Например, при поиске слова дом будет пропущено слово домашний
  4. Использовать подстановочные знаки - Булево - Если истина, то используются встроенные регулярные выражения.
  5. Искать похожие - Булево - Если истина, то результат поиска будет содержать похожие слова
  6. Искать все формы - Булево - Если истина, то результат поиска будет содержать различные формы слов.
  7. Поиск сначала - Булево - Если истина, то будет осуществляться с начала до конца документа
  8. Охват - WdFindWrap - Опредяляет направление поиска
  9. Формат - Format - Формат искомого текста
  10. Строка замены - Строка - Строка, на которую будет заменен исходный текст
  11. Количество замен - WdReplace - Определяет сколько раз выполнять замену
  12. и т.д.

WdReplace - Constant Value:
wdReplaceAll 2
wdReplaceNone 0
wdReplaceOne 1

Данный метод не позволяет получить "Строка замены" как выделенную область, но он работает где-то в 10 раз медленнее. Для получения выделенной области можно воспользоваться немножко откорректированной типовой функцией:

//УправлениеПечатьюMSWordКлиент c незначительными изменениями для конфигурации УПП 1.3 Функция ВыполнитьЗамену(знач Object, Параметр, Значение) СтрокаПоиска = "[" + Параметр + "]"; СтрокаЗамены = Строка(Значение); //Необходимо выделить областей, в которой мы осуществляем замену Object.Select(); //Получаем выделенную область Selection = Object.Application.Selection; //Найдем все вхождения параметра и заменим его на нужное нам значение FindObject = Selection.Find; FindObject.ClearFormatting(); Пока FindObject.Execute(СтрокаПоиска) Цикл Если ПустаяСтрока(СтрокаЗамены) Тогда Selection.Delete(); Иначе Selection.TypeText(СтрокаЗамены); КонецЕсли; КонецЦикла; //Отменим выделение Selection.Collapse(); КонецФункции

Уже получив выделенную область можно отредактировать стиль текста, шрифт и т.д.

//Редактирование шрифта Selection.Font //Редактирование цвета Selection.HighlightColorIndex

Также есть второй подход, использующий такой объект Word, как поля. Мне он не очень нравится, т.к. в больших документах, порядка 100 страниц, эти поля начинают глючить (исчезать, не подставлять нужные значения) и прочая ерунда. Ну по крайней мере в Word 2007. Но я его все равно приведу:

При подготовке шаблона в тело документа необходимо навставлять полей с типом DOCVARIABLE (можно вставлять горячими клавишими Ctrl+F9).

Доступ к таким полям можно получить следующим нехитрым образом:

ДокументВорд.Variables.Item(НазваниеПараметра).Values

Заполнение таблиц по шаблону

Итак, мы заполнили параметры в основном тексте документа, заменили параметры в колонтитулах, но у нас еще есть одна неприятность - нужно заполнить таблицу.

Подход, описанный ниже, годится только для таблиц с заранее известным форматом. Т.е. мы можем как угодно отформатировать таблицу и её строки изначально. Но потом изменять довольно-таки проблематично.

К таблицам можно получить доступ через области документа.

//Получаем доступ к первой таблице в основном тексте Таблица = ДокументВорд.Content.Tables(1)

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

//Данные для заполнения ПараметрыТЧ = ПараметрыДокумента.ПриложениеТЧ; //Нужно оставить шапку и первую строку нетронутой Итератор = 2; Таблица = ДокументВорд.Content.Tables(3); Для каждого Строка Из ПараметрыТЧ Цикл //По умолчанию добавляет строку выше первой Таблица.Rows.Add(); ЗаполнитьСтрокуТаблицы(Таблица, Итератор, Строка,"ПП,НоменклатураНаименование,ЕдИзмерения,ЦенаСтрокой",ПараметрыТЧ); Итератор = Итератор + 1; КонецЦикла; //Структура шаблонов содержит Наименование колонок и их порядок Процедура ЗаполнитьСтрокуТаблицы(Таблица, НомерСтроки, ЗначениеЗаполнения, СтруктураШаблонов,ТаблицаЗначений) МассивСтрок = ОбщегоНазначения.РазложитьСтрокуВМассивПодстрок(СтруктураШаблонов,","); Итератор = 1; Для каждого Строка Из МассивСтрок Цикл Если ТаблицаЗначений.Колонки.Найти(Строка) = Неопределено И Строка <> "ПП" Тогда Итератор = Итератор + 1; Продолжить; //Не забываем, что Шапка тоже строка, а при нумерации нам нужно её исключить ИначеЕсли Строка = "ПП" Тогда Таблица.Cell(НомерСтроки, Итератор).Range().Text = Строка(НомерСтроки-1); Итератор = Итератор + 1; Продолжить; КонецЕсли; Таблица.Cell(НомерСтроки, Итератор).Range().Text = Строка(ЗначениеЗаполнения[Строка]); Итератор = Итератор + 1; КонецЦикла; КонецПроцедуры

Вот, в принципе, и все. Основные вопросы, возникающие при работе с Word, я постарался осветить. Надеюсь, данный обзор поможет вам в работе =)

Спасибо за советы и комментарии:
,

Критика только приветствуется. Чем больше замечаний, тем лучше будет гайд =)

Итак, в чем суть моей статьи?

1. Есть сервер (x64), с установленным Office 2016 (x64), есть 1С 8.3.8 также x64 (имеется в виду сервер 1С). Пользователи подключаются как через тонкого клиента, так и через веб клиент.

2. Есть куча типовых договоров и накладных счет-фактур, оформленных (подготовленных) отделом продаж. Там шрифт, абзац и т.д., естественно, все оформлено в Word,Excel 2016 (x64), естественно, нет желания все это рисовать в 1С, а есть желание засунуть в макеты формата ActiveDocument

Решение банальное, вроде бы :) , не буду описывать весь механизм, как это делается, как заполняются данные из 1С в макет ActiveDocument - думаю, вы это знаете прекрасно и без меня. Но в ходе работы выяснилось, что не все так гладко в царстве COM объектов, а именно:

2. Какой вариант лучше выбрать: ActiveDocument или ДвоичныеДанные? Хотя для меня это звучит примерно как, что выбрать Водку С Пивом или Пиво С Водкой :) . Один фиг, надо забить документы данными из 1С и передать их Клиенту.

Ну да ладно пропустим лирику, я выбираю ActiveDocument, не буду описывать весь алгоритм, просто перечислю "подводные" камни и их решение. Все нижеизложенное это мои личные измышления и в никакой мере не претендуют на истину в последней инстанции. Возможно, вы решили эти проблемы или решите по другому.

1. Камень "первый ". Не работает метод SaveAs (как для MSWord, так и для MSExcel). При попытке записать ДвоичныеДанные 1С просто вылетает. Смотрим фрагмент листинга:

MSWord = Макет.Получить(); Попытка Документ = MSWord.Application.Documents(1); Документ.Activate(); //Далее каким-либо образом получаем данные и заполняем Word-овский документ //Получим путь во временной директории для сохранения туда файла ИмяВрем = ПолучитьИмяВременногоФайла(".docx"); Документ.SaveAs(ИмяВрем); //здесь начинается камень МойДокументВДвоичныхДанных = Новый ДвоичныеДанные(ИмяВрем); //ну а здесь этот камень нафиг выбивает 1С:) MSWord.Application.Quit();

Спасибо огромное.

Создание папок
C:\Windows\SysWOW64\config\systemprofile\Desktop
C:\Windows\System32\config\systemprofile\Desktop
проблему решило. Тема закрыта.

В чем причина? Причина в том, что код

MSWord = Макет.Получить();

Всегда вызывает экземпляр объекта COM (x32) независимо от того какой разрядности Office установлен. Вы никогда не задумывались, почему в макет ActoveDocument нельзя вставить файлы с расширением docx,xlsx

это можно проверить и через ДиспетчерЗадач, но факт есть факт - макет ActiveDocument вызывает неявно экземпляр COM (x32) и поэтому все дальнейшие манипуляции нужно делать учитывая это особенность.

1. Либо сервер и все ПО должно быть x32. Тогда ничего делать (в смысле переписывать код) не надо

2. Либо переписать код, таким образом

// получаем имя временного файла ВремФайл = ПолучитьИмяВременногоФайла("doc"); // этот код точно вызовет экземпляр COM нужной разрядности, в нашем случае x64 Word = Новый COMОбъект("Word.Application"); Word.Displayalerts = 0; ДокументН = Word.Application.Documents.Add(); ДокументН.SaveAs(ВремФайл,0); Word.Quit(); // далее все по старому Макет = УправлениеПечатью.МакетПечатнойФормы("Документ.АктПередачиОборудования."+НазваниеМакета); MSWord = Макет.Получить(); Попытка Документ = MSWord.Application.Documents(1); Документ.Activate(); // здесь что-то делаем, заполняем данные // здесь мы пересохраним наш файл из COM x62 в COM x64 MSWord.Application.Selection.WholeStory(); MSWord.Application.Selection.Copy(); ДокументН = MSWord.Application.Documents.Open(ВремФайл); ДокументН.Activate(); MSWord.Application.Selection.Paste(); ДокументН.SaveAs(ВремФайл,0); ДокументН.Close(); MSWord = Неопределено; Исключение // Если произойдет ошибка выводятся данные об ошибке и объект закрывается. Информация = ИнформацияОбОшибке(); ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка - "+Информация.Описание+" код ошибки - "+СокрЛП(Информация.ИсходнаяСтрока)); MSWord.Application.Quit(); КонецПопытки;

Думаю, тут все понятно, сначала мы создали экземпляр COM нужной разрядности, создали пустой файл и сохранили во временную папку, далее работает с COM x32, заполняем данными и напоследок копируем содержимое всего документа и сохраняем в ранее подготовленный файл.

Все то же самое, но только для Excel

ВремФайл = ПолучитьИмяВременногоФайла("xls"); Excel = Новый COMОбъект("Excel.Application"); Excel.Displayalerts = 0; КнигаН = Excel.WorkBooks.Add(); ЛистН = КнигаН.WorkSheets(1); КнигаН.SaveAs(ВремФайл, -4143); Excel.Quit(); Макет = УправлениеПечатью.МакетПечатнойФормы("Документ.НакладнаяОборудования."+НазваниеМакета); MSExcel = Макет.Получить(); КнигаН = MSExcel.Application.Workbooks.Open(ВремФайл); ЛистН = КнигаН.WorkSheets(1); Попытка WBook = MSExcel.Application.Workbooks(1); Лист = WBook.WorkSheets(1); Лист.Activate(); // что-то делаем, заполняем данными из 1С MSExcel.Application.WorkBooks(1).WorkSheets(1).Cells.Copy(ЛистН.Cells); КнигаН.Save(); КнигаН.Close(); Исключение // Если произойдет ошибка выводятся данные об ошибке и объект закрывается. Информация = ИнформацияОбОшибке(); ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка - "+Информация.Описание+" код ошибки - "+СокрЛП(Информация.ИсходнаяСтрока)); MSExcel.Application.Quit(); КонецПопытки;

ну вот "первый " камень я решил, на сервере x64 с Office x64, все работает точно как часы, без ошибок и не надо создавать никаких папок и все прочее.

Камень "второй ". фрагмент кода

ВремФайл = ПолучитьИмяВременногоФайла("xls");

есть не очень хорошо, потому как идет запись в папку: "c:\Users\ че там....", вообще эта папка всегда в черном списке всех фаерволов, антивирусов и прочее, прочее, хотя бы открыть центр управления безопасностью Word или Excel. Глянем и мы туда

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

1. Открываем Конфигуратор и добавляем новый РегистрСведений

здесь мы будем хранить наши готовые Word, Excel файлы уже заполненные, конечно:

НазваниеМакета - Идентификатор макета

ДокументOffice - ХранилищеЗначений, здесь мы и держим наш готовый файл

2. Дописываем выше написанный код следующим образом:

МЗ = РегистрыСведений.ВременноеХранилищеOffice.СоздатьМенеджерЗаписи(); МЗ.Объект = Выборка.Ссылка; МЗ.НазваниеМакета = НазваниеМакета; МЗ.Прочитать(); МЗ.Объект = Выборка.Ссылка; МЗ.НазваниеМакета = НазваниеМакета; МЗ.ДокументOffice = Новый ХранилищеЗначения(Новый ДвоичныеДанные(ВремФайл)); МЗ.Записать(); УдалитьФайлы(ВремФайл);

Что мы делаем, мы записываем готовый файл в регистр сведений и затем удаляем сам временный файл, решаем проблему "Центра безопасности Word,Excel". Осталось только одно показать этот готовый файл Клиенту (клиент тонкий и веб)

3. Камень "третий " - передача файла клиенту, тут просто выложу весь код, что-то взял из БСП, что-то из Демонстрационная конфигурация "Управляемое приложение", что-то из Инета, но в общем вот код (целиком)

//////////////////////////////////////////////////////////////////////////////// // СЛУЖЕБНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ БСП &НаСервере Функция ПолучитьМакет() ОбъектСсылка = СтруктураДанных.Объект; НазваниеМакета = СтруктураДанных.НазваниеМакета; КлючЗаписи = РегистрыСведений.ВременноеХранилищеOffice.СоздатьКлючЗаписи(Новый Структура("Объект,НазваниеМакета",ОбъектСсылка,НазваниеМакета)); // Адрес = ПолучитьНавигационнуюСсылку(КлючЗаписи,"ДокументOffice"); Возврат Адрес; КонецФункции // &НаКлиенте Процедура ПослеЗапускаПриложения(КодВозврата, ИмяПриложения) Экспорт; // КонецПроцедуры &НаКлиенте Процедура ПослеПолученияФайлов(ПереданныеФайлы, ДополнительныеПараметры) Экспорт Если НЕ ПереданныеФайлы=Неопределено Тогда Для каждого Описание Из ПереданныеФайлы Цикл ОпПослеЗапускаПриложения = Новый ОписаниеОповещения("ПослеЗапускаПриложения", ЭтотОбъект, Описание.Имя); НачатьЗапускПриложения(ОпПослеЗапускаПриложения, Описание.Имя); КонецЦикла; КонецЕсли; КонецПроцедуры &НаКлиенте Процедура ПослеВыбораКаталога(ВыбранныеФайлы, ИмяКоманды) Экспорт Если ВыбранныеФайлы = Неопределено Тогда Возврат; КонецЕсли; Каталог = ВыбранныеФайлы; ОбщегоНазначенияВызовСервера.СохранитьРабочийКаталог(Каталог); Если ИмяКоманды = "Накладная" Тогда НазваниеМакета = "Накладная" КонецЕсли; СтруктураДанных.Вставить("Каталог", Каталог); ПодключитьОбработчикОжидания("Подключаемый_ПередатьФайлКлиенту",5,Истина); КонецПроцедуры &НаКлиенте Процедура ОткрытьФайлыЧерезРасширение(ИмяКоманды) ОпПослеВыбораКаталога = Новый ОписаниеОповещения("ПослеВыбораКаталога", ЭтотОбъект, ИмяКоманды); Каталог = ОбщегоНазначенияВызовСервера.ПолучитьРабочийКаталог(); Если Каталог = Неопределено ИЛИ Каталог = "" Тогда Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога); Диалог.Заголовок = НСтр("ru = "Выбор каталога временного хранения файлов"", "ru"); Диалог.Показать(ОпПослеВыбораКаталога); Иначе ВыбранныеФайлы = Новый Массив; ВыбранныеФайлы.Добавить(Каталог); ВыполнитьОбработкуОповещения(ОпПослеВыбораКаталога, ВыбранныеФайлы); КонецЕсли; КонецПроцедуры &НаКлиенте Процедура ОбработатьПодключениеРасширенияРаботыСФайлами(РасширениеПодключено,ДополнительныеПараметры) Экспорт Если РасширениеПодключено Тогда ОткрытьФайлыЧерезРасширение(ДополнительныеПараметры.ИмяКоманды); КонецЕсли; КонецПроцедуры &НаКлиенте Процедура Подключаемый_ПередатьФайлКлиенту() Адрес = ПолучитьМакет(); Если Адрес<>Неопределено Тогда ОтключитьОбработчикОжидания("Подключаемый_ПередатьФайлКлиенту"); НомерДокумента = СтруктураДанных.НомерДокумента; Каталог = СтруктураДанных.Каталог; НазваниеМакета = СтруктураДанных.НазваниеМакета; ПутьКфайлу = Каталог+"\"+НазваниеМакета+"_№"+НомерДокумента+".xls"; Описание = Новый ОписаниеПередаваемогоФайла(ПутьКфайлу, Адрес); ПередаваемыеФайлы = Новый Массив; ПередаваемыеФайлы.Добавить(Описание); НачатьПолучениеФайлов(Новый ОписаниеОповещения("ПослеПолученияФайлов", ЭтотОбъект), ПередаваемыеФайлы, "", Ложь); КонецЕсли; КонецПроцедуры &НаСервере Процедура ВыполнитьПечатьСервер() ОбъектСсылка = СтруктураДанных.Объект; НазваниеМакета = СтруктураДанных.НазваниеМакета; СтруктураДанных.Вставить("НомерДокумента", ОбъектСсылка.Номер); МассивОбъектов = Новый Массив; МассивОбъектов.Добавить(ОбъектСсылка); Документы.НакладнаяОборудования.ПечатьНакладная(МассивОбъектов,НазваниеМакета,Истина); КонецПроцедуры &НаКлиенте Процедура Подключаемый_ВыполнитьПечать() ВыполнитьПечатьСервер(); КонецПроцедуры // СтандартныеПодсистемы.Печать &НаКлиенте Процедура Подключаемый_ВыполнитьКомандуПечати(Команда) Ссылка = Элементы.Список.ТекущиеДанные.Ссылка; СтруктураДанных = Новый Структура; СтруктураДанных.Вставить("Объект", Ссылка); СтруктураДанных.Вставить("НазваниеМакета", "Накладная"); ПодключитьОбработчикОжидания("Подключаемый_ВыполнитьПечать", 1, Истина); ОписаниеКоманды = УправлениеПечатьюКлиент.ОписаниеКомандыПечати(Команда.Имя,ИмяФормы); НачатьУстановкуРасширенияРаботыСФайлами(); НачатьПодключениеРасширенияРаботыСФайлами(Новый ОписаниеОповещения("ОбработатьПодключениеРасширенияРаботыСФайлами",ЭтотОбъект,Новый Структура("ИмяКоманды",ОписаниеКоманды.Идентификатор))); КонецПроцедуры // Конец СтандартныеПодсистемы.Печать

Немного пояснений:

1. Во-первых, клиент у нас работает как через Тонкий, так и через Веб режимы, поэтому заранее в свойствах Конфигуратор ставим следующие значения:

Чтобы не было проблем при работе с браузером

2. Используем обработчики ожидания, чтобы избежать проблем с синхронностью вызов (это касается только режим Веб)

3. И последнее, подключаем Расширение для работы с Файлами (помним что в режиме Тонкий клиент, это расширение включено всегда). И через код:

передаем файл Клиенту используя механизм НавигационнаяСсылка, получаем следующие сообщения в браузере (Тонкий само собой работает):

ну вот, кажется, все. Надеюсь, это поможет кому-нибудь...

По поводу Word, Excel файлы вставлять в виде ДвоичныхДанные? проблема-то в чем?

1. Мы либо должны вытащить из макета эти ДвоичныеДанные и заполнить данными из 1С и ВНИМАНИЕ снова записать в виде ДвоичныхДанных (Водка С Пивом или Пиво С Водкой)

2. Либо мы должно получить макет ДвоичныеДанные на стороне Клиента и там заполнить его, НО COM объект поддерживается только браузером IE и то с танцами с настройками ActiveX, другие браузеры давно отказались от использования ActiveX

В работе программиста 1С, данный функционал требуется крайне редко, хотя благодаря нему можно решать довольно сложные задачи с динамическими и не постоянными печатными формами. При реализации данной задачи, работа по созданию шаблонов Word и расстановке закладок в шаблоне Word возлагается на пользователей 1C.

В данном примере будет показано, как используя механизмы 1С и документа Word в качестве шаблона, можно создать печатную форму для вывода данных, пользователям программы 1С.

Создадим шаблон на основе документа Word для 1С. Сначала необходимо включить показ закладок для более удобной работы с документом:


Далее необходимо в файл шаблона для 1С, добавить текст и пару закладок. Закладка добавляется через меню «Вставка» — «Закладка». Нужно выделить текст закладки, скопировать его, открыть меню «Вставка — Закладка», вставить скопированный текст закладки и нажать «Добавить»:


Когда файл шаблона Word готов, создаем новую внешнею обработку в 1С, добавляем форму и в модуле формы вставляем следующий код: &НаКлиенте Процедура ПриОткрытии(Отказ) ЗаполнитьШаблонВорд(); КонецПроцедуры &НаКлиенте Функция ЗаполнитьШаблонВорд() //Попытка создать COM объект Попытка ОбъектВорд = Новый COMОбъект("Word.Application"); Исключение Сообщить("Ошибка при запуске Microsoft Word: " + ОписаниеОшибки(), СтатусСообщения.Внимание); Возврат Неопределено; КонецПопытки; //Путь к шаблону ОбъектВорд.Documents.Add("C:\Users\Константин\Desktop\Шаблон.docx"); ШаблонВорд = ОбъектВорд.ActiveDocument; //Получаем все закладки из шаблона Для каждого ЗакладкаВорд Из ШаблонВорд.Bookmarks Цикл Сообщить(ЗакладкаВорд.Name); КонецЦикла; //Заменяем закладку1 на текст ШаблонВорд.Bookmarks("Закладка1").Select(); ШаблонВорд.Application.Selection.TypeText("Текст для закладки №1."); //Заменяем закладку2 на текст ШаблонВорд.Bookmarks("Закладка2").Select(); ШаблонВорд.Application.Selection.TypeText("Текст для закладки №2."); //Заменяем закладку3 на текущую дату ШаблонВорд.Bookmarks("Закладка3").Select(); ШаблонВорд.Application.Selection.TypeText(ТекущаяДата()); ОбъектВорд.Application.Visible = Истина; ОбъектВорд.Activate(); //Закрытие документа //ОбъектВорд.Application.Quit(); КонецФункции

В результат запуска внешней обработки 1С по формированию печатной формы на основе шаблона в виде файла Word, мы получим следующий результат:

Информацию по методам COM объекта Word, а так же дополнительные данные можно узнать из MSDN .

Дополнение к заметке Использование шаблонов Word в 1С

Выражаю благодарность, всем тем, кто комментирует запись, благодаря вам, материал дополняется новыми данными и будет полезен более широкому кругу посетителей.

1. А как поменять размер шрифта во вставляемом тексте?
Достаточно изменить размер шрифта закладки, в шаблоне. А вот, если необходимо динамически менять размерность, тогда:

//Заменяем закладку3 на текущую дату ШаблонВорд.Bookmarks("Закладка3").Select(); ШаблонВорд.Application.Selection.Font.Bold = 1; ШаблонВорд.Application.Selection.Font.Size = 20; ШаблонВорд.Application.Selection.TypeText(ТекущаяДата());

2. Картинки есть возможность выгружать в шаблон?
Да, конечно. Примеры для C# и VB описаны по ссылке: https://msdn.microsoft.com/ru-ru/library/ms178792.aspx , а для 1С код будет следующий:

// Вставка изображения ШаблонВорд.Bookmarks("Закладка3").Select(); ШаблонВорд.Application.Selection.InlineShapes.AddPicture("C:\Users\Константин\Desktop\logo_1c.png");

3. При выполнении кода: ОбъектВорд.Documents.Add(«C:/Шаблон.docx») (туда я поместил Ваш документ) получаю ошибку: «Ошибка при вызове метода контекста (Add)» в чем может быть причина?
Может попробовать изменить путь на: «C:\Шаблон.docx».

4. Подскажите пожалуйста еще почему при сохранении макета типа active document в файл word кодом:

ОбработкаОбъект = РеквизитФормыВЗначение("Объект"); Макет = ОбработкаОбъект.ПолучитьМакет("ШаблонПисьмаПросрочка"); ИмяФайла = КаталогВременныхФайлов() + "\"файл.doc"; Макет.Записать(ИмяФайла);

Я получаю вместо нормального документа вот такой http://prntscr.com/eshgjk
Предполагаю, что метод Записать(,) по умолчанию, записывает файл в формате MXL. Попробуйте вторым параметром установить значение: ТипФайлаТабличногоДокумента.DOCX, более подробно описано в «Синтакс-помощнике».

5. У меня в документе есть ссылки на файлы которые расположены на сетевом диске. Как в шаблоне сделать гиперссылку на эти файлы?
Интересный вопрос, если попробовать сделать запись макроса и посмотреть результат, то в Visual Basic добавление гиперссылки будет выглядеть следующим образом:

Sub Макрос1() " " Макрос1 Макрос " " Selection.MoveLeft Unit:=wdCharacter, Count:=15, Extend:=wdExtend ActiveDocument.Hyperlinks.Add Anchor:=Selection.Range, Address:= _ "C:\Users\Константин\Desktop\Тест.txt", SubAddress:="", ScreenTip:="", _ TextToDisplay:="ТестоваяСсылка" End Sub

Описание метода добавления в MSDN: https://msdn.microsoft.com/en-us/vba/word-vba/articles/hyperlinks-add-method-word

Соответственно код для 1С следующий:

Функция ЗаполнитьШаблонВорд() //Попытка создать COM объект Попытка ОбъектВорд = Новый COMОбъект("Word.Application"); Исключение Сообщить("Ошибка при запуске Microsoft Word: " + ОписаниеОшибки(), СтатусСообщения.Внимание); Возврат Неопределено; КонецПопытки; //Путь к шаблону ОбъектВорд.Documents.Add("C:\Users\k.blagin\Desktop\Шаблон.docx"); ШаблонВорд = ОбъектВорд.ActiveDocument; //Получаем все ссылки из шаблона Для каждого ЗакладкаВорд Из ШаблонВорд.Hyperlinks Цикл Сообщить(ЗакладкаВорд.Name); КонецЦикла; //Меняем адрес уже существующей ссылки ШаблонВорд.Hyperlinks("Logs.zip").Address = "C:\Users\k.blagin\Desktop\Memory.zip"; //Заменяем закладку на ссылку ОбъектЗакладка = ШаблонВорд.Bookmarks("Закладка3"); ШаблонВорд.Hyperlinks.Add(ОбъектЗакладка.Range, "C:\Users\k.blagin\Desktop\Шаблон.docx", "", "Шаблон.docx", "C:\Users\k.blagin\Desktop\Шаблон.docx"); ОбъектВорд.Application.Visible = Истина; ОбъектВорд.Activate(); //Закрытие документа //ОбъектВорд.Application.Quit(); КонецФункции