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