При работе с управляемыми формами в системе 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда некоторые события, предназначенные для интерактивного взаимодействия пользователя, не срабатывают автоматически при программных действиях. Одной из таких распространенных проблем является невсрабатывание события ПриСменеСтраницы для группы страниц или панели при программном изменении текущей страницы.
Предположим, мы разрабатываем обработку, которая программно открывает форму документа и затем пытается переключить ее на определенную страницу, ожидая, что код обработчика ПриСменеСтраницы выполнится. Однако, к нашему удивлению, этого не происходит. Мы с вами разберем, почему так происходит, и какие существуют эффективные способы решения этой задачи.
Давайте сначала выясним, почему возникает данная проблема. В 1С:Предприятии 8.3 события управляемых форм делятся на два основных типа: интерактивные и программные. Событие ПриСменеСтраницы, как и многие другие, по своей природе является интерактивным. Это означает, что оно спроектировано для срабатывания в ответ на действия пользователя, такие как щелчок мышью по заголовку страницы или использование клавиш навигации.
Когда мы программно изменяем текущую страницу группы страниц, например, присваивая значение свойству ТекущаяСтраница элемента формы:
ФормаДокумента.Элементы.МояГруппаСтраниц.ТекущаяСтраница = ФормаДокумента.Элементы.СтраницаДанных;
система 1С:Предприятие интерпретирует это как прямое изменение состояния элемента, а не как интерактивное действие пользователя. В результате, стандартный механизм вызова обработчика ПриСменеСтраницы не активируется автоматически. Переключение страницы на форме визуально происходит, но связанный с этим событием код не выполняется, что и порождает проблему, с которой мы столкнулись.
Самый прямой и часто используемый способ решения проблемы — это явный вызов процедуры-обработчика события ПриСменеСтраницы после того, как мы программно установили нужную страницу. Если код, который должен выполняться при смене страницы, находится в процедуре, например, МояСтраницаПриСменеСтраницы, нам необходимо вызвать эту процедуру вручную.
Однако, как было показано в обсуждении, прямая попытка вызова вида ФормаДок.Элементы.ГруппаЭлементов.МояСтраницаПриСменеСтраницы(ТекущаяСтраница); или МояСтраницаПриСменеСтраницы(ТекущаяСтраница) может привести к ошибке "Процедура или функция с указанным именем не определена". Почему так происходит?
Ошибка "Процедура или функция с указанным именем не определена" возникает потому, что процедуры, являющиеся обработчиками событий, по умолчанию не являются экспортными. То есть, они не доступны для вызова извне модуля, где они определены, или из другого контекста (например, из клиентского модуля, если процедура серверная, и наоборот), или из внешней обработки, которая открывает эту форму.
Чтобы иметь возможность вызвать процедуру обработчика извне, нам необходимо сделать ее экспортной. Это особенно важно, если мы вызываем форму из другой обработки или модуля, и хотим влиять на ее внутреннюю логику. Также важно учитывать контекст вызова: если процедура обработчика является клиентской, ее можно вызвать только на клиенте; если серверной, то на сервере.
Предположим, в модуле формы документа у нас есть обработчик события ПриСменеСтраницы для элемента формы "ГруппаСтраниц". Мы можем вынести логику этого обработчика в отдельную экспортную процедуру или сделать сам обработчик экспортным. Рассмотрим оба варианта.
Мы можем сделать сам обработчик экспортным. В модуле формы это будет выглядеть так:
// В модуле управляемой формы документа
&НаКлиенте
Процедура ГруппаСтраницПриСменеСтраницы(Элемент, ТекущаяСтраница) Экспорт // Добавляем слово "Экспорт"
// Здесь находится весь наш код, который должен выполняться при смене страницы
Сообщить("Выполнен код для страницы: " + ТекущаяСтраница.Имя);
// Например, обновление видимости элементов
ОбновитьВидимостьЭлементов(ТекущаяСтраница.Имя);
КонецПроцедуры
// Вспомогательная процедура, если нужна
&НаКлиенте
Процедура ОбновитьВидимостьЭлементов(ИмяСтраницы)
Если ИмяСтраницы = "СтраницаОсновная" Тогда
Элементы.Поле1.Видимость = Истина;
Элементы.Поле2.Видимость = Ложь;
ИначеЕсли ИмяСтраницы = "СтраницаДополнительная" Тогда
Элементы.Поле1.Видимость = Ложь;
Элементы.Поле2.Видимость = Истина;
КонецЕсли;
КонецПроцедуры
Обратите внимание, что процедура ГруппаСтраницПриСменеСтраницы принимает два параметра: Элемент (сама группа страниц) и ТекущаяСтраница (элемент формы, представляющий активную страницу). При программном вызове необходимо передать эти параметры.
Теперь, после того как мы программно открыли форму и изменили страницу, мы можем вызвать этот обработчик из клиентского модуля обработки или другого клиентского кода:
// В модуле обработки или другом клиентском модуле
&НаКлиенте
Процедура ВыполнитьДействиеПриОткрытииФормы()
// Открываем форму документа
ПараметрыОткрытия = Новый Структура;
// ... (заполнение параметров открытия, если нужно)
ФормаДокумента = ОткрытьФорму("Документ.МойДокумент.ФормаДокумента", ПараметрыОткрытия, , , , , РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
Если ФормаДокумента <> Неопределено Тогда
// Программно меняем страницу
// Дожидаемся, пока форма будет готова к работе, например, через обработчик ожидания,
// или если это происходит сразу после открытия на клиенте.
// Для простоты примера предположим, что форма уже проинициализирована.
// Устанавливаем нужную страницу
НужнаяСтраница = ФормаДокумента.Элементы.СтраницаДополнительная;
ФормаДокумента.Элементы.МояГруппаСтраниц.ТекущаяСтраница = НужнаяСтраница;
// Теперь явно вызываем наш экспортный обработчик
// Передаем правильные параметры: сама группа страниц и новая текущая страница
ФормаДокумента.ГруппаСтраницПриСменеСтраницы(ФормаДокумента.Элементы.МояГруппаСтраниц, НужнаяСтраница);
КонецЕсли;
КонецПроцедуры
Мы обязательно передаем в процедуру два параметра: объект группы страниц (ФормаДокумента.Элементы.МояГруппаСтраниц) и новую текущую страницу (НужнаяСтраница). Это соответствует стандартной сигнатуре события и позволяет обработчику корректно работать.
Мы можем вынести основную логику в отдельную экспортную процедуру, а сам обработчик будет лишь вызывать ее.
// В модуле управляемой формы документа
&НаКлиенте
Процедура ГруппаСтраницПриСменеСтраницы(Элемент, ТекущаяСтраница)
// Вызываем внутреннюю экспортную процедуру
ВыполнитьДействияПриСменеСтраницы(Элемент, ТекущаяСтраница);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьДействияПриСменеСтраницы(ГруппаСтраницЭлемент, НоваяТекущаяСтраница) Экспорт // Эта процедура теперь экспортная
// Здесь находится весь наш код, который должен выполняться при смене страницы
Сообщить("Выполнен код для страницы: " + НоваяТекущаяСтраница.Имя + " (через экспортную процедуру)");
// Например, обновление видимости элементов
ОбновитьВидимостьЭлементов(НоваяТекущаяСтраница.Имя);
КонецПроцедуры
// Вспомогательная процедура, если нужна
&НаКлиенте
Процедура ОбновитьВидимостьЭлементов(ИмяСтраницы)
Если ИмяСтраницы = "СтраницаОсновная" Тогда
Элементы.Поле1.Видимость = Истина;
Элементы.Поле2.Видимость = Ложь;
ИначеЕсли ИмяСтраницы = "СтраницаДополнительная" Тогда
Элементы.Поле1.Видимость = Ложь;
Элементы.Поле2.Видимость = Истина;
КонецЕсли;
КонецПроцедуры
Теперь из нашей обработки мы будем вызывать именно эту новую экспортную процедуру:
// В модуле обработки или другом клиентском модуле
&НаКлиенте
Процедура ВыполнитьДействиеПриОткрытииФормы()
// ... (код открытия формы, как выше)
ФормаДокумента = ОткрытьФорму("Документ.МойДокумент.ФормаДокумента", ПараметрыОткрытия, , , , , РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
Если ФормаДокумента <> Неопределено Тогда
НужнаяСтраница = ФормаДокумента.Элементы.СтраницаДополнительная;
ФормаДокумента.Элементы.МояГруппаСтраниц.ТекущаяСтраница = НужнаяСтраница;
// Вызываем нашу новую экспортную процедуру
ФормаДокумента.ВыполнитьДействияПриСменеСтраницы(ФормаДокумента.Элементы.МояГруппаСтраниц, НужнаяСтраница);
КонецЕсли;
КонецПроцедуры
Этот подход гарантирует, что вся необходимая логика будет выполнена, и при этом мы сохраняем чистоту кода, отделяя интерактивные обработчики от общей логики, которую можно вызывать программно.
Иногда прямой вызов обработчика может быть не самым элегантным или подходящим решением, особенно если логика связана с первоначальной инициализацией формы или требует специфического контекста. Рассмотрим другие методы.
Если код, который должен выполняться при смене страницы, фактически предназначен для подготовки данных или отображения элементов при первоначальном открытии формы на определенной странице, мы можем перенести эту логику в события, которые срабатывают при создании или открытии формы. Это особенно актуально, когда мы программно открываем форму и сразу устанавливаем нужную страницу.
Основные события, которые мы можем использовать:
ПриСозданииНаСервере: Срабатывает на сервере при первом создании формы. Здесь удобно инициализировать данные объекта формы, выполнять запросы, получать параметры.
// В модуле управляемой формы документа
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если Параметры.Свойство("ИнициализироватьНаСтранице") Тогда
ИмяСтраницыДляИнициализации = Параметры.ИнициализироватьНаСтранице;
// Установка текущей страницы должна происходить на клиенте,
// но на сервере мы можем подготовить данные или выполнить другие действия,
// связанные с этой страницей.
// Например, загрузка данных, специфичных для страницы
Если ИмяСтраницыДляИнициализации = "СтраницаСОтчетом" Тогда
// Вызов серверной процедуры для формирования отчета
ЗаполнитьДанныеОтчетаНаСервере();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
После серверной подготовки, на клиенте в событии ПриОткрытии или в другом клиентском обработчике, мы можем установить текущую страницу и, при необходимости, вызвать клиентскую часть логики.
ПриОткрытии: Срабатывает на клиенте после события ПриСозданииНаСервере, но до того, как форма будет показана пользователю. Это хорошее место для клиентской инициализации, установки видимости элементов, начальной установки страницы.
// В модуле управляемой формы документа
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Если Параметры.Свойство("ИнициализироватьНаСтранице") Тогда
ИмяСтраницыДляИнициализации = Параметры.ИнициализироватьНаСтранице;
// Находим нужную страницу по имени
Для Каждого Страница Из Элементы.МояГруппаСтраниц.Страницы Цикл
Если Страница.Имя = ИмяСтраницыДляИнициализации Тогда
Элементы.МояГруппаСтраниц.ТекущаяСтраница = Страница;
// После установки страницы, явно вызываем логику, которая должна была быть в ПриСменеСтраницы
// Если это была экспортная процедура, вызываем ее.
ВыполнитьДействияПриСменеСтраницы(Элементы.МояГруппаСтраниц, Страница);
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
При таком подходе мы передаем информацию о желаемой начальной странице через параметры открытия формы:
// В модуле обработки или другом клиентском модуле
&НаКлиенте
Процедура ВыполнитьДействиеПриОткрытииФормы()
ПараметрыОткрытия = Новый Структура("ИнициализироватьНаСтранице", "СтраницаДополнительная");
ФормаДокумента = ОткрытьФорму("Документ.МойДокумент.ФормаДокумента", ПараметрыОткрытия, , , , , РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
// Дальнейшие действия, если нужны
КонецПроцедуры
Этот метод позволяет полностью контролировать процесс инициализации формы и ее страниц с самого начала.
В некоторых сложных сценариях, когда логика смены страницы зависит от полной отрисовки формы или других асинхронных операций, которые происходят после открытия формы, может быть полезным использовать однократный обработчик ожидания.
Этот подход заключается в том, что мы устанавливаем обработчик ожидания, который сработает один раз через короткий интервал времени после открытия формы. В этом обработчике мы уже можем безопасно изменить страницу и вызвать соответствующую логику.
// В модуле управляемой формы документа
&НаКлиенте
Перем гТекущаяСтраницаДляИнициализации; // Переменная для хранения имени страницы
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Если Параметры.Свойство("ИнициализироватьНаСтранице") Тогда
гТекущаяСтраницаДляИнициализации = Параметры.ИнициализироватьНаСтранице;
// Подключаем обработчик ожидания, который сработает через 100 мс
ПодключитьОбработчикОжидания("УстановитьНачальнуюСтраницуПослеОткрытия", 0.1, Истина); // Истина - однократный
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура УстановитьНачальнуюСтраницуПослеОткрытия()
Если ЗначениеЗаполнено(гТекущаяСтраницаДляИнициализации) Тогда
Для Каждого Страница Из Элементы.МояГруппаСтраниц.Страницы Цикл
Если Страница.Имя = гТекущаяСтраницаДляИнициализации Тогда
Элементы.МояГруппаСтраниц.ТекущаяСтраница = Страница;
// Явно вызываем логику, которую должен был выполнить обработчик ПриСменеСтраницы
ВыполнитьДействияПриСменеСтраницы(Элементы.МояГруппаСтраниц, Страница);
Прервать;
КонецЕсли;
КонецЦикла;
гТекущаяСтраницаДляИнициализации = Неопределено; // Сбрасываем флаг
КонецЕсли;
КонецПроцедуры
Этот метод гарантирует, что все элементы формы будут полностью отрисованы и доступны к моменту выполнения нашей логики.
В рамках обсуждения прозвучала идея использовать событие ПриАктивизации для реквизита на странице. Этот подход является скорее обходным путем, чем прямым решением проблемы с ПриСменеСтраницы.
Идея заключается в том, чтобы разместить на каждой странице какой-либо элемент (например, скрытый реквизит) и перенести логику, которая должна выполняться при активации страницы, в обработчик события ПриАктивизации этого реквизита. При смене страницы реквизит на новой активной странице будет активирован, и его обработчик ПриАктивизации сработает.
Однако у этого подхода есть существенные недостатки:
ПриАктивизации может срабатывать в других случаях, не связанных напрямую со сменой страницы группы, что может привести к нежелательному поведению.ПриСменеСтраницы, которое имеет свою четкую семантику.Поэтому мы рекомендуем использовать явный вызов экспортной процедуры или перенос логики в события создания/открытия формы как более надежные и контролируемые методы.
При работе с программным вызовом процедур в 1С:Предприятии 8.3 крайне важно учитывать контекст выполнения (клиентский или серверный) и область видимости процедур.
&НаСервере или &НаСервереБезКонтекста. Вызов клиентской процедуры из другого клиентского модуля возможен, если она экспортная.
ВызватьНаСервере() или при обращении к серверному коду (например, к свойствам объекта).
В нашем случае, событие ПриСменеСтраницы является клиентским событием управляемой формы (поскольку оно напрямую связано с интерактивным взаимодействием с интерфейсом). Следовательно, и процедура, которую мы явно вызываем, должна быть клиентской и экспортной, а ее вызов должен происходить из клиентского контекста.
Мы с вами подробно рассмотрели проблему невсрабатывания события ПриСменеСтраницы при программном изменении текущей страницы управляемой формы в 1С:Предприятии 8.3. Мы выяснили, что это происходит из-за различий между интерактивными и программными действиями системы.
Основными и наиболее эффективными решениями являются:
ПриСменеСтраницы после программной установки новой страницы. Мы подчеркнули важность объявления процедуры как экспортной и передачи ей правильных параметров.
ПриСозданииНаСервере или ПриОткрытии, особенно если действия связаны с первоначальной загрузкой формы на определенной странице.
Использование однократного обработчика ожидания может быть полезным для более сложных сценариев, требующих полной отрисовки формы, в то время как "воркароун" с ПриАктивизации не рекомендуется из-за его ограниченности и потенциальных проблем.
Выбирайте тот подход, который наилучшим образом соответствует архитектуре вашего решения и специфике выполняемых при смене страницы действий. Всегда помните о контексте выполнения (клиент/сервер) и доступности процедур при их программном вызове.