При разработке конфигураций на платформе 1С:Предприятие 7.7 начинающие программисты часто сталкиваются с проблемой разделения контекстов выполнения кода. Типичный сценарий: во время проведения документа необходимо проверить заполнение реквизита табличной части и вывести пользователю красивое, понятное сообщение об ошибке, используя именно тот заголовок колонки, который он видит на экране. Однако при попытке обратиться к свойствам формы из модуля документа система выдает ошибку: Переменная не определена (Форма).
В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем опасные "костыли", которые ни в коем случае нельзя использовать в реальных базах данных, и рассмотрим правильные архитектурные решения этой задачи.
Платформа 1С 7.7 строго разделяет логику представления (интерфейс) и логику базы данных (проведение документов). Давайте разберем особенности работы этих механизмов:
Форма, через который можно управлять элементами диалога, цветом колонок, доступностью кнопок и читать их заголовки.ОбработкаПроведения()). В этом контексте никакой формы может вообще не существовать. Например, когда документы перепроводятся программно (через групповую обработку, при восстановлении последовательности или регламентным заданием ночью), интерфейсная часть полностью отсутствует.Попытка вызвать Форма.ПолучитьАтрибут() или получить заголовок напрямую из модуля документа неизбежно приведет к аварийному завершению работы кода.
Иногда разработчики пытаются обойти ограничения платформы с помощью внешних компонент (например, популярной библиотеки FormEx) и пишут следующий код в модуле документа:
Если Контрагент.Выбран() = 0 Тогда
Конт = "";
Сервис = СоздатьОбъект("Сервис");
Если Сервис.АктивныйКонтекст(Конт) = 1 Тогда
Предупреждение("Не выбран реквизит """ + Конт.Форма.Контрагент.Заголовок() + """ !");
Иначе
Предупреждение("Не выбран реквизит ""Контрагент"" !");
КонецЕсли;
СтатусВозврата(0);
Возврат;
КонецЕсли;
Внимание! Настоятельно рекомендуем никогда не использовать подобный подход. И вот почему:
Предупреждение() без указания таймаута открывает модальное окно, которое блокирует выполнение всего потока. Если этот документ будет проводиться в монопольном режиме во время ночного перепроведения базы данных, система просто «застынет» в ожидании нажатия кнопки «ОК» пользователем, которого нет за компьютером. В многопользовательской базе это приведет к блокировкам таблиц и простою всей компании.Сервис.АктивныйКонтекст() возвращает контекст формы, которая активна на экране в данный момент времени. При программном проведении активной может оказаться совершенно другая форма (например, форма обработки перепроведения), что вызовет непредсказуемые ошибки.Ниже мы рассмотрим три правильных, безопасных и стандартных способа решения этой задачи.
Самый правильный с точки зрения архитектуры 1С 7.7 подход — проверять корректность заполнения реквизитов формы до начала транзакции проведения. Лучшее место для этого — предопределенная процедура ПриЗаписи() в модуле формы документа.
Давайте рассмотрим пример реализации такой проверки по шагам:
Процедура ПриЗаписи()
// Проверяем заполнение реквизитов в табличной части
ВыбратьСтроки();
Пока ПолучитьСтроку() = 1 Цикл
Если Контрагент.Выбран() = 0 Тогда
// В модуле формы объект Форма доступен напрямую!
ЗаголовокКолонки = Форма.Контрагент.Заголовок();
Если ПустаяСтрока(ЗаголовокКолонки) = 1 Тогда
ЗаголовокКолонки = "Контрагент"; // Резервное имя, если заголовок пустой
КонецЕсли;
// Используем Предупреждение с таймаутом (например, 10 секунд), чтобы избежать зависаний
Предупреждение("В строке № " + НомерСтроки + " не заполнен реквизит: """ + ЗаголовокКолонки + """!", 10);
// Отменяем запись и проведение документа
СтатусВозврата(0);
Возврат;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Этот код абсолютно безопасен. Если пользователь проводит документ интерактивно, он увидит предупреждение с актуальным заголовком и запись отменится. Если документ проводится программно, процедура ПриЗаписи() формы просто не вызывается, что исключает зависание системы.
Если заголовок колонки табличной части совпадает с синонимом реквизита, заданным в конфигураторе, то нам вообще не нужно обращаться к форме. Мы можем безопасно прочитать синоним реквизита прямо в модуле документа через объект метаданных.
Проанализируем пример кода для использования внутри процедуры ОбработкаПроведения():
Процедура ОбработкаПроведения()
ВыбратьСтроки();
Пока ПолучитьСтроку() = 1 Цикл
Если Контрагент.Выбран() = 0 Тогда
// Получаем синоним реквизита табличной части из метаданных
ИмяРеквизита = "Контрагент";
СинонимРеквизита = Метаданные.Документ(Вид()).РеквизитТабличнойЧасти(ИмяРеквизита).Синоним;
Если ПустаяСтрока(СинонимРеквизита) = 1 Тогда
ИмяДляСообщения = Метаданные.Документ(Вид()).РеквизитТабличнойЧасти(ИмяРеквизита).Идентификатор;
Иначе
ИмяДляСообщения = СинонимРеквизита;
КонецЕсли;
// Безопасный вывод сообщений без модальных окон!
Сообщить("Документ " + ТекущийДокумент() + " не проведен! В строке " + НомерСтроки + " не заполнен реквизит """ + ИмяДляСообщения + """.", "!");
СтатусВозврата(0);
Возврат;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Этот способ универсален. Метод Сообщить() выводит информацию в нижнее окно сообщений 1С, не прерывая выполнение программного кода модальными диалогами. Такой документ без проблем проведется при пакетной обработке, а администратор увидит ошибку в логах.
Иногда заголовок колонки меняется программно на лету (например, в зависимости от договора он может называться «Поставщик», «Покупатель» или «Комиссионер»). Как быть в этом случае, если значение заголовка нужно знать в модуле проведения?
Для решения этой задачи применим паттерн хранения состояния:
ТекущийЗаголовокКонтрагента (на форму его выносить не нужно).
// Пример кода в модуле формы при изменении типа договора
Процедура ПриВыбореДоговора()
Если Договор.Вид = "Покупка" Тогда
НовыйЗаголовок = "Поставщик";
Иначе
НовыйЗаголовок = "Покупатель";
КонецЕсли;
// Меняем заголовок визуального элемента
Форма.Контрагент.Заголовок(НовыйЗаголовок);
// Записываем значение в скрытый реквизит документа для модуля проведения
ТекущийЗаголовокКонтрагента = НовыйЗаголовок;
КонецПроцедуры
ОбработкаПроведения() мы можем легко и безопасно прочитать это значение:
Процедура ОбработкаПроведения()
ВыбратьСтроки();
Пока ПолучитьСтроку() = 1 Цикл
Если Контрагент.Выбран() = 0 Тогда
ИмяРеквизита = ?(ПустаяСтрока(ТекущийЗаголовокКонтрагента) = 0, ТекущийЗаголовокКонтрагента, "Контрагент");
Сообщить("Ошибка проведения: Не заполнено поле """ + ИмяРеквизита + """ в строке " + НомерСтроки, "!");
СтатусВозврата(0);
Возврат;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Разделив логику интерфейса и проведения таким образом, мы получили стабильный, отказоустойчивый код, который корректно отрабатывает в любых режимах работы платформы 1С:Предприятие 7.7.