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