gifts2017

Работа с ActiveDocument в клиент-серверном варианте

Опубликовал Арсений Прялкин (CeHbKA) в раздел Программирование - Практика программирования

Наверняка, многие, хотя бы раз в своей практике сталкивались (или еще столкнуться) с этим удивительным типом объекта метаданных Макет. В данной статье, я постараюсь дать основные тезисы и методы работы с ActiveDocument, дабы Вы не наступали на эти банальные грабли и не колупали себе мозг, почему оно не работает :)

Введение. Предметная область

Макет – это один из самых универсальных объектов 1С Предприятия 8. Использование макетов широко применяется как в оформлении различных документов (отчеты, печатные формы и т.д.), так и в решении нестандартных задач (например хранение файлов внутри конфигурации).

Для чего нужен  и применяется такой прекрасный тип макета как ActiveDocument? Самый банальный пример – это шаблон договора, сделанный юристом на скорую руку в MS Word.  Тип макета ActiveDocument позволяет загрузить этот шаблон MS Word в конфигурации и осуществлять редактирование напрямую из конфигуратора.

Т.е. вот вы сделали шаблон договора, загрузили в конфигурацию, проработали с этим шаблоном 1 год, и потом вдруг выяснилось, что нужно изменить кусок текста этого договора. Без проблем – открываем конфигуратор, 2 щелчка на нужном макете и можем править word-вский файлик прямо в конфигураторе. Закрываем, обновляем конфигурацию – дело сделано!

Принцип работы.

К сожалению (или к счастью) работа с макетом ActiveDocument возможна только программная. Т.е. чтобы при нажатии кнопки пользователю открылся наш word-вский файлик, нужно написать код! (ВАУ)

Сама схема кода следующая:

1 – Получаем макет ActiveDocument

2 – Инициализируем COM-объект нашего ActiveDocument

3 – Работаем с этим COM-объектом (заполняем данные, редактируем, выводим на экран)

И вот тут начинается самое интересное…

Когнитивный диссонанс

	&НаСервере

	АктивныйДокумент = ПолучитьМакет("Макет");
	КомОбъект = АктивныйДокумент.Получить();	

Вот самые первые строчки, на которые натыкается в гугле юный неофит. В файловом варианте такой код сработает корректно (неважно в толстом или тонком клиенте). Т.к. и сервер и клиент у нас находятся на одной машине, и MS Word тоже установлен на этой машине.

А давайте представим, что у нас клиент-сервер, да ещё и сервер находится на другой машине. Будет оно работать в тонком клиенте? Нет. И тут возникнет когнитивный диссонанс.  WTF? O_o

Во-первых, получить макет (любой) можно только &На Сервере. Результатом нашего получения будет объект «Оболочка ActiveDocument». «Фигня!» - скажет опытный гуру конфигурирования – «вернём &НаКлиент, как и в случае с табличным документом!»

Не фигня. Оболочка ActiveDocument существует только на сервере и вернуть её на клиент не получится (а нам-то нужно запустить Word именно на клиентской машине).

Во-вторых, инициализировав COM-объект &НаСервере, вернуть его на клиент, также не получится

(и кстати, если сделать наоборот - с клиента на сервер, тоже не прокатит)

Танцы с бубном

Как быть? Ведь нужно инициализировать COM-объект на клиенте, причем из данных, получаемых только на сервере и которых на клиент передать нельзя.

Решение у этой задачи самое корявое, которое только может быть. Но давайте вспомним, зачем мы вообще используем ActiveDocument? Правильно, чтобы внести изменения в шаблон через конфигуратор «на лету».  Это и есть та единственная причина, по которой мы используем ActiveDocument. Иначе бы мы использовали тип макета «Двоичные данные».

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

Таким образом наша предыдущая схема превращается вот во что:

1 – &НаСервере Получаем макет ActiveDocument

2 – &НаСервере Сохраняем полученный макет в файл и возвращаем на клиент полное имя файла (полный путь + наименование с расширением)

3 – &НаКлиенте Инициализируем COM-объект из файла

4 -  &НаКлиенте заполняем и выводим этот COM-объект

Листинг

Небольшой листинг кода:

&НаКлиенте
Процедура ПоказатьМакет(Документ)	
	
	Макет = ОбщийМодуль.ПолучитьМакет(Документ); //серверная функция, записывает макет в файл и возвращает полное имя файла

	Попытка 
		Шаблон = ПолучитьCOMОбъект(Макет); //инициализируем COM-объект
	Исключение
		КомОбъект = 0;
		Сообщение = Новый СообщениеПользователю;
		Сообщение.Текст = ОписаниеОшибки();
		Сообщение.Сообщить(); 
		
		Возврат;
	КонецПопытки;  

	Если Шаблон = Неопределено Тогда
		Возврат;
	КонецЕсли;

	//здесь делаем всякие штуки типа заполнения и прочего

	Шаблон.Fields.UpDate();
	Шаблон.Activate();
	Шаблон.Application.Visible = 1; //указываем, что надо показать юзеру наш COM-объект
	Шаблон.Save(); //записываем изменения

	Шаблон = 0;

КонецПроцедуры

//Получает макет ActiveDocument и записывает его в файл
&НаСервере
Функция ПолучитьМакет(Документ) Экспорт
	
	Каталог = ПараметрыСеанса.ТекущийПользователь.РабочийКаталог;
	Каталог = ?(Прав(Каталог,1) = "\", Каталог, Каталог+"\");
	Если ЗначениеЗаполнено(Каталог) Тогда
		Попытка
			ПолноеИмяФайла = Каталог+Документ.Номер+".doc";
			Макет = Документы.ДоговорыКонтрагентов.ПолучитьМакет("ActiveDocument");
			Макет.Записать(ПолноеИмяФайла);
			
			Возврат ПолноеИмяФайла;                       
		Исключение
			Сообщение = Новый СообщениеПользователю;
			Сообщение.Текст = "Не удалось записать файл "+ОписаниеОшибки();       
			Сообщение.Сообщить();
			Возврат Неопределено;           
		КонецПопытки;            
	Иначе
		Сообщение = Новый СообщениеПользователю;
		Сообщение.Текст = "Не указан каталог. Сохранение файла невозможно";
		Сообщение.Сообщить();
		Возврат Неопределено;
	КонецЕсли;    
	
КонецФункции // ()

Заключение

Естественно, пример с MS Word самый простой и банальный. Вы можете запихнуть туда и другие файлики).

Надеюсь статья кому-то сэкономит 10мин :) Удачи! ;)  

P.S. кстати, обратите внимание, какая изящная задача получилась для тестирования опытного кандидата на работу. Если вам нужен человек, для интеграции 1С с другими ПО, да еще и чтоб в Управляемых формах шарил - вы легко сможете это определить.

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Андрей К. (andrei.k) 02.04.14 08:14
Спасибо за статью, но ничего нового тут нет. Если Вы испытываете трудности с получением макета - храните вордовский шаблон файлом на диске (в общей шаре конечно) и все сведется к получению кома файла НаКлиенте. Возможно есть случаи, когда необходимо хранить шаблон вордовского файла в конфигурации, но на мой взгляд оперативно изменять текстовку шаблона (особенно если это договор и юристы никак не могут договориться о содержании шаблона :)) без влезания в конфигуратор проще и быстрее.
2. ффф ыыы (zqzq) 02.04.14 08:23
P.S. кстати, обратите, какая изящная задача получилась для тестирования опытного кандидата на работу.
На собеседовании спрашивают то, что сами недано узнали (с)баш

А если по теме, с какой стати у клиента есть доступ к файловой системе сервера и наоборот (и расшаренные папки тоже неуниверсально)? Лучше через двоичные данные и ХранилищеЗначения передавать, см. работу с картинками, а на клиенте сохранять во временный файл.

А так полезная статья, узнал зачем нужен ActiveDocument :)

(1) andrei.k, согласен, пусть лучше юзеры свой шаблон правят, не программистское это дело.
klinval; FractalizeR; CeHbKA; +3 Ответить
3. Арсений Прялкин (CeHbKA) 02.04.14 09:19
Друзья, я с Вами полностью согласен. Но файла в общей шаре может и не быть, а может у него имя изменили, а может шара недоступна, а может там что-то изменили без нашего ведома и заполнение параметров этого файла не получится сделать корректно.
Слишком уж тут много неявных причин.

По сути да - не очень понятно на кой черт такие танцы с бубном, ведь программная работа с ActiveDocument ничем не отличается от ДвоичныхДанных или ХранилищаЗначения. Принцип тот же.

Но все-таки, пусть будет - для галочки ;)
4. Юрий Сиголаев (YurySigolaev) 02.04.14 11:25
Макет = ОбщийМодуль.ПолучитьМакет(Документ); //серверная функция, записывает макет в файл и возвращает полное имя файла

разъясните

если серверная функция записывает макет в файл, то где этот файл будет лежать?
если на сервере, то как к нему получает доступ клиент в этом коде?
Шаблон = ПолучитьCOMОбъект(Макет); //инициализируем COM-объект


если на клиенте, то как серверная функция получит доступ к файловой системе клиента?
и как она передаст на клиента этот самый файл?

в общем, нужно привести листинг этой серверной функции, а также параметры модуля "ОбщийМодуль"
5. Арсений Прялкин (CeHbKA) 02.04.14 11:43
(4) YurySigolaev, у нас настроена общая шара, которая доступна для клиента и для сервера, по той простой причине, что все юзеры находятся в домене и не могут самостоятельно сохранять файл на локальную машину, с которой работают в данный момент (т.к. каждый раз они могут садиться за разные компьютеры)

В остальных случаях уже написали как это делать :)
6. Юленька (s_uu) 21.05.14 00:52
А можно для тупых полный листинг привести?))
7. misha pripa (Dighty) 04.06.14 15:48
Повторюсь
А можно для тупых полный листинг привести?))
8. Арсений Прялкин (CeHbKA) 04.06.14 15:51
(6) s_uu, (7) Dighty,
какой именно листинг нужно привести?
В статье ведь есть листинг
9. misha pripa (Dighty) 04.06.14 16:15
Листинг &НаСервере если можно.
10. misha pripa (Dighty) 04.06.14 16:19
Или помогите я разобраться не могу. Мне надо документ activedocument на тонкий клиент вывести, сижу 3 день мозг ломаю. Пожалуйста помогите.
11. Арсений Прялкин (CeHbKA) 04.06.14 17:06
(10) Dighty, через двоичные данные. Читайте 2-й пост
12. Валерий К (klinval) 16.09.15 17:59
Каким методом вы записывали файл Word на сервере? У меня на файловой метод SaveAs(ПУТЬ) работает, а на сервере нет. Путь доступен соответственно и там и тут, только на сервере почему то метод SaveAs не обнаружен... Метод Save работает только для ранее сохраненных, а у меня файл вновь созданный.
/////Уточнение от 17.09.15
Метод SaveAs он не пишет, что не обнаружен, а пишет "Произошла исключительная ситуация (Microsoft Word): Ошибка команды"
13. Арсений Прялкин (CeHbKA) 18.09.15 11:36
(12) klinval, a word на сервере установлен?
14. Валерий К (klinval) 18.09.15 12:00
(13) CeHbKA, да, установлен. Спотыкается именно на SaveAs. Макет получается(ПолучитьМакет),
 MSWord = Макет.Получить(); Документ = MSWord.Application.Documents(1); Документ.Activate();
проходят успешно. Заполнение тоже проходит, а на Документ.SaveAs(ИмяВрем); спотыкается.
Ощущение, что что-то не так с word-ом именно под пользователем под которым работает служба 1С, хотя я под ним заходил и офис нормально запускается!
Чтобы не дебажить весь код, например можно попробовать написать (см. обработка во вложении):
&НаКлиенте
Процедура СоздатьТестWord(Команда)
	СоздатьТестWordНаСервере();
КонецПроцедуры

&НаСервере
Процедура СоздатьТестWordНаСервере()
	Word = Новый COMОбъект("Word.Application");
	Word.Visible=1;
	Документ = Word.Documents.Add();
	ИмяВрем = ПолучитьИмяВременногоФайла(".docx");
	Документ.SaveAs(ИмяВрем);
	Сообщить(ИмяВрем);
КонецПроцедуры
...Показать Скрыть

На файловой базе работает, на SQL не работает (спотыкается на SaveAs). Можете попробовать у себя? Если эта простенькая обработка у вас заработает, значит у нас что-то не так с word-ом на сервере под этим Windows-пользователем.

Я кстати задачу решил через двоичные данные, но всё равно интересно почему не работает SaveAs на сервере!
Прикрепленные файлы:
СоздатьТестWord.epf
15. Арсений Прялкин (CeHbKA) 18.09.15 12:51
(14) klinval, проверьте права доступа для пользователя, word и 1С на стороне сервера
Скорей всего проблема в этом. Можете попробовать залогиниться на сервак под юзером, под которым запускается 1С сервер и проверить, даёт ли там сохранить файл интерактивно
16. Валерий К (klinval) 18.09.15 13:04
(15) CeHbKA, я уже ранее проводил такую проверку: залогинился под пользователем и сохранил word интерактивно. Клиентской 1С на сервере до этого вообще не стояло, я поставил. Запустился, а там ошибка(см. приложение). Возможно в этом и косяк.
У вас то обработка (во вложении к 14 сообщению) работает на SQL базе?
Прикрепленные файлы:
17. Арсений Прялкин (CeHbKA) 18.09.15 16:15
(16) klinval, конкретно в данный момент, нет возможности проверить вашу обработку

Добавьте 1С в исключения в свойствах обозревателя IE
18. Валерий К (klinval) 25.09.15 09:23
(17) CeHbKA, отключил усиленную безопасность IE, поставил на самый "небезопасный" уровень, ошибка, указанная в 16 сообщении ушла, но метод SaveAS всё равно не работает.
Так всё таки под кодом:
 Макет = ОбщийМодуль.ПолучитьМакет(Документ); //серверная функция, записывает макет в файл и возвращает полное имя файла

Каким методом ты записываешь? Save, SaveAS?
19. Арсений Прялкин (CeHbKA) 25.09.15 09:42
(18) klinval, посмотрите на дату публикации. Доступа к той обработке у меня давно нет, а в памяти я такое держать не могу :)

Что значит
метод SaveAS всё равно не работает.

?

Ошибка выпадает? Файл не сохраняет?
20. Валерий К (klinval) 25.09.15 11:27
(19) CeHbKA,
Ошибка выпадает? Файл не сохраняет?

Да. Да:)
По-любому косяк где-то на уровне прав Windows-пользователя (т.е. связано с администрированием серверов, а не с 1С). Даже обработка с сообщения 14 не работает (хотя где там можно ошибиться?).
{Форма.Форма.Форма(13)}: Ошибка при вызове метода контекста (SaveAs)
Документ.SaveAs(ИмяВрем);
по причине:
Произошла исключительная ситуация (Microsoft Word): Ошибка команды

Поэтому мне проще было не разбираться, а запихнуть шаблон не в ActiveDocument, а в двоичные данные, которые я могу спокойно передать на клиент. Что я и сделал... А ошибка с SaveAs, скорее всего так и останется неразгаданной.
21. Арсений Прялкин (CeHbKA) 25.09.15 11:45
(20) klinval, вам нужно запуститься непосредственно по тем пользователем, который указан для 1С на сервере и именно под ним попробовать сохранить MS Word интерактивно
22. Валерий К (klinval) 25.09.15 11:59
(21) CeHbKA, уже пробовал. Сохраняет... как ни странно.
23. Арсений Прялкин (CeHbKA) 25.09.15 12:07
(22) klinval, тогда нужно ловить ошибку, которую возвращает приложение

подключитесь к MS Word из 1С через COM на сервере и попытайтесь сохранить файл методом SaveAs
24. Валерий К (klinval) 25.09.15 13:59
(23) CeHbKA, ну так это я и сделал! Ошибка уже давно выявлена и описание её ни о чём не говорит. Файл в 14 сообщении, ошибка от его исполнения в 20 сообщении.
{Форма.Форма.Форма(13)}: Ошибка при вызове метода контекста (SaveAs)
Документ.SaveAs(ИмяВрем);
по причине:
Произошла исключительная ситуация (Microsoft Word): Ошибка команды

Если дать заведомо рабочий путь вручную: та-же ошибка (не важно сетевой или на диск C:)
Если дать заведомо некорректный путь, то уже другая ошибки:
{Форма.Форма.Форма(16)}: Ошибка при вызове метода контекста (SaveAs)
Документ.SaveAs("Ы:\111.docx");
по причине:
Произошла исключительная ситуация (Microsoft Word): Неверно указано имя файла.
Попробуйте выполнить следующие действия:
* Убедитесь, что путь введен правильно.
* Выберите нужный файл из списка файлов и папок.

Пробовал эксперементировать с расширением файла:
	Для Инд = 0 По 100 Цикл
		Попытка
			Word = Новый COMОбъект("Word.Application");
			Word.Visible=1;
			Документ = Word.Documents.Add();
			Документ.SaveAs("C:\111.docx",Инд);
			Сообщить("ПОЛУЧИЛОСЬ:"+Инд);
		Исключение
			Сообщение = Новый СообщениеПользователю();
			Сообщение.Текст = Строка(Инд)+"!"+ОписаниеОшибки();
			Сообщение.Сообщить();
		КонецПопытки;
	КонецЦикла;	
...Показать Скрыть

файл так и не создался.
Сверил все настройки Word локальные и на сервере (запустившись от имени пользователя под которым работает служба 1С). Процесс ворда запускается точно под тем пользователем под которым запускается служба (проверил в диспетчере задач)...
25. Арсений Прялкин (CeHbKA) 25.09.15 14:20
(24) klinval, вам нужна ошибка именно MS Word
{Форма.Форма.Форма(13)}: Ошибка при вызове метода контекста (SaveAs)
Документ.SaveAs(ИмяВрем);
по причине:
Произошла исключительная ситуация (Microsoft Word): Ошибка команды

Это листинг ошибки 1С. Обработайте через 1С, сообщение об ошибке MS Word и выведите её содержимое
26. Валерий К (klinval) 25.09.15 14:22
(25) CeHbKA,
Обработайте через 1С, сообщение об ошибке MS Word и выведите её содержимое

Не понял, как это сделать?
27. Арсений Прялкин (CeHbKA) 25.09.15 14:54
28. Валерий К (klinval) 25.09.15 15:34
(27) CeHbKA, правильно я понял: ты хочешь увидеть ошибку как её видит Word, а не как её нам даёт 1С?
Если да, то опять вопрос: как из 1С это можно вывести?
29. Арсений Прялкин (CeHbKA) 25.09.15 15:50
(28) klinval, читай про COM-объекты и инструментарий по MS Word
30. Валерий К (klinval) 25.09.15 16:00
(29) CeHbKA, что искать/читать и где? Вы ответьте хотя бы на:
правильно я понял: ты хочешь увидеть ошибку как её видит Word, а не как её нам даёт 1С?
31. Арсений Прялкин (CeHbKA) 25.09.15 16:16
(30) klinval, искать\читать:
- работа с COM объектами в 1С
- интеграция MS Word и 1С

где:
- в гугле

Вы ответьте хотя бы на:

1С вообще никакой ошибки не видит, она тупо посредник между пользователем и приложением (MS Word)
32. Валерий К (klinval) 25.09.15 16:35
(31) CeHbKA, это реально можно вывести ошибку в 1С как её видит Word или ты чисто теоретически предполагаешь, что такой метод есть?
Может я тебя не правильно понял: по тексту мне показалось, что ты уверен, что такая возможность (такой метод) есть, а может ты имел ввиду, что чисто теоретически такая возможность должна быть.
33. Ivan Antonov (ivant) 17.03.16 08:41
(32) Столкнулся с аналогичной проблемой - хотел узнать, удалось ли ее в итоге победить или забили?
Ошибка таже:
Ошибка при вызове метода контекста (SaveAs)
34. Валерий К (klinval) 17.03.16 09:39
(33) ivant, вроде как CeHbKA утверждал, что можно считать ошибку как то по-другому и увидеть причину косяков. Я сильно "копнул" в этом направлении, но ничего не нашёл. Похоже это совет из разряда гипотетических, что мол возможно считать ошибку Word как то по другому, но я не знаю как, но точно можно... Можете как советовал CeHbKA копнуть в эту сторону, может я что-то не увидел!

Хотя если честно ошибку я как-то исправил. Но вот не помню как, но явно не по этому совету. Моя обработка в (14) сообщении теперь у меня работает на серверной базе. Возможно ошибка просто ушла после обновления платформы.

Ещё есть вариант перейти с ActiveDocument на двоичные данные, как я в начале и сделал. Можете посмотреть тут код как это делается. Если вкратце:
//На клиенте получаем макет и заполняем его предварительно полученными данными
ДвоичныеДанныеМакета = ПолучитьИзВременногоХранилища(ПолучитьМакетСКлиента("ПисьмоНаОплатуWord"));
            ИмяВрем = ПолучитьИмяВременногоФайла(".docx");
            ДвоичныеДанныеМакета.Записать(ИмяВрем);
            Попытка
                Документ = ПолучитьCOMОбъект(ИмяВрем); 
                Для Каждого Параметр из СтруктураПараметров.Значение Цикл
                    //В оригинале тут другой код, но для простаты пример:
                    Selection = Документ.Content;
                    Selection.Find.Execute("["+Параметр.Ключ+"]",Ложь,Истина,Ложь,,,Истина,,Ложь,Параметр.Значение,2);
                КонецЦикла;
                Документ.Application.Visible = Истина;
                Документ.Application.WindowState = 2;
                Документ.Application.WindowState = 1;
                Документ.Activate();
            Исключение
.....

&НаСервереБезКонтекста
Функция ПолучитьМакетСКлиента(Имя)
    Возврат ПоместитьВоВременноеХранилище(Документы.ПисьмоНаОплату.ПолучитьМакет("ПисьмоНаОплатуWordДвоичныеДанные"));
КонецФункции
...Показать Скрыть
35. Арсений Прялкин (CeHbKA) 19.03.16 11:40
(32) klinval, (33) ivant, (34) klinval,
вы получаете COM-объект MS Word
соответственно работаете с API данного com-объекта (т.е. Word)
функция SaveAS является функцией API Word
значит, чтобы получить ошибку Word нужно воспользоваться API этого самого Word
37. Арсений Прялкин (CeHbKA) 10.08.16 10:20