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