При разработке решений на платформе 1С:Предприятие 7.7 мы часто сталкиваемся с тем, что во время выполнения длительных циклов (например, пакетной печати документов, закрытия месяца или групповой обработки справочников) интерфейс программы «замирает». Это происходит из-за однопоточной архитектуры платформы: пока выполняется программный код, система не обрабатывает события пользовательского интерфейса, такие как нажатия кнопок на форме.
В этой статье мы подробно разберем, как решить задачу корректного прерывания работы процедуры пакетной печати чеков. Наша цель — дать пользователю возможность остановить процесс в любой момент, но при этом гарантированно сохранить информацию о том, сколько чеков уже было напечатано, чтобы избежать дублирования или потери данных.
Рассмотрим типичную ситуацию. У нас есть цикл, который перебирает список документов и отправляет их на печать. Если мы просто разместим на форме кнопку «Стоп» и попытаемся нажать её во время работы этого цикла, ничего не произойдет. Система поставит нажатие в очередь сообщений Windows, но обработает его только после завершения всей процедуры. Очевидно, что для экстренной остановки такой вариант не подходит.
Давайте проанализируем несколько проверенных подходов к решению этой задачи, от штатных механизмов платформы до использования архитектурных хитростей.
Платформа 1С 7.7 имеет встроенный механизм прерывания выполнения кода клавишей Esc (или сочетанием Ctrl+Break). Однако, по умолчанию нажатие этой клавиши вызывает аварийную остановку выполнения модуля с выдачей сообщения пользователю, что не позволяет нам корректно сохранить данные (количество уже напечатанных чеков).
Чтобы использовать этот механизм грамотно, мы должны обернуть тело цикла в конструкцию Попытка ... Исключение. Рассмотрим, как это реализовать.
Логика работы следующая:
Esc, платформа генерирует исключение.Пример реализации кода:
Процедура ПечатьПакета()
Перем Сч;
Док = СоздатьОбъект("Документ.ЧекККМ");
// Выборка документов...
НачатьТранзакцию();
Для Сч = 1 По СписокЧеков.РазмерСписка() Цикл
ТекЧек = СписокЧеков.ПолучитьЗначение(Сч);
Попытка
// Здесь происходит основная работа
// Если пользователь нажмет Esc, управление перейдет в Исключение
// 1. Печатаем чек
ПечатьЧека(ТекЧек);
// 2. Фиксируем, что чек напечатан (например, ставим флаг в документе)
ТекЧек.Напечатан = 1;
ТекЧек.Записать();
// Обновляем счетчик на форме, чтобы пользователь видел прогресс
Состояние("Печать чека " + Сч + " из " + СписокЧеков.РазмерСписка());
Исключение
// Сюда мы попадаем при нажатии Esc
ОтменитьТранзакцию(); // Или ЗафиксироватьТранзакцию(), в зависимости от логики
Предупреждение("Печать прервана пользователем! Напечатано чеков: " + (Сч - 1));
// Важно: сохраняем текущий статус обработки в родительском документе
ЗаписатьКоличествоНапечатанных(Сч - 1);
Возврат; // Выходим из процедуры
КонецПопытки;
КонецЦикла;
ЗафиксироватьТранзакцию();
Сообщить("Печать завершена успешно.");
КонецПроцедуры
Важно: Чтобы этот метод работал стабильно, транзакции лучше фиксировать для каждого чека отдельно (или для небольших групп), иначе при ОтменитьТранзакцию() вся работа будет потеряна. Если скорость позволяет, используйте конструкцию НачатьТранзакцию() / ЗафиксироватьТранзакцию() внутри итерации цикла.
Если мы хотим, чтобы кнопка «Стоп» на форме оставалась активной и нажималась пользователем, нам нужно разорвать непрерывный цикл. Мы можем использовать метод ОбработкаОжидания.
Суть метода заключается в том, что мы не запускаем цикл Для...Цикл. Вместо этого мы создаем процедуру, которая печатает ровно один чек, а затем сама себя «планирует» к запуску через короткий промежуток времени.
Разберем алгоритм по шагам:
РаботаРазрешена = 1.ОбработкаОжидания("ИтерацияПечати", 1).ИтерацияПечати.РаботаРазрешена = 1, процедура завершается, ожидая следующего вызова.РаботаРазрешена = 0. При следующем запуске ИтерацияПечати увидит это и остановит процесс.Реализация в модуле формы:
Перем ТекИндекс; // Глобальная переменная модуля формы
Перем ФлагОстановки;
Процедура КнопкаСтоп()
ФлагОстановки = 1;
Форма.КнопкаСтоп.Доступность(0);
КонецПроцедуры
Процедура ИтерацияПечати()
// Снимаем ожидание, чтобы процедура не вызывалась повторно сама собой,
// пока мы не закончим текущий шаг
ОбработкаОжидания("", 0);
Если (ФлагОстановки = 1) ИЛИ (ТекИндекс > СписокЧеков.РазмерСписка()) Тогда
Сообщить("Обработка завершена или остановлена. Напечатано: " + (ТекИндекс - 1));
// Сохраняем результат в документ
ЗаписатьРезультатВДокумент(ТекИндекс - 1);
Возврат;
КонецЕсли;
// Печать одного чека
ТекЧек = СписокЧеков.ПолучитьЗначение(ТекИндекс);
ПечатьЧека(ТекЧек);
ТекИндекс = ТекИндекс + 1;
// Снова планируем выполнение этой же процедуры
// В штатной 1С 7.7 минимальное время - 1 секунда.
// С использованием FormEx можно ставить миллисекунды.
ОбработкаОжидания("ИтерацияПечати", 1);
КонецПроцедуры
Процедура КнопкаПечать()
ТекИндекс = 1;
ФлагОстановки = 0;
Форма.КнопкаСтоп.Доступность(1);
// Запускаем процесс
ОбработкаОжидания("ИтерацияПечати", 1);
КонецПроцедуры
Преимущества: Интерфейс полностью отзывчив. Пользователь видит нажатие кнопки «Стоп».
Недостатки: Низкая скорость при использовании штатных средств (1 чек в секунду). Если чеков сотни, это будет слишком долго. Проблема решается использованием внешней компоненты FormEx, которая позволяет использовать метод Сервис.ОбработкаОжидания() с интервалом в миллисекундах (например, 50-100 мс).
Если использование Esc кажется ненадежным, а ОбработкаОжидания — медленной, можно использовать проверку внешнего условия внутри цикла. Поскольку опрашивать кнопки мы не можем, будем опрашивать файловую систему.
Идея заключается в следующем: внутри цикла печати мы проверяем наличие определенного файла-маркера в папке временных файлов или каталоге пользователя. Для остановки процесса пользователю нужно создать этот файл.
Чтобы это было удобно для пользователя (не заставлять же его создавать файлы вручную), можно сделать специальный ярлык на рабочем столе или горячую клавишу в Windows, которая запускает простой .bat файл, создающий пустой файл-флаг.
Пример кода внутри цикла:
ИмяФайлаСтоп = КаталогПользователя() + "stop_print.flg";
// Убедимся, что файла нет перед началом
ФС.УдалитьФайл(ИмяФайлаСтоп);
Для Сч = 1 По СписокЧеков.РазмерСписка() Цикл
// Проверка наличия файла-флага
Если ФС.СуществуетФайл(ИмяФайлаСтоп) = 1 Тогда
// Удаляем файл, чтобы сбросить флаг
ФС.УдалитьФайл(ИмяФайлаСтоп);
Если Вопрос("Прервать печать?", "Да+Нет") = "Да" Тогда
Прервать;
КонецЕсли;
КонецЕсли;
// ... Код печати чека ...
КонецЦикла;
Этот метод работает быстро и надежно, так как операция ФС.СуществуетФайл выполняется практически мгновенно и не тормозит цикл.
Для профессиональной разработки на 7.7 часто используются расширения, такие как FormEx. Эта компонента позволяет перехватывать нажатия клавиш клавиатуры на низком уровне, даже когда 1С занята выполнением цикла.
С помощью FormEx можно:
ОбработкаВнешнегоСобытия или перехват клавиатуры.Рассмотрим вариант с «микро-паузами», которые позволяют системе обработать очередь событий (включая клики мышью), не прерывая цикл надолго. В 1С 7.7 это реализуется сложно, но с помощью ВК (например, DynWrap и вызовов WinAPI функций PeekMessage, TranslateMessage, DispatchMessage) можно заставить 1С «прокручивать» события Windows внутри цикла.
Попытка ... Исключение в цикле. Обучите пользователей нажимать Esc для остановки. Это не требует изменения архитектуры и внешних компонент.ОбработкаОжидания. Это позволяет сделать реальную кнопку «Стоп» или «Пауза». Если скорость печати критична — обязательно подключите FormEx для уменьшения интервала между итерациями.Какой бы способ вы ни выбрали, помните о главном требовании автора задачи: сохранение состояния. Всегда обновляйте счетчик напечатанных документов и записывайте его в базу данных (в реквизит документа или журнал регистрации) либо после каждого чека, либо в блоке обработки прерывания перед окончательным выходом.