При разработке собственных конфигураций или доработке существующих на платформе «1С:Предприятие 8» программисты часто сталкиваются с классической архитектурной задачей: как правильно организовать печать ценников или этикеток. Типичная ошибка начинающих разработчиков заключается в попытке создать и заполнить объект ТаблицаЗначений на тонком клиенте, а затем передать его в модуль менеджера или модуль объекта для печати. В результате система возвращает ошибку, так как данный тип данных недоступен на клиенте.
В этой статье мы подробно разберем, почему возникает эта проблема, рассмотрим несколько эффективных способов передачи данных с клиента на сервер, а также научимся оптимизировать вывод ценников на лист, чтобы они печатались красивой сеткой, экономя бумагу.
Для начала давайте проанализируем ситуацию. Нам необходимо взять данные из табличной части формы (например, список номенклатуры и цен), передать их в модуль менеджера обработки и сформировать один общий ТабличныйДокумент для вывода на экран. Попытка инициализировать таблицу значений на клиенте выглядит следующим образом:
&НаКлиенте
Процедура Печать(Команда)
ТаблицаДанных = Новый ТаблицаЗначений; // Здесь произойдет ошибка!
// Заполнение таблицы...
КонецПроцедуры
Объект ТаблицаЗначений является исключительно серверным типом данных. Тонкий клиент ничего не знает о его существовании, структуре колонок и методах. Платформа 1С строго разграничивает контексты выполнения кода ради повышения производительности и безопасности. Поэтому передавать напрямую таблицу значений между клиентом и сервером в исходном виде нельзя.
Кроме того, если запускать печать для каждой позиции номенклатуры в цикле прямо из клиентского кода, то для 10 товаров программа откроет 10 отдельных окон с печатными формами. Это крайне неудобно для пользователя. Наша задача — собрать все данные, передать их на сервер за один вызов, сформировать единый табличный документ и показать его пользователю.
Рассмотрим несколько надежных решений этой задачи.
Самый простой и методологически верный способ — сделать таблицу со списком номенклатуры реквизитом самой обработки. Если на форме мы работаем с табличной частью, которая принадлежит объекту (например, Объект.Товары или Объект.ЗИП), то эти данные уже доступны на сервере без дополнительных ухищрений.
В этом случае нам достаточно вызвать серверную процедуру, преобразовать данные формы в полноценный прикладной объект и передать его в модуль менеджера. Давайте разберем по шагам, как будет выглядеть этот код.
Код в модуле формы обработки:
&НаКлиенте
Процедура Печать(Команда)
ТабДок = Новый ТабличныйДокумент;
// Вызываем серверную процедуру для заполнения табличного документа
СформироватьПечатнуюФормуНаСервере(ТабДок);
// Настраиваем отображение и выводим пользователю
ТабДок.ОтображатьСетку = Ложь;
ТабДок.Показать("Печать ценников");
КонецПроцедуры
&НаСервере
Процедура СформироватьПечатнуюФормуНаСервере(ТабДок)
// Преобразуем реквизит формы "Объект" в прикладной объект обработки
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
// Вызываем экспортный метод печати из модуля менеджера или модуля объекта
Обработки.ПечатьЦенников.СформироватьПечать(ТабДок, ОбработкаОбъект.ЗИП);
КонецПроцедуры
Использование метода РеквизитФормыВЗначение позволяет нам легко перекинуть данные из интерфейса в серверный контекст, где мы можем использовать весь стек объектных методов 1С.
Что делать, если таблица на форме создана динамически или мы не хотим привязываться к реквизитам объекта? В таком случае отличным решением станет использование сериализуемых типов данных, которые без проблем «путешествуют» между клиентом и сервером. К таким типам относятся Массив и Структура.
Мы можем обойти строки нашей таблицы на клиенте, упаковать нужные параметры (например, ссылку на номенклатуру и цену отгрузки) в структуры, поместить их в массив и передать этот массив на сервер.
Посмотрим на пример реализации данного алгоритма:
&НаКлиенте
Процедура ПечатьВыборочно(Команда)
ДанныеДляПечати = Новый Массив;
// Обходим строки табличной части на клиенте
Для Каждого СтрокаЗИП Из Объект.ЗИП Цикл
// Формируем легкую структуру для передачи на сервер
ПараметрыТовара = Новый Структура;
ПараметрыТовара.Вставить("Номенклатура", СтрокаЗИП.Номенклатура);
ПараметрыТовара.Вставить("Цена", СтрокаЗИП.ЦенаОтгрузки);
ДанныеДляПечати.Добавить(ПараметрыТовара);
КонецЦикла;
ТабДок = Новый ТабличныйДокумент;
СформироватьЦенникиИзМассиваНаСервере(ТабДок, ДанныеДляПечати);
ТабДок.Показать("Печать ценников");
КонецПроцедуры
&НаСервере
Процедура СформироватьЦенникиИзМассиваНаСервере(ТабДок, ДанныеДляПечати)
// Здесь мы уже на сервере. Можем преобразовать массив структур в ТаблицуЗначений,
// если это необходимо для построения сложного запроса или СКД.
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаДанных.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
Для Каждого ЭлементМассива Из ДанныеДляПечати Цикл
НоваяСтрока = ТаблицаДанных.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ЭлементМассива);
КонецЦикла;
// Передаем готовую таблицу значений в модуль менеджера для дальнейшего вывода
Обработки.ПечатьЦенников.Ценники(ТабДок, ТаблицаДанных);
КонецПроцедуры
Этот способ универсален, не перегружает сеть лишними транзакциями и позволяет передавать только строго необходимые свойства объектов.
Если объем отправляемых на печать данных исчисляется сотнями или тысячами строк, прямая передача больших массивов через параметры серверного вызова может замедлить работу интерфейса. В таких ситуациях рекомендуется использовать механизм временного хранилища.
Алгоритм действий выглядит следующим образом: на сервере в контексте формы мы формируем таблицу значений, помещаем её во временное хранилище, передаем на клиент уникальный адрес-ссылку (строку), а затем передаем этот адрес в модуль менеджера. В модуле менеджера мы считываем таблицу обратно.
Рассмотрим код реализации данного метода:
&НаКлиенте
Процедура ПечатьЧерезХранилище(Команда)
ТабДок = Новый ТабличныйДокумент;
АдресХранилища = "";
// Шаг 1. Переносим данные во временное хранилище на сервере
ПодготовитьДанныеНаСервере(АдресХранилища);
// Шаг 2. Формируем печатную форму на сервере без передачи тяжелых объектов
СформироватьПечатнуюФормуПоАдресу(ТабДок, АдресХранилища);
ТабДок.Показать();
КонецПроцедуры
&НаСервере
Процедура ПодготовитьДанныеНаСервере(АдресХранилища)
// Создаем серверную ТаблицуЗначений напрямую из ТЧ формы
ТаблицаДанных = Объект.ЗИП.Выгрузить();
// Помещаем во временное хранилище с привязкой к уникальному идентификатору формы
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДанных, УникальныйИдентификатор);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура СформироватьПечатнуюФормуПоАдресу(ТабДок, АдресХранилища)
// В модуле менеджера или общем модуле извлекаем данные
ТаблицаДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ТипЗнч(ТаблицаДанных) = Тип("ТаблицаЗначений") Тогда
Обработки.ПечатьЦенников.Ценники(ТабДок, ТаблицаДанных);
КонецЕсли;
КонецПроцедуры
После того как мы решили проблему передачи данных на сервер, необходимо правильно сформировать сам табличный документ. Начинающие разработчики часто совершают ошибку: выводят горизонтальный разделитель страниц после каждого ценника. Если ценник имеет стандартный размер (например, 58х40 мм), принтер распечатает ровно один ценник на весь лист формата А4. Это приведет к колоссальному нерациональному расходу бумаги.
Для правильного размещения ценников на листе используют технологию плиточного (сеточного) вывода с контролем заполнения страницы по горизонтали и вертикали.
Давайте подробно разберем, как реализовать этот алгоритм в модуле менеджера вашей обработки:
Процедура Ценники(ТабДок, ТаблицаДанных) Экспорт
Макет = ПолучитьМакет("МакетЦенника");
ОбластьЦенника = Макет.ПолучитьОбласть("Ценник");
// Очищаем табличный документ перед заполнением
ТабДок.Очистить();
// Задаем уникальный ключ параметров печати, чтобы система запоминала настройки принтера
ТабДок.КлючПараметровПечати = "ПараметрыПечати_Ценники";
// Устанавливаем минимальные поля для экономии места
ТабДок.ПолеСверху = 5;
ТабДок.ПолеСнизу = 5;
ТабДок.ПолеСлева = 5;
ТабДок.ПолеСправа = 5;
// Создаем массив для проверки возможности вертикального вывода строки ценников
МассивОбластейДляПроверки = Новый Массив;
МассивОбластейДляПроверки.Добавить(ОбластьЦенника);
ПервыйЦенникСтроки = Истина;
Для Каждого СтрокаТовара Из ТаблицаДанных Цикл
// Заполняем параметры макета данными товара
ОбластьЦенника.Параметры.Номенклатура = СтрокаТовара.Номенклатура;
ОбластьЦенника.Параметры.Цена = СтрокаТовара.Цена;
Если ПервыйЦенникСтроки Тогда
// Проверяем, поместится ли новая строка ценников по вертикали на текущем листе
Если Не ТабДок.ПроверитьВывод(МассивОбластейДляПроверки) Тогда
// Если не помещается, принудительно переносим вывод на новую страницу
ТабДок.ВывестиГоризонтальныйРазделительСтраниц();
КонецЕсли;
// Выводим первый ценник в новой строке
ТабДок.Вывести(ОбластьЦенника);
ПервыйЦенникСтроки = Ложь;
Иначе
// Проверяем, помещается ли очередной ценник по горизонтали в текущей строке
Если ТабДок.ПроверитьПрисоединение(ОбластьЦенника) Тогда
// Присоединяем ценник справа
ТабДок.Присоединить(ОбластьЦенника);
Иначе
// Если по ширине листа место закончилось, переходим на новую строку
// И проверяем, поместится ли она по вертикали
Если Не ТабДок.ПроверитьВывод(МассивОбластейДляПроверки) Тогда
ТабДок.ВывестиГоризонтальныйРазделительСтраниц();
КонецЕсли;
// Выводим ценник с новой строки методом Вывести()
ТабДок.Вывести(ОбластьЦенника);
КонецЕсли;
EndIf;
КонецЦикла;
КонецПроцедуры
Проанализируем ключевые методы, использованные в коде:
ТабДок.ПроверитьВывод(МассивОбластей) — проверяет, уместится ли переданный массив областей по высоте страницы при текущих настройках принтера и полей. Возвращает Истина или Ложь. Если возвращается Ложь, мы программно добавляем разделитель страниц методом ВывестиГоризонтальныйРазделительСтраниц().ТабДок.ПроверитьПрисоединение(Область) — проверяет, помещается ли присоединяемая область по ширине страницы справа от уже выведенных областей. Если возвращается Ложь, текущая строка заполнена, и следующий ценник мы должны начать выводить с новой строки через метод Вывести().ТабДок.КлючПараметровПечати — позволяет сохранить пользовательские настройки параметров страницы (ориентация, масштаб, выбранный принтер) в системном реестре. При следующей печати ценников пользователю не придется заново настраивать масштаб и поля в диалоговом окне печати.Разделение логики на клиентскую и серверную часть — один из ключевых принципов разработки в современных версиях «1С:Предприятие 8». Передавая структурированные данные вместо тяжелых серверных таблиц значений, мы обеспечиваем стабильную и быструю работу приложения даже на тонких клиентах с медленным интернет-соединением. А использование сеточного вывода с методами проверки границ страницы делает наши печатные формы экономичными и профессионально оформленными.