В процессе разработки на платформе 1С:Предприятие 8.3 мы часто сталкиваемся с необходимостью предоставлять пользователю выбор из определенного набора данных. Распространенной задачей является создание временной таблицы значений (ТЗ), ее заполнение и последующее предложение пользователю выбрать одну из строк. Однако, при попытке использовать метод ВыбратьСтроку() объекта ТаблицаЗначений в серверном коде, мы можем столкнуться с ошибкой "значение не является значением объектного типа(Номенклатура)", как это произошло в ситуации, описанной в нашем случае. Давайте разберем эту проблему и найдем эффективные способы ее решения.
Предположим, у нас есть следующий фрагмент кода, который выполняется на сервере:
Если ВыборкаНоменк.Количество()> 1 Тогда
ВыборНоменклатуры = Новый ТаблицаЗначений;
ВыборНоменклатуры.Колонки.Добавить("Номенклатура", ,"Номенклатура");
ВыборНоменклатуры.Колонки.Добавить("Статус", ,"Статус");
ВыборНоменклатуры.Колонки.Добавить("КомментарийПриема", ,"КомментарийПриема");
Пока ВыборкаНоменк.Следующий() Цикл
стр = ВыборНоменклатуры.Добавить();
ЗаполнитьЗначенияСвойств(стр, ВыборкаНоменк);
КонецЦикла;
Выбор = ВыборНоменклатуры.ВыбратьСтроку();
Номенклатура = Выбор.Номенклатура;
ТекущийСтатус = Выбор.Статус;
КонецЕсли;
При выполнении этого кода на сервере мы сталкиваемся с ошибкой "значение не является значением объектного типа(Номенклатура)" в строке Номенклатура = Выбор.Номенклатура;. При отладке выясняется, что переменная Выбор имеет значение Неопределено. Это происходит потому, что метод ВыбратьСтроку(), как и многие другие методы, предназначенные для взаимодействия с пользовательским интерфейсом (открытие диалоговых окон, форм, списков), может быть вызван только на клиенте.
Почему так происходит? Сервер 1С:Предприятия не имеет пользовательского интерфейса. Он занимается обработкой данных, выполнением запросов и бизнес-логики, но у него нет "экрана" или "мыши", чтобы показать пользователю диалог выбора или принять от него ввод. Когда мы вызываем ВыбратьСтроку() на сервере, система не может выполнить запрошенное действие (открыть модальное окно выбора) и вместо этого возвращает Неопределено. Соответственно, любая попытка обратиться к свойствам такого неопределенного значения (например, Выбор.Номенклатура) приведет к упомянутой ошибке.
Таким образом, основная причина проблемы заключается в неправильном использовании клиентского метода ВыбратьСтроку() в серверном контексте. Для решения этой задачи нам необходимо перенести логику взаимодействия с пользователем на клиент.
Мы выяснили, что интерактивный выбор должен происходить на клиенте. Рассмотрим несколько подходов к организации такого выбора, когда данные для него подготавливаются на сервере.
Этот способ является наиболее гибким и мощным, если нам требуется сложный механизм выбора с возможностью фильтрации, сортировки или отображения большого объема данных. Мы создаем отдельную управляемую форму, которая будет отображать нашу таблицу значений, и открываем ее программно на клиенте. Результат выбора с этой формы мы получаем через механизм оповещения.
Разберем по шагам, как это реализовать:
Сначала мы создаем новую управляемую форму (например, "ФормаВыбораСтрокиТЗ"). На эту форму мы добавляем элемент "Таблица", который будет отображать данные нашей ТаблицыЗначений. Для этого можно использовать динамический список или создать реквизит формы типа ТаблицаЗначений.
Мы также добавляем на форму кнопки "Выбрать" и "Отмена". При нажатии на "Выбрать" мы должны передать выбранную строку обратно вызывающей стороне. Для этого на форме выбора создаем процедуру, которая будет возвращать выбранные данные. Например, в процедуре кнопки "Выбрать" мы можем использовать Закрыть(ТекущиеДанныеФормы.Элементы.ИмяТаблицы.ТекущиеДанные), где ТекущиеДанные — это выбранная строка.
Поскольку ТаблицаЗначений является мутабельным объектом и не может быть напрямую передана с сервера на клиент без сериализации, мы преобразуем ее на сервере в более подходящий для передачи тип, например, в Массив структур или Массив соответствий. Затем этот массив передаем в клиентскую процедуру.
На клиенте мы можем использовать этот массив для заполнения реквизита формы типа ТаблицаЗначений или напрямую заполнить данные для динамического списка формы выбора.
Из серверной процедуры мы вызываем клиентскую процедуру (помеченную &НаКлиенте), которая будет открывать форму выбора. В эту клиентскую процедуру мы передаем подготовленные на сервере данные (например, массив структур).
На клиенте мы используем глобальный метод ОткрытьФорму() для открытия нашей формы выбора. Ключевым моментом здесь является параметр ОписаниеОповещенияОЗакрытии. Это объект, который описывает процедуру-обработчик на клиенте, которая будет вызвана после закрытия модально открытой формы выбора, передавая результат выбора.
Рассмотрим пример кода:
// В серверной процедуре:
&НаСервере
Процедура НашаСервернаяПроцедура()
// ... создание и заполнение ВыборНоменклатуры на сервере ...
// Преобразуем ТаблицуЗначений в массив структур для передачи на клиент
МассивДанныхДляВыбора = Новый Массив;
Для Каждого СтрокаТЗ Из ВыборНоменклатуры Цикл
МассивДанныхДляВыбора.Добавить(СтрокаТЗ.ПолучитьСтруктуру()); // Или можно заполнить структуру вручную
КонецЦикла;
// Вызываем клиентскую процедуру для открытия формы выбора
ОткрытьФормуВыбораНаКлиенте(МассивДанныхДляВыбора);
КонецПроцедуры
// В клиентской процедуре:
&НаКлиенте
Процедура ОткрытьФормуВыбораНаКлиенте(МассивДанныхДляВыбора)
ПараметрыФормы = Новый Структура("ИсходныеДанные", МассивДанныхДляВыбора);
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораСтроки", ЭтотОбъект);
ОткрытьФорму("Обработка.ИмяОбработки.Форма.ФормаВыбораСтрокиТЗ", ПараметрыФормы, ЭтотОбъект, УникальныйИдентификатор, , , ОписаниеОповещения, РежимОткрытияОкна.БлокироватьВесьИнтерфейс);
КонецПроцедуры
// Обработчик оповещения на клиенте (процедура, которая будет вызвана после закрытия формы выбора)
&НаКлиенте
Процедура ПослеВыбораСтроки(ВыбраннаяСтрока, ДополнительныеПараметры) Экспорт
Если ВыбраннаяСтрока <> Неопределено Тогда
// Здесь мы получили выбранную строку из формы
// ВыбраннаяСтрока будет иметь тип Структура, если мы так настроили закрытие формы выбора
Номенклатура = ВыбраннаяСтрока.Номенклатура;
ТекущийСтатус = ВыбраннаяСтрока.Статус;
// ... дальнейшая обработка выбранных данных ...
Сообщить("Выбрана номенклатура: " + Номенклатура);
Иначе
Сообщить("Выбор отменен.");
КонецЕсли;
КонецПроцедуры
На форме выбора, в процедуре, которая закрывает форму с результатом (например, по кнопке "Выбрать"), будет код вида:
// На форме выбора (Например, в команде "Выбрать")
&НаКлиенте
Процедура Выбрать(Команда)
Если Элементы.ТаблицаСДанными.ТекущаяСтрока <> Неопределено Тогда
// Возвращаем текущую выбранную строку как структуру
Закрыть(Элементы.ТаблицаСДанными.ТекущиеДанные.ПолучитьСтруктуру());
Иначе
Закрыть(Неопределено); // Если ничего не выбрано
КонецЕсли;
КонецПроцедуры
ВыбратьИзСписка() или ПоказатьВыборИзСписка()Если нам нужен простой выбор из небольшого списка значений без сложного интерфейса, мы можем использовать методы ВыбратьИзСписка() (модальный) или его немодальный аналог ПоказатьВыборИзСписка(). Эти методы работают с объектом СписокЗначений.
Рассмотрим применение:
На сервере мы подготавливаем данные так же, как и для формы выбора, но вместо массива структур, мы можем создать СписокЗначений, если это удобно, или массив структур, который затем будет преобразован в СписокЗначений на клиенте.
Мы передаем данные на клиент, как и в первом варианте, и вызываем клиентскую процедуру.
СпискаЗначений и вызов метода на клиенте:
На клиенте мы формируем объект СписокЗначений. Каждый элемент списка будет иметь свойство "Значение" (то, что будет возвращено при выборе) и "Представление" (то, что увидит пользователь).
Затем мы используем ВыбратьИзСписка() или ПоказатьВыборИзСписка(). Для ПоказатьВыборИзСписка() также потребуется ОписаниеОповещения.
Пример кода:
// В серверной процедуре:
&НаСервере
Процедура НашаСервернаяПроцедура_2()
// ... создание и заполнение ВыборНоменклатуры на сервере ...
// Преобразуем ТаблицуЗначений в массив структур
МассивДанныхДляВыбора = Новый Массив;
Для Каждого СтрокаТЗ Из ВыборНоменклатуры Цикл
МассивДанныхДляВыбора.Добавить(СтрокаТЗ.ПолучитьСтруктуру());
КонецЦикла;
// Вызываем клиентскую процедуру
ПоказатьВыборИзСпискаНаКлиенте(МассивДанныхДляВыбора);
КонецПроцедуры
// В клиентской процедуре:
&НаКлиенте
Процедура ПоказатьВыборИзСпискаНаКлиенте(МассивДанныхДляВыбора)
СписокДляВыбора = Новый СписокЗначений;
Для Каждого СтрокаСтруктура Из МассивДанныхДляВыбора Цикл
// Значение будет сама структура строки, Представление - Номенклатура + Статус
СписокДляВыбора.Добавить(СтрокаСтруктура, СтрокаСтруктура.Номенклатура + " (" + СтрокаСтруктура.Статус + ")");
КонецЦикла;
Если СписокДляВыбора.Количество() > 0 Тогда
// Для модального вызова (если Режим использования модальности разрешен)
// ВыбранноеЗначение = ВыбратьИзСписка(СписокДляВыбора, Элементы.ПолеВвода);
// Если ВыбранноеЗначение <> Неопределено Тогда
// Номенклатура = ВыбранноеЗначение.Номенклатура;
// ТекущийСтатус = ВыбранноеЗначение.Статус;
// КонецЕсли;
// Для немодального вызова (рекомендуется)
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораИзСписка", ЭтотОбъект);
ПоказатьВыборИзСписка(ОписаниеОповещения, СписокДляВыбора, Элементы.ПолеВвода);
КонецЕсли;
КонецПроцедуры
// Обработчик оповещения на клиенте
&НаКлиенте
Процедура ПослеВыбораИзСписка(ВыбранноеЗначение, ДополнительныеПараметры) Экспорт
Если ВыбранноеЗначение <> Неопределено Тогда
// ВыбранноеЗначение здесь будет той структурой, которую мы передали в СписокЗначений.Добавить()
Номенклатура = ВыбранноеЗначение.Номенклатура;
ТекущийСтатус = ВыбранноеЗначение.Статус;
Сообщить("Выбрана номенклатура: " + Номенклатура);
Иначе
Сообщить("Выбор отменен.");
КонецЕсли;
КонецПроцедуры
Обратите внимание, что ВыбратьИзСписка() блокирует интерфейс, пока пользователь не сделает выбор, что может быть нежелательно в современных управляемых приложениях. ПоказатьВыборИзСписка() является предпочтительным вариантом, особенно если конфигурация настроена на "Не использовать модальность".
Хотя ТаблицаЗначений напрямую не передается с сервера на клиент, мы можем преобразовать ее в формат, который поддается передаче, а затем восстановить на клиенте. Наиболее распространенный способ — это преобразование в Массив структур или Массив соответствий.
Рассмотрим процесс:
Как и в предыдущих случаях, на сервере мы преобразуем нашу ТаблицуЗначений в Массив структур. Каждая структура будет соответствовать строке таблицы и содержать ее колонки как свойства.
// Пример на сервере
&НаСервере
Функция ПолучитьДанныеДляВыбораСервер()
// ... создание и заполнение ВыборНоменклатуры на сервере ...
// Пусть у нас есть ВыборНоменклатуры (ТаблицаЗначений)
МассивДанных = Новый Массив;
Для Каждого СтрокаТЗ Из ВыборНоменклатуры Цикл
СтруктураСтроки = Новый Структура;
Для Каждого КолонкаТЗ Из ВыборНоменклатуры.Колонки Цикл
СтруктураСтроки.Вставить(КолонкаТЗ.Имя, СтрокаТЗ[КолонкаТЗ.Имя]);
КонецЦикла;
МассивДанных.Добавить(СтруктураСтроки);
КонецЦила;
Возврат МассивДанных;
КонецФункции
Полученный Массив структур передается в клиентскую процедуру.
// В серверной процедуре:
&НаСервере
Процедура НашаСервернаяПроцедура_3()
МассивДанныхДляВыбора = ПолучитьДанныеДляВыбораСервер();
ПоказатьВыборНаКлиентеИзМассива(МассивДанныхДляВыбора);
КонецПроцедуры
ТаблицыЗначений и интерактивный выбор на клиенте:
На клиенте мы можем из этого массива структур вновь создать ТаблицуЗначений. Затем мы можем предложить пользователю выбор из этой ТЗ различными способами, например, через временный реквизит формы типа ТаблицаЗначений, который будет отображаться в управляемом элементе "Таблица", или через программное открытие формы выбора (как в Варианте 1).
// В клиентской процедуре:
&НаКлиенте
Процедура ПоказатьВыборНаКлиентеИзМассива(МассивДанныхДляВыбора)
// Создаем ТаблицуЗначений на клиенте из полученного массива
КлиентскаяТЗ = Новый ТаблицаЗначений;
Если МассивДанныхДляВыбора.Количество() > 0 Тогда
// Создаем колонки по первой структуре
Для Каждого КлючЗначение Из МассивДанныхДляВыбора[0] Цикл
КлиентскаяТЗ.Колонки.Добавить(КлючЗначение.Ключ);
КонецЦикла;
// Заполняем ТаблицуЗначений
Для Каждого СтруктураСтроки Из МассивДанныхДляВыбора Цикл
НоваяСтрока = КлиентскаяТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтруктураСтроки);
КонецЦикла;
КонецЕсли;
// Теперь, когда КлиентскаяТЗ создана на клиенте,
// мы можем использовать ее для выбора, например, привязав к реквизиту формы
// и открыв форму выбора (как в Варианте 1) или использовав ее для других целей.
// Например, если на форме есть реквизит "ТаблицаВыбора" типа ТаблицаЗначений,
// и элемент формы привязан к нему:
// ТаблицаВыбора = КлиентскаяТЗ;
// Здесь можно вызвать форму выбора, передав ей КлиентскаяТЗ или ее элементы
// или предложить выбор другим способом (например, через СписокЗначений)
// Пример с созданием СпискаЗначений на клиенте и ПоказатьВыборИзСписка
СписокДляВыбора = Новый СписокЗначений;
Для Каждого СтрокаТЗ_Кл Из КлиентскаяТЗ Цикл
СписокДляВыбора.Добавить(СтрокаТЗ_Кл.ПолучитьСтруктуру(), СтрокаТЗ_Кл.Номенклатура + " (" + СтрокаТЗ_Кл.Статус + ")");
КонецЦикла;
Если СписокДляВыбора.Количество() > 0 Тогда
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораИзСписка", ЭтотОбъект);
ПоказатьВыборИзСписка(ОписаниеОповещения, СписокДляВыбора, Элементы.ПолеВвода);
КонецЕсли;
КонецПроцедуры
При разработке в 1С:Предприятии мы всегда должны стремиться к минимизации клиент-серверных вызовов. Каждый такой вызов требует передачи данных по сети и увеличивает время отклика приложения, особенно в веб-клиенте или при медленном соединении.
&НаСервереБезКонтекста:
Если серверная процедура не требует доступа к контексту формы (например, к реквизитам формы), используйте эту директиву. Это позволяет сократить объем передаваемых данных при вызове сервера.
Не передавайте на клиент или сервер больше данных, чем абсолютно необходимо для выполнения текущей операции.
Постарайтесь сгруппировать несколько логически связанных операций в один клиент-серверный вызов, вместо того чтобы выполнять каждый шаг отдельным вызовом.
Начиная с версии 8.3.3, в платформе 1С:Предприятие введены ограничения на использование модальных окон. Рекомендуется использовать немодальные аналоги методов и механизмы оповещения, чтобы обеспечить лучшую отзывчивость интерфейса, особенно в веб-клиенте.
Таким образом, проблема с ошибкой "значение не является значением объектного типа" при использовании ВыбратьСтроку() на сервере является классическим примером некорректного клиент-серверного взаимодействия. Понимая это разделение и используя адекватные методы для переноса интерактивных операций на клиент, мы можем создавать более надежные, производительные и удобные для пользователя решения в 1С.