На мысль натолкнуло обсуждение на местном форуме, где человек задался вопросом, а можно ли редактировать макет внешней печатной формы в пользовательском режиме. Решил поковыряться поглубже.
Ведь действительно, в современных конфигурациях - очень удобно, раз и подрисовал какую нибудь мелочь, прямо в пользовательском режиме через меню администрирование-печатные формы, отчеты и обработки-макеты печатных форм.
Но этот механизм работает только для объектов из самой конфигурации. А как нам его использовать для своих внешних отчетов и обработок ? И можно ли ? Начинаем смотреть.
Сами пользовательские макеты у нас хранятся в регистре сведений - ПользовательскиеМакетыПечати
Регистр независимый, непериодический. Измерения имеют строковый тип, сам макет - тип "хранилище значений".
Все красиво, все прекрасно. На данном этапе нет никаких причин и ограничений для использования его по своему назначению.
Посмотрим на форму списка регистра, называется он "МакетыПечатныхФорм".
&НаСервере
Процедура ЗаполнитьТаблицуМакетовПечатныхФорм()
КоллекцииОбъектовМетаданных = Новый Массив;
КоллекцииОбъектовМетаданных.Добавить(Метаданные.Справочники);
КоллекцииОбъектовМетаданных.Добавить(Метаданные.Документы);
КоллекцииОбъектовМетаданных.Добавить(Метаданные.Обработки);
КоллекцииОбъектовМетаданных.Добавить(Метаданные.БизнесПроцессы);
КоллекцииОбъектовМетаданных.Добавить(Метаданные.Задачи);
КоллекцииОбъектовМетаданных.Добавить(Метаданные.ЖурналыДокументов);
КоллекцииОбъектовМетаданных.Добавить(Метаданные.Отчеты);
Для Каждого КоллекцияОбъектовМетаданных Из КоллекцииОбъектовМетаданных Цикл
Для Каждого ОбъектМетаданныхКоллекции Из КоллекцияОбъектовМетаданных Цикл
Для Каждого ОбъектМетаданныхМакет Из ОбъектМетаданныхКоллекции.Макеты Цикл
ТипМакета = ТипМакета(ОбъектМетаданныхМакет.Имя);
Если ТипМакета = Неопределено Тогда
Продолжить;
КонецЕсли;
Если ОбщегоНазначения.ОбъектМетаданныхДоступенПоФункциональнымОпциям(ОбъектМетаданныхКоллекции) Тогда
ДобавитьОписаниеМакета(ОбъектМетаданныхКоллекции.ПолноеИмя() + "." + ОбъектМетаданныхМакет.Имя, ОбъектМетаданныхМакет.Синоним, ОбъектМетаданныхКоллекции.Синоним, ТипМакета);
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
Для Каждого ОбъектМетаданныхМакет Из Метаданные.ОбщиеМакеты Цикл
ТипМакета = ТипМакета(ОбъектМетаданныхМакет.Имя);
Если ТипМакета = Неопределено Тогда
Продолжить;
КонецЕсли;
ДобавитьОписаниеМакета("ОбщийМакет." + ОбъектМетаданныхМакет.Имя, ОбъектМетаданныхМакет.Синоним, НСтр("ru = 'Общий макет'"), ТипМакета);
КонецЦикла;
МакетыПечатныхФорм.Сортировать("ПредставлениеМакета Возр");
УстановитьФлагиИспользованияИзмененныхМакетов();
КонецПроцедуры
&НаСервере
Функция ТипМакета(ИмяОбъектаМетаданныхМакета)
ТипыМакетов = Новый Массив;
ТипыМакетов.Добавить("MXL");
ТипыМакетов.Добавить("DOC");
ТипыМакетов.Добавить("ODT");
Для Каждого ТипМакета Из ТипыМакетов Цикл
Позиция = СтрНайти(ИмяОбъектаМетаданныхМакета, "ПФ_" + ТипМакета);
Если Позиция > 0 Тогда
Возврат ТипМакета;
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Видим, что в список попадут исключительно печатные формы по объектам метаданных, да еще и с формализованными именами вида "ПФ_MXL" "ПФ_DOC" "ПФ_ODT".
Значит, на стандартной форме мы никак не увидим нашего макета. Но это все равно делу не мешает.
Зайдя в любой менеджер объекта, мы увидим, что печатная форма стандартно вызывается через общий модуль.
Макет = УправлениеПечатью.МакетПечатнойФормы(ИмяОбъектаМетаданных);
Это экспортная процедура
Функция МакетПечатнойФормы(ПолныйПутьКМакету) Экспорт
ЧастиПути = СтрЗаменить(ПолныйПутьКМакету, ".", Символы.ПС);
Если СтрЧислоСтрок(ЧастиПути) = 3 Тогда
ПутьКМетаданным = СтрПолучитьСтроку(ЧастиПути, 1) + "." + СтрПолучитьСтроку(ЧастиПути, 2);
ПутьКОбъектуМетаданных = СтрПолучитьСтроку(ЧастиПути, 3);
ИначеЕсли СтрЧислоСтрок(ЧастиПути) = 2 Тогда
ПутьКМетаданным = СтрПолучитьСтроку(ЧастиПути, 1);
ПутьКОбъектуМетаданных = СтрПолучитьСтроку(ЧастиПути, 2);
Иначе
ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр("ru = 'Макет ""%1"" не найден. Операция прервана.'"), ПолныйПутьКМакету);
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Макет КАК Макет, Использование КАК Использование
|ИЗ
| РегистрСведений.ПользовательскиеМакетыПечати
|ГДЕ
| Объект=&Объект
| И ИмяМакета=&ИмяМакета
| И Использование";
Запрос.Параметры.Вставить("Объект", ПутьКМетаданным);
Запрос.Параметры.Вставить("ИмяМакета", ПутьКОбъектуМетаданных);
УстановитьОтключениеБезопасногоРежима(Истина);
УстановитьПривилегированныйРежим(Истина);
Выборка = Запрос.Выполнить().Выбрать();
УстановитьПривилегированныйРежим(Ложь);
УстановитьОтключениеБезопасногоРежима(Ложь);
Результат = Неопределено;
Если Выборка.Следующий() Тогда
Результат = Выборка.Макет.Получить();
Иначе
Если СтрЧислоСтрок(ЧастиПути) = 3 Тогда
Результат = ОбщегоНазначения.МенеджерОбъектаПоПолномуИмени(ПутьКМетаданным).ПолучитьМакет(ПутьКОбъектуМетаданных);
Иначе
Результат = ПолучитьОбщийМакет(ПутьКОбъектуМетаданных);
КонецЕсли;
КонецЕсли;
Если Результат = Неопределено Тогда
ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр("ru = 'Макет ""%1"" не найден. Операция прервана.'"), ПолныйПутьКМакету);
КонецЕсли;
Возврат Результат;
КонецФункции
По процедуре мы видим, что сначала разбивается путь к объекту метаданных, и из регистра пытается получить макет по ключевым параметрам, ИмяМакета и Объект.
Если найдет - то сразу возвращает, содержимое хранилища. А вот если нет, то через менеджер объекта по полному имени получает макет из конфигурации.
Соответственно для внешней обработки, если ничего в хранилище не будет, она неизбежно упадет в исключение.
Ну и ладно, значит так и обработаем.
Осталось понять где же сам редактор.
Сам редактор - это общая форма "РедактированиеТабличногоДокумента". Ее только нужно открыть с нужными нам параметрами, а при закрытии она вызывает оповещение формы родителя с ОпределеннымиСобытиями.
Проанализировав код программы, находим нужные нам методы.
Открыть редактор, делаем кнопку в самой нашей обработке
&НаКлиенте
Процедура РедактироватьМакет(Команда)
ПараметрыОткрытия = Новый Структура;
ПараметрыОткрытия.Вставить("ИмяОбъектаМетаданныхМакета", ИмяОбъектаМетаданных); //Полный путь
ПараметрыОткрытия.Вставить("ТипМакета", "MXL");
ПараметрыОткрытия.Вставить("ТабличныйДокумент", ПолучениеМакета()); //Тут должен быть макет
ПараметрыОткрытия.Вставить("ИмяДокумента", "Внешняя обработка Редактирование печатных форм");
ПараметрыОткрытия.Вставить("Редактирование", Истина);
ОткрытьФорму("ОбщаяФорма.РедактированиеТабличногоДокумента", ПараметрыОткрытия, ЭтотОбъект);
КонецПроцедуры
оповещение возвращает нам параметр, отредактированный макет
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "Запись_ТабличныйДокумент" И Источник.ВладелецФормы = ЭтотОбъект Тогда
Макет = Параметр.ТабличныйДокумент;
АдресМакетаВоВременномХранилище = ПоместитьВоВременноеХранилище(Макет);
ЗаписатьМакет(Параметр.ИмяОбъектаМетаданныхМакета, АдресМакетаВоВременномХранилище);
КонецЕсли;
КонецПроцедуры
Как осуществляется запись макета подсмотрим в форме регистра
&НаСервереБезКонтекста
Процедура ЗаписатьМакет(ИмяОбъектаМетаданныхМакета, АдресМакетаВоВременномХранилище)
УправлениеПечатью.ЗаписатьМакет(ИмяОбъектаМетаданныхМакета, АдресМакетаВоВременномХранилище);
КонецПроцедуры
Т.к. из администривания нам наш макет не будет виден, то позаботимся и о том, чтобы этот макет можно было удалить из регистра.
Тоже сделаем кнопочку на форме нашей обработки.
&НаКлиенте
Процедура УдалитьМакет(Команда)
УдаляемыеМакеты = Новый Массив;
УдаляемыеМакеты.Добавить(ИмяОбъектаМетаданных);
УдалитьИзмененныеМакеты(УдаляемыеМакеты);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура УдалитьИзмененныеМакеты(УдаляемыеМакеты)
Для Каждого ИмяОбъектаМетаданныхМакета Из УдаляемыеМакеты Цикл
ЧастиИмени = СтрРазделить(ИмяОбъектаМетаданныхМакета, ".");
ИмяМакета = ЧастиИмени[ЧастиИмени.ВГраница()];
ИмяВладельца = "";
Для НомерЧасти = 0 По ЧастиИмени.ВГраница()-1 Цикл
Если Не ПустаяСтрока(ИмяВладельца) Тогда
ИмяВладельца = ИмяВладельца + ".";
КонецЕсли;
ИмяВладельца = ИмяВладельца + ЧастиИмени[НомерЧасти];
КонецЦикла;
МенеджерЗаписи = РегистрыСведений.ПользовательскиеМакетыПечати.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = ИмяВладельца;
МенеджерЗаписи.ИмяМакета = ИмяМакета;
МенеджерЗаписи.Удалить();
КонецЦикла;
КонецПроцедуры
ну и так, как у нас стандартная процедура в случае отсутствия пользовательского макета вываливает исключение, то мы используем ее частично, и будем не напрямую обращатся, а через свою процедурку.
&НаСервере
Функция ПолучениеМакета()
Попытка
Макет = УправлениеПечатью.МакетПечатнойФормы(ИмяОбъектаМетаданных);
Элементы.ФормаУдалитьМакет.Видимость=Истина;
исключение
Макет = РеквизитФормыВЗначение("Объект").ПолучитьМакет("ПФ_MXL_МакетВПФ");
Элементы.ФормаУдалитьМакет.Видимость=Ложь;
КонецПопытки;
Возврат Макет;
КонецФункции
Заодно будем устанавливать видимость кнопки "удалить", если макет там уже есть.
Ну и выведем наш тестовый пример печатной формы
&НаСервере
Функция СформироватьПФНаСервере()
ТабДок = Новый ТабличныйДокумент;
Макет = ПолучениеМакета();
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьСтрокаШапка = Макет.ПолучитьОбласть("СтрокаШапка");
ОбластьСтрДанные = Макет.ПолучитьОбласть("СтрДанные");
ТабДок.Вывести(ОбластьШапка);
ТабДок.Вывести(ОбластьСтрокаШапка);
Для Каждого Стр из Объект.Товары Цикл
ОбластьСтрДанные.Параметры.Заполнить(стр);
ТабДок.Вывести(ОбластьСтрДанные);
КонецЦикла;
Возврат ТабДок;
КонецФункции
Собственно все работает.
Получаем обработку, с двумя дополнительными кнопочками - редактирование и удаление макета. И макет у нас при этом, вполне себе меняется в пользовательском режиме. Что нам и требовалось.
Если вдруг сильно хочется, посмотреть содержимое регистра, то мы его сможем увидеть только запросом, в консольке.
Для наглядности, если не очень понятно, прикладываю эту родившуюся обработку.
Эксприментировал на типовой 1С Бухгалтерии 3.0.52.42.