Разработка вспомогательных инструментов, таких как «Инспектор форм», «Исследователь интерфейса» или динамические обработчики для управляемых форм — классическая задача в экосистеме 1С. Однако при ее реализации разработчики неизбежно сталкиваются с фундаментальным ограничением платформы: строгой изоляцией контекста управляемой формы.
В этой статье мы подробно разберем, почему нельзя просто «взять» серверный контекст чужой формы из внешней обработки, какие ограничения накладывает трехзвенная архитектура 1С и какими практическими путями (от штатного тестирования до глубокого обхода ограничений платформы) можно решить эту задачу.
Выясним причины этого ограничения и подробно рассмотрим 5 работающих способов реализации «исследователя форм».
Для начала проанализируем архитектуру управляемого приложения. Управляемая форма существует в двух ипостасях:
УправляемаяФорма. Здесь доступны элементы управления, параметры отображения и клиентские методы. Большинство реальных данных и метаданных на клиенте отсутствует.&НаСервере). Как только серверный вызов завершается, контекст сериализуется, передается обратно на клиент, а серверная копия уничтожается.Платформа 1С категорически запрещает передавать клиентский объект УправляемаяФорма в качестве параметра на сервер в «чужие» методы (например, в экспортный метод общего модуля или внешней обработки). Передать форму на сервер как контекстный объект (где она имеет полноценный тип УправляемаяФорма) можно только в процедуру модуля самой этой формы.
Если мы попытаемся передать форму в сторонний серверный метод, платформа выдаст ошибку сериализации или передаст лишь ограниченную, пустую ссылку. Тем не менее, решить задачу анализа структуры формы можно. Рассмотрим доступные варианты.
Если ваша цель — создать надежный инструмент для исследования открытых форм (подсвечивание элементов на экране, получение путей к данным, типов и названий кнопок), то самым правильным решением будет использование встроенного API тестирования 1С. Этот способ вообще не требует изменения кода конфигурации или использования «хаков».
Разберем этот механизм по шагам:
/TESTCLIENT./TESTMANAGER (менеджер тестирования).Посмотрим на пример кода во внешней обработке:
// Подключаемся к запущенному клиенту тестирования
ПортТестирования = 1538; // Стандартный порт или настроенный вами
Попытка
ТестируемоеПриложение.Подключить(ПортТестирования);
Исключение
Сообщить("Не удалось подключить клиент тестирования: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Получаем список открытых окон
ОкнаПриложения = ТестируемоеПриложение.ПолучитьПодчиненныеОбъекты();
Для Каждого ТекущееОкно Из ОкнаПриложения Цикл
// Ищем активную форму
Если ТекущееОкно.Основное Тогда
АктивнаяФорма = ТекущееОкно.ПолучитьАктивнуюФорму();
Если АктивнаяФорма <> Неопределено Тогда
// Мы получили объект типа ТестируемаяФорма!
// Теперь мы можем обойти все ее элементы программно
РазберемЭлементыФормы(АктивнаяФорма.Элементы);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Метод РазберемЭлементыФормы может рекурсивно обходить коллекцию элементов, получая их свойства:
Процедура РазберемЭлементыФормы(Элементы)
Для Каждого Элемент Из Элементы Цикл
ИмяЭлемента = Элемент.Имя;
ТипЭлемента = ТипЗнч(Элемент);
// Для полей ввода можно получить путь к данным
Если ТипЭлемента = Тип("ТестируемоеПолеФормы") Тогда
ПутьКДанным = Элемент.ПутьКДанным;
Сообщить(СтрШаблон("Поле: %1, Путь к данным: %2", ИмяЭлемента, ПутьКДанным));
КонецЕсли;
// Рекурсивно обходим подчиненные элементы (группы, таблицы)
Если Элемент.ПолучитьПодчиненныеОбъекты().Количество() > 0 Тогда
РазберемЭлементыФормы(Элемент.Элементы);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Преимущества: Полная безопасность, стабильность, поддержка платформой, возможность программно подсвечивать (активизировать) элементы на клиенте тестирования.
Если запускать систему в режиме тестирования невозможно или неудобно, мы можем выполнить базовый анализ структуры элементов прямо из обычной внешней обработки на клиенте. Для этого воспользуемся методом глобального контекста ПолучитьОкна().
Разберем, как получить доступ к элементам открытой формы без серверного вызова:
&НаКлиенте
Процедура ИсследоватьОткрытыеФормы()
ОткрытыеОкна = ПолучитьОкна();
Для Каждого Окно Из ОткрытыеОкна Цикл
// Исключаем системные и пустые окна
Если Окно.Основное Или Окно.Доступно Тогда
СодержимоеОкна = Окно.Содержимое;
Для Каждого ОбъектСодержимого Из СодержимоеОкна Цикл
// Проверяем, является ли объект управляемой формой
Если ТипЗнч(ОбъектСодержимого) = Тип("УправляемаяФорма") Тогда
ПроанализироватьЭлементыКлиента(ОбъектСодержимого);
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ПроанализироватьЭлементыКлиента(ФормаОбъекта)
Сообщить("Исследуем форму: " + ФормаОбъекта.ИмяФормы);
// На клиенте доступна коллекция Элементы
Для Каждого Элемент Из ФормаОбъекта.Элементы Цикл
// Мы можем прочитать базовые свойства: Имя, Видимость, Доступность, ПутьКДанным (для некоторых элементов)
Сообщить(СтрШаблон("Элемент: %1, Тип: %2", Элемент.Имя, ТипЗнч(Элемент)));
КонецДля;
КонецПроцедуры
Важное ограничение: На клиенте у элементов управляемой формы недоступны многие важные свойства (например, назначенные серверные обработчики событий, детальные метаданные реквизитов и т.д.). Попытка обратиться к ним вызовет ошибку выполнения.
Если конфигурация построена на базе Библиотеки Стандартных Подсистем (БСП), практически каждая форма объекта (документа, справочника) содержит встроенный механизм подключаемых команд (заполнение, печать, отчеты). Мы можем использовать этот механизм, чтобы «заставить» форму саму передать свой серверный контекст в нашу внешнюю обработку.
Выясним, как это работает:
Форма.ПараметрыПодключаемыхКоманд.ДополнительныеОтчетыИОбработки как обработка заполнения объекта.ВыполнитьКоманду(), где параметром выступает ПолучательОбъекта (фактически, серверный контекст формы).Это штатный и очень мощный способ выполнения произвольного серверного кода над формой в рамках стандартов БСП.
Если у нас есть возможность минимально вмешаться в код исследуемой формы (или сделать это через расширение, о чем пойдет речь ниже), мы можем при создании формы собрать все ее серверные свойства в специальный скрытый реквизит.
Проанализируем пример реализации такого подхода в модуле исследуемой формы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Динамически добавляем на форму реквизит для хранения структуры контекста
МассивРеквизитов = Новый Массив;
МассивРеквизитов.Добавить(Новый РеквизитФормы("КонтекстФормы", Новый ОписаниеТипов("Неопределено")));
ИзменитьРеквизиты(МассивРеквизитов);
// Заполняем структуру данными, которые доступны только на сервере
ЭтаФорма.КонтекстФормы = Новый Структура;
МассивДействий = Новый Массив;
МассивДействий.Добавить("ПриИзменении");
МассивДействий.Добавить("ПередНачаломДобавления");
Для Каждого Элемент Из Элементы Цикл
// Создаем описание для каждого элемента
СвойстваЭлемента = Новый Структура("Имя, ПутьКДанным, События", Элемент.Имя, "", Новый Соответствие);
ЗаполнитьЗначенияСвойств(СвойстваЭлемента, Элемент);
// Пытаемся получить серверные обработчики событий
Если ТипЗнч(Элемент) = Тип("ПолеФормы") Или ТипЗнч(Элемент) = Тип("ТаблицаФормы") Тогда
Для Каждого Действие Из МассивДействий Цикл
Попытка
ИмяПроцедуры = Элемент.ПолучитьДействие(Действие);
Если Не ПустаяСтрока(ИмяПроцедуры) Тогда
СвойстваЭлемента.События.Вставить(Действие, ИмяПроцедуры);
КонецЕсли;
Исключение
// Данное событие не поддерживается типом элемента
КонецПопытки;
КонецДля;
КонецЕсли;
ЭтаФорма.КонтекстФормы.Вставить(Элемент.Имя, СвойстваЭлемента);
КонецДля;
КонецПроцедуры
Теперь из внешней обработки на клиенте мы можем легко прочитать этот реквизит: ФормаОбъекта.КонтекстФормы и получить полную карту серверных свойств, включая назначенные обработчики событий!
Наиболее изящный способ, не требующий изменения основной конфигурации и позволяющий выполнять произвольный код на сервере в контексте любой формы — это использование расширения конфигурации.
Разберем по шагам, как реализовать этот подход:
МойМодульСервер) с возможностью выполнения произвольного кода:
Функция ВыполнитьКодВКонтексте(КонтекстФормы, ТекстКода) Экспорт
// Через выполнить мы можем запустить любой код,
// но нам нужно передать сам контекст формы
Результат = Неопределено;
Попытка
// В ТекстКода можно написать, например: "Результат = КонтекстФормы.Объект.Ссылка;"
Выполнить(ТекстКода);
Исключение
Результат = ОписаниеОшибки();
КонецПопытки;
Возврат Результат;
КонецФункции
ПриСозданииНаСервере для интересующих нас форм (или вообще для всех форм документов/справочников через общую форму/шаблоны) с аннотацией &После:
&После("ПриСозданииНаСервере")
Процедура Мой_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Динамически добавляем кнопку "Исследовать форму" на командную панель формы
НоваяКоманда = Команды.Добавить("Мой_КомандаИсследоватьФорму");
НоваяКоманда.Действие = "Мой_ДействиеИсследоватьФорму";
НоваяКнопка = Элементы.Добавить("Мой_КнопкаИсследовать", Тип("КнопкаФормы"), Элементы.КоманднаяПанель);
НоваяКнопка.ИмяКоманды = "Мой_КомандаИсследоватьФорму";
НоваяКнопка.Заголовок = "Исследовать форму";
КонецПроцедуры
ЭтаФорма в наш общий модуль расширения:
&НаКлиенте
Процедура Мой_ДействиеИсследоватьФорму(Команда)
Мой_ДействиеИсследоватьФормуНаСервере();
КонецПроцедуры
&НаСервере
Процедура Мой_ДействиеИсследоватьФормуНаСервере()
// Здесь мы находимся внутри родного серверного контекста формы!
// Мы можем передать "ЭтаФорма" в наш обработчик
ТекстКода = "Результат = Новый Структура; "
+ "Результат.Вставить(""Реквизиты"", КонтекстФормы.ПолучитьРеквизитыФормы());";
ДанныеФормы = МойМодульСервер.ВыполнитьКодВКонтексте(ЭтаФорма, ТекстКода);
// Далее можем передать эти данные во внешнюю обработку или вывести на экран
КонецПроцедуры
Почему это работает? Мы обошли ограничение на передачу контекста формы, потому что вызов инициирован самой формой (кнопкой на ней), а управление передано в общий модуль расширения уже на сервере, где тип УправляемаяФорма полностью сохраняет свою работоспособность.
Мы проанализировали ограничения платформы 1С и разобрали различные варианты получения контекста формы:
/TESTCLIENT + /TESTMANAGER и объект ТестируемаяФорма.ПолучитьОкна().ЭтаФорма в серверный модуль расширения.