При доработке старых конфигураций, работающих в режиме обычного приложения, часто возникает необходимость внедрения элементов управляемого интерфейса. Например, вы добавляете управляемую форму списка для части пользователей, чтобы они могли работать через тонкий клиент или просто использовать более современный интерфейс. Однако при выполнении стандартных действий (проведение, пометка на удаление) из управляемой формы начинают сыпаться ошибки компиляции в модуле объекта. Разберем, почему это происходит и как правильно разделить код.
Проанализируем ситуацию: в обычном приложении в режиме Толстого клиента модуль объекта компилируется на стороне клиента. Это позволяет разработчикам использовать такие интерактивные методы, как Вопрос(), и обращаться к системным перечислениям вроде КодВозвратаДиалога прямо внутри обработчиков ПередЗаписью или ОбработкаПроведения.
Когда же вы открываете управляемую форму (даже внутри обычного приложения), контекст исполнения меняется. В управляемом приложении работа с объектом (его запись и проведение) всегда происходит на сервере. На сервере 1С не существует понятий «диалог», «вопрос» или «форма». Поэтому попытка сервера скомпилировать код, содержащий КодВозвратаДиалога.Да, приводит к ошибке: «Переменная не определена».
Рассмотрим типичный проблемный участок кода:
СписокКнопок = Новый СписокЗначений;
СписокКнопок.Добавить(КодВозвратаДиалога.Да, "Создать");
СписокКнопок.Добавить(КодВозвратаДиалога.Нет, "Отмена");
Ответ = Вопрос("Создать связанные документы?", СписокКнопок);
Самый простой и быстрый способ решить проблему «в лоб» — это обернуть весь интерактивный код в инструкции препроцессора. Это позволит исключить данный фрагмент кода из компиляции, когда модуль объекта компилируется на сервере.
Проанализируем, какую директиву лучше использовать. На форумах часто предлагают вариант #Если Клиент Тогда. Это рабочее решение, но стоит учитывать нюансы работы в разных режимах. Рассмотрим наиболее надежный вариант для обычного приложения:
#Если ТолстыйКлиентОбычноеПриложение Тогда
СписокКнопок = Новый СписокЗначений;
СписокКнопок.Добавить(КодВозвратаДиалога.Да, "+");
СписокКнопок.Добавить(КодВозвратаДиалога.Нет, "-");
Ответ = Вопрос("Что создаем", СписокКнопок);
#КонецЕсли
Важный момент: Если вы используете #Если Клиент Тогда, код будет доступен и в Тонком клиенте. Однако в тонком клиенте (управляемые формы) выполнение модуля объекта всё равно происходит на сервере, поэтому ошибки компиляции могут остаться или поведение будет непредсказуемым. Директива #Если ТолстыйКлиентОбычноеПриложение Тогда максимально точно отсекает старый код, предназначенный только для обычных форм.
Рассмотрим более архитектурно правильный подход. Использование диалогов внутри модуля объекта считается плохим тоном (anti-pattern), так как это мешает автоматизации (например, групповой обработке документов). Вместо того чтобы задавать вопросы в модуле объекта, мы можем вынести интерактив в модуль формы, а результат передать в объект.
Разберем по шагам, как это реализовать через ДополнительныеСвойства:
Записать() задаем вопрос пользователю.ДополнительныеСвойства объекта.Пример кода в форме:
// В обработчике нажатия кнопки или ПередЗаписью на клиенте
Если ЭтоНовый() Тогда
// Задаем вопрос
Режим = РежимДиалогаВопрос.ДаНет;
Оповещение = Новый ОписаниеОповещения("ПослеВопроса", ЭтотОбъект);
ПоказатьВопрос(Оповещение, "Выполнить дополнительную обработку?", Режим);
КонецЕсли;
// В процедуре обратного вызова
&НаКлиенте
Процедура ПослеВопроса(Результат, ДополнительныеПараметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
Объект.ДополнительныеСвойства.Вставить("НужноЛиДелатьДопДействие", Истина);
КонецЕсли;
ЭтотОбъект.Записать();
КонецПроцедуры
Пример кода в модуле объекта:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если ДополнительныеСвойства.Свойство("НужноЛиДелатьДопДействие")
И ДополнительныеСвойства.НужноЛиДелатьДопДействие Тогда
// Выполняем логику, которая раньше требовала вопроса
КонецЕсли;
КонецПроцедуры
Иногда директив препроцессора недостаточно, и нужно программно понять, в каком интерфейсе находится пользователь. Для этого можно использовать метод ТекущийВариантИнтерфейсаКлиентскогоПриложения().
Проанализируем ситуацию: если ваша конфигурация работает на платформе 8.3, вы можете проверять, используется ли интерфейс «Такси» или «Версия 8.2» (обычное приложение). Это помогает гибко управлять логикой, не полагаясь только на место компиляции кода.
Если ТекущийВариантИнтерфейсаКлиентскогоПриложения() = ВариантИнтерфейсаКлиентскогоПриложения.Версия82 Тогда
// Мы в обычном интерфейсе, можно использовать старые методы
#Если Клиент Тогда
Сообщить("Вы используете обычное приложение");
#КонецЕсли
КонецЕсли;
Важно помнить, что управляемые формы работают по принципу асинхронности. Если в обычной форме метод Вопрос() останавливал выполнение кода до получения ответа, то в управляемом приложении (особенно в веб-клиенте или при установленном свойстве конфигурации «Режим использования модальности = Не использовать») необходимо использовать ПоказатьВопрос().
Посмотрим на типичную ошибку: если вы просто добавите #Если Клиент Тогда вокруг Вопрос(), то при вызове из управляемой формы на клиенте код сработает, но если платформа настроена на работу без модальных окон, вы получите исключение времени выполнения. Поэтому вынос интерактива в модули соответствующих форм — это не просто рекомендация, а необходимость для стабильной работы гибридного приложения.
Чтобы не загромождать модули объектов многочисленными вставками #Если...#КонецЕсли, мы рекомендуем следовать следующим правилам:
РаботаСДиалогами) с флагами «Клиент (обычное приложение)». В нем можно инкапсулировать вызовы диалогов.ОбщегоНазначения.ЭтоУправляемоеПриложение(). Это сделает ваш код более читаемым и универсальным.Рассмотрим пример использования общего модуля для разделения логики:
// В модуле объекта
#Если Клиент Тогда
Ответ = РаботаСИнтерфейсомКлиент.ЗадатьВопросПользователю("Продолжить выполнение?");
#Иначе
Ответ = КодВозвратаДиалога.Да; // На сервере считаем, что ответ всегда положительный
#КонецЕсли
Таким образом, мы обеспечиваем работоспособность кода в обоих режимах: в обычном интерфейсе пользователь увидит привычное окно, а при выполнении фоновых заданий или записи из управляемых форм (где код выполняется на сервере) система продолжит работу без прерывания на диалоги.