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