Как разделить код исполнения между обычными и управляемыми формами в модуле объекта 1С

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При доработке старых конфигураций, работающих в режиме обычного приложения, часто возникает необходимость внедрения элементов управляемого интерфейса. Например, вы добавляете управляемую форму списка для части пользователей, чтобы они могли работать через тонкий клиент или просто использовать более современный интерфейс. Однако при выполнении стандартных действий (проведение, пометка на удаление) из управляемой формы начинают сыпаться ошибки компиляции в модуле объекта. Разберем, почему это происходит и как правильно разделить код.

Выясним причину возникновения ошибок

Проанализируем ситуацию: в обычном приложении в режиме Толстого клиента модуль объекта компилируется на стороне клиента. Это позволяет разработчикам использовать такие интерактивные методы, как Вопрос(), и обращаться к системным перечислениям вроде КодВозвратаДиалога прямо внутри обработчиков ПередЗаписью или ОбработкаПроведения.

Когда же вы открываете управляемую форму (даже внутри обычного приложения), контекст исполнения меняется. В управляемом приложении работа с объектом (его запись и проведение) всегда происходит на сервере. На сервере 1С не существует понятий «диалог», «вопрос» или «форма». Поэтому попытка сервера скомпилировать код, содержащий КодВозвратаДиалога.Да, приводит к ошибке: «Переменная не определена».

Рассмотрим типичный проблемный участок кода:


СписокКнопок = Новый СписокЗначений;
СписокКнопок.Добавить(КодВозвратаДиалога.Да, "Создать");
СписокКнопок.Добавить(КодВозвратаДиалога.Нет, "Отмена");
Ответ = Вопрос("Создать связанные документы?", СписокКнопок);

Способ 1. Использование директив препроцессора

Самый простой и быстрый способ решить проблему «в лоб» — это обернуть весь интерактивный код в инструкции препроцессора. Это позволит исключить данный фрагмент кода из компиляции, когда модуль объекта компилируется на сервере.

Проанализируем, какую директиву лучше использовать. На форумах часто предлагают вариант #Если Клиент Тогда. Это рабочее решение, но стоит учитывать нюансы работы в разных режимах. Рассмотрим наиболее надежный вариант для обычного приложения:


#Если ТолстыйКлиентОбычноеПриложение Тогда
    СписокКнопок = Новый СписокЗначений;
    СписокКнопок.Добавить(КодВозвратаДиалога.Да, "+");
    СписокКнопок.Добавить(КодВозвратаДиалога.Нет, "-");
    Ответ = Вопрос("Что создаем", СписокКнопок);
#КонецЕсли

Важный момент: Если вы используете #Если Клиент Тогда, код будет доступен и в Тонком клиенте. Однако в тонком клиенте (управляемые формы) выполнение модуля объекта всё равно происходит на сервере, поэтому ошибки компиляции могут остаться или поведение будет непредсказуемым. Директива #Если ТолстыйКлиентОбычноеПриложение Тогда максимально точно отсекает старый код, предназначенный только для обычных форм.

Способ 2. Использование свойств объекта для передачи ответов

Рассмотрим более архитектурно правильный подход. Использование диалогов внутри модуля объекта считается плохим тоном (anti-pattern), так как это мешает автоматизации (например, групповой обработке документов). Вместо того чтобы задавать вопросы в модуле объекта, мы можем вынести интерактив в модуль формы, а результат передать в объект.

Разберем по шагам, как это реализовать через ДополнительныеСвойства:

  1. В модуле формы (управляемой или обычной) перед вызовом Записать() задаем вопрос пользователю.
  2. Результат ответа записываем в структуру ДополнительныеСвойства объекта.
  3. В модуле объекта проверяем наличие этого свойства и выполняем логику.

Пример кода в форме:


// В обработчике нажатия кнопки или ПередЗаписью на клиенте
Если ЭтоНовый() Тогда
    // Задаем вопрос
    Режим = РежимДиалогаВопрос.ДаНет;
    Оповещение = Новый ОписаниеОповещения("ПослеВопроса", ЭтотОбъект);
    ПоказатьВопрос(Оповещение, "Выполнить дополнительную обработку?", Режим);
КонецЕсли;

// В процедуре обратного вызова
&НаКлиенте
Процедура ПослеВопроса(Результат, ДополнительныеПараметры) Экспорт
    Если Результат = КодВозвратаДиалога.Да Тогда
        Объект.ДополнительныеСвойства.Вставить("НужноЛиДелатьДопДействие", Истина);
    КонецЕсли;
    ЭтотОбъект.Записать();
КонецПроцедуры

Пример кода в модуле объекта:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если ДополнительныеСвойства.Свойство("НужноЛиДелатьДопДействие") 
        И ДополнительныеСвойства.НужноЛиДелатьДопДействие Тогда
        // Выполняем логику, которая раньше требовала вопроса
    КонецЕсли;
КонецПроцедуры

Способ 3. Программная проверка режима интерфейса

Иногда директив препроцессора недостаточно, и нужно программно понять, в каком интерфейсе находится пользователь. Для этого можно использовать метод ТекущийВариантИнтерфейсаКлиентскогоПриложения().

Проанализируем ситуацию: если ваша конфигурация работает на платформе 8.3, вы можете проверять, используется ли интерфейс «Такси» или «Версия 8.2» (обычное приложение). Это помогает гибко управлять логикой, не полагаясь только на место компиляции кода.


Если ТекущийВариантИнтерфейсаКлиентскогоПриложения() = ВариантИнтерфейсаКлиентскогоПриложения.Версия82 Тогда
    // Мы в обычном интерфейсе, можно использовать старые методы
#Если Клиент Тогда
    Сообщить("Вы используете обычное приложение");
#КонецЕсли
КонецЕсли;

Особенности асинхронной работы в управляемых формах

Важно помнить, что управляемые формы работают по принципу асинхронности. Если в обычной форме метод Вопрос() останавливал выполнение кода до получения ответа, то в управляемом приложении (особенно в веб-клиенте или при установленном свойстве конфигурации «Режим использования модальности = Не использовать») необходимо использовать ПоказатьВопрос().

Посмотрим на типичную ошибку: если вы просто добавите #Если Клиент Тогда вокруг Вопрос(), то при вызове из управляемой формы на клиенте код сработает, но если платформа настроена на работу без модальных окон, вы получите исключение времени выполнения. Поэтому вынос интерактива в модули соответствующих форм — это не просто рекомендация, а необходимость для стабильной работы гибридного приложения.

Рекомендации по организации общего кода

Чтобы не загромождать модули объектов многочисленными вставками #Если...#КонецЕсли, мы рекомендуем следовать следующим правилам:

  1. Выносите общие проверки в общие модули: Создайте общий модуль (например, РаботаСДиалогами) с флагами «Клиент (обычное приложение)». В нем можно инкапсулировать вызовы диалогов.
  2. Используйте БСП: Если в вашей базе подключена Библиотека Стандартных Подсистем, используйте функции ОбщегоНазначения.ЭтоУправляемоеПриложение(). Это сделает ваш код более читаемым и универсальным.
  3. Минимизируйте интерактив в объектах: Стремитесь к тому, чтобы модуль объекта отвечал только за целостность данных. Все вопросы пользователю должны быть сосредоточены в экранных формах.

Рассмотрим пример использования общего модуля для разделения логики:


// В модуле объекта
#Если Клиент Тогда
    Ответ = РаботаСИнтерфейсомКлиент.ЗадатьВопросПользователю("Продолжить выполнение?");
#Иначе
    Ответ = КодВозвратаДиалога.Да; // На сервере считаем, что ответ всегда положительный
#КонецЕсли

Таким образом, мы обеспечиваем работоспособность кода в обоих режимах: в обычном интерфейсе пользователь увидит привычное окно, а при выполнении фоновых заданий или записи из управляемых форм (где код выполняется на сервере) система продолжит работу без прерывания на диалоги.

← На главную