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