Мы часто сталкиваемся с распространенной проблемой при работе с динамическими списками в 1С:Предприятии, когда система выдает ошибку "Не задано значение параметра". Эта ситуация возникает, когда параметр, используемый в запросе динамического списка, не инициализирован к моменту выполнения этого запроса. Давайте разберем эту проблему шаг за шагом, выясним ее причины и найдем эффективное решение, а также рассмотрим, как создавать вычисляемые поля для интерактивной работы со списком.
Чтобы полностью понять причину ошибки, нам необходимо рассмотреть, как взаимодействуют управляемая форма и динамический список при открытии. Это фундаментальный аспект работы с управляемым приложением, который определяет правильное место для инициализации данных.
ПриСозданииНаСервере. На этом этапе формируются все элементы формы, ее структура, а также происходит инициализация динамических списков, которые привязаны к этой форме.ПриОткрытии. Динамический список должен получить все необходимые параметры для формирования данных еще до того, как его запрос будет выполнен к информационной базе.ПриОткрытии.Из этого следует ключевой вывод: если параметр динамического списка используется в его запросе, он обязательно должен быть установлен до выполнения запроса. Соответственно, инициализация параметра должна произойти в серверном контексте, а именно в процедуре ПриСозданииНаСервере. Попытка установить параметр позже, например, в ПриОткрытии на клиенте, приведет к ошибке, так как запрос уже попытается выполниться без этого параметра.
Изначально, мы могли бы попробовать инициализировать параметр на клиенте, например, в обработчике ПриОткрытии. Это часто является первой интуитивной попыткой, но, как мы выяснили из жизненного цикла формы, она ошибочна. Рассмотрим пример кода, который приводит к ошибке "Не задано значение параметра":
&НаКлиенте Перем ВыбраннаяНоменклатура; //Массив, хранящий отмеченную галочками номенклатуру для отображения в списке
&НаКлиенте Процедура ПриОткрытии(Отказ)
Сообщить("Работает"); //Инициализация массива
ВыбраннаяНоменклатура = Новый Массив; //Задал параметр
Список.Параметры.УстановитьЗначениеПараметра("ВыбраннаяНоменклатура", ВыбраннаяНоменклатура);
КонецПроцедуры
В этом примере переменная ВыбраннаяНоменклатура объявлена на клиенте, и ее инициализация, а также установка параметра для динамического списка, происходит в процедуре ПриОткрытии. Как мы уже выяснили, процедура ПриОткрытии выполняется после того, как запрос динамического списка уже попытался выполниться на сервере. К этому моменту параметр ВыбраннаяНоменклатура еще не был установлен, что и вызывает ошибку: "Не задано значение параметра 'ВыбраннаяНоменклатура'".
Сообщение об ошибке, которое мы получаем, ясно указывает на причину и место возникновения проблемы:
Ошибка при выполнении запроса. по причине: Ошибка получения данных по причине: Ошибка создания набора данных "НаборДанныхДинамическогоСписка" по причине: Не задано значение параметра "ВыбраннаяНоменклатура" по причине: {(2, 25)}: Не задано значение параметра "ВыбраннаяНоменклатура" Номенклатура.Ссылка В (>&ВыбраннаяНоменклатура) КАК Пометка
Мы видим, что ошибка происходит еще на этапе создания набора данных динамического списка, то есть при формировании запроса к базе данных, до того, как клиентский код имел шанс отработать. Именно это и является ключевым моментом для понимания и исправления ошибки.
Правильное решение состоит в том, чтобы инициализировать параметр ВыбраннаяНоменклатура и установить его для динамического списка в обработчике ПриСозданииНаСервере. Это гарантирует, что к моменту выполнения запроса динамического списка параметр уже будет иметь значение, пусть даже пустым массивом. Это полностью соответствует жизненному циклу управляемой формы.
Рассмотрим исправленный код:
&НаСервере
Перем ВыбраннаяНоменклатура; //Массив, хранящий отмеченную галочками номенклатуру для отображения в списке
// Если это реквизит формы, то объявление здесь не требуется.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Инициализация массива должна быть на сервере, до выполнения запроса динамического списка
ВыбраннаяНоменклатура = Новый Массив;
// Установка параметра для динамического списка
// Без параметра, даже пустого, список выдаст ошибку, так как запрос будет выполнен
Список.Параметры.УстановитьЗначениеПараметра("ВыбраннаяНоменклатура", ВыбраннаяНоменклатура);
КонецПроцедуры
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// На клиенте в ПриОткрытии больше нет необходимости устанавливать параметр,
// так как он уже инициализирован на сервере.
// Здесь можно выполнять другие действия, которые не влияют на начальное формирование списка.
КонецПроцедуры
Обратите внимание на несколько важных моментов в исправленном коде:
ВыбраннаяНоменклатура объявлена в модуле формы. Если она должна быть доступна и в клиентском, и в серверном контекстах, а также сохранять свое состояние, лучше всего сделать ее реквизитом формы. Если это реквизит формы типа "Массив", то его объявление Перем ВыбраннаяНоменклатура; в начале модуля не требуется. Мы просто инициализируем его в ПриСозданииНаСервере.ВыбраннаяНоменклатура = Новый Массив; теперь происходит в обработчике ПриСозданииНаСервере, что гарантирует ее выполнение до построения и выполнения запроса динамического списка.Список.Параметры.УстановитьЗначениеПараметра("ВыбраннаяНоменклатура", ВыбраннаяНоменклатура); также находится в ПриСозданииНаСервере. Это делает параметр доступным для запроса.ПриОткрытии на клиенте теперь может быть пустой или содержать код, не влияющий на начальное формирование динамического списка.С таким подходом мы гарантируем, что параметр ВыбраннаяНоменклатура будет доступен и установлен до того, как динамический список попытается выполнить свой запрос, успешно избегая ошибки. Это является основой для правильной работы с параметризованными динамическими списками.
Когда мы работаем с динамическим списком и интерактивно изменяем массив ВыбраннаяНоменклатура (например, ставим или снимаем пометки с элементов списка), нам необходимо обновить сам список, чтобы эти изменения отразились в отображении. Динамический список не отслеживает изменения в массиве параметра автоматически.
Рассмотрим пример процедуры, которая реагирует на выбор элемента в списке и обновляет пометку:
&НаКлиенте
Процедура СписокВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
// Нас интересует только событие нажатия на чекбокс.
// Если пользователь нажал на колонку "Пометка", которая является булевым полем.
Если Поле = Элементы.СписокПометка Тогда
СтандартнаяОбработка = Ложь; // Отменяем стандартную обработку, чтобы управлять поведением вручную.
ТекущиеДанные = Элемент.ТекущиеДанные; // Получаем данные текущей строки списка.
Если ТекущиеДанные <> Неопределено Тогда
Если ТекущиеДанные.Пометка Тогда
// Если пометка была установлена, снимаем ее.
СнятьПометку(ТекущиеДанные.Ссылка);
Иначе
// Если пометка не была установлена, ставим ее.
ПоставитьПометку(ТекущиеДанные.Ссылка);
КонецЕсли;
КонецЕсли;
КонецЕсли;
// После изменения массива 'ВыбраннаяНоменклатура', параметр в динамическом списке нужно обновить
Список.Параметры.УстановитьЗначениеПараметра("ВыбраннаяНоменклатура", ВыбраннаяНоменклатура);
Элементы.Список.Обновить; // Принудительно обновляем динамический список, чтобы он перечитал данные с новым параметром.
КонецПроцедуры
&НаКлиенте
Процедура СнятьПометку(Номенклатура)
Индекс = ВыбраннаяНоменклатура.Найти(Номенклатура); // Ищем ссылку в массиве.
Если Индекс >= 0 Тогда
ВыбраннаяНоменклатура.Удалить(Индекс); // Если нашли, удаляем.
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПоставитьПометку(Номенклатура)
Индекс = ВыбраннаяНоменклатура.Найти(Номенклатура); // Ищем ссылку в массиве.
Если Индекс = -1 Тогда // Проверяем, что элемента нет, прежде чем добавить (Найти возвращает -1, если не найдено)
ВыбраннаяНоменклатура.Добавить(Номенклатура); // Если не нашли, добавляем.
КонецЕсли;
КонецПроцедуры
В этом коде после изменения массива ВыбраннаяНоменклатура (путем добавления или удаления ссылок на номенклатуру) мы снова вызываем Список.Параметры.УстановитьЗначениеПараметра(...) для передачи обновленного массива в динамический список. После этого необходимо вызвать Элементы.Список.Обновить, чтобы принудительно обновить данные списка и перерисовать его на форме. Это заставит динамический список выполнить свой запрос заново, используя новое значение параметра, и пересчитать поле "Пометка" для всех строк.
Обратите внимание, что в процедуре ПоставитьПометку добавлена проверка на наличие элемента в массиве (Индекс = -1), чтобы избежать дублирования одной и той же ссылки в массиве, что может привести к непредсказуемому поведению или лишнему расходу памяти.
Теперь рассмотрим, как само поле "Пометка" появляется в динамическом списке. Оно не является полем из базы данных, а вычисляется на основе нашего параметра ВыбраннаяНоменклатура. Это делается непосредственно в тексте запроса динамического списка.
Пример запроса для справочника "Номенклатура" с полем "Пометка":
ВЫБРАТЬ
Номенклатура.Ссылка,
Номенклатура.Ссылка В (&ВыбраннаяНоменклатура) КАК Пометка
ИЗ
Справочник.Номенклатура КАК Номенклатура
Конструкция Номенклатура.Ссылка В (&ВыбраннаяНоменклатура) КАК Пометка создает булево поле, которое будет ИСТИНА, если ссылка на текущий элемент номенклатуры (Номенклатура.Ссылка) содержится в массиве, переданном в параметре &ВыбраннаяНоменклатура, и ЛОЖЬ в противном случае. Таким образом, динамический список сам вычисляет значение "Пометка" для каждой строки на основе актуального состояния параметра.
Это очень эффективный способ формирования вычисляемых полей, так как вся логика выполняется на уровне СУБД, что минимизирует объем данных, передаваемых на клиент, и обеспечивает высокую производительность даже для больших списков. При необходимости можно ознакомиться с альтернативным способом реализации флажков в динамическом списке.
Нам часто требуется добавить такое вычисляемое поле в уже существующий, более сложный запрос динамического списка, например, для документов. Предположим, у нас есть запрос для документов "АктСписанияМЗ", и мы хотим добавить поле "Пометка", основанное на наличии ссылки на документ в некоем массиве &ВыбранныеДокументы. Принцип интеграции остается тем же.
Рассмотрим пример сложного запроса:
ВЫБРАТЬ
ДокументАктСписанияМЗ.Ссылка,
ДокументАктСписанияМЗ.ВерсияДанных,
ДокументАктСписанияМЗ.ПометкаУдаления,
ДокументАктСписанияМЗ.Номер,
ДокументАктСписанияМЗ.Дата,
ДокументАктСписанияМЗ.Проведен,
ДокументАктСписанияМЗ.Организация,
ДокументАктСписанияМЗ.ИФО,
ДокументАктСписанияМЗ.ПодразделениеОтправитель,
ДокументАктСписанияМЗ.ЦМООтправитель,
ДокументАктСписанияМЗ.Комментарий,
ДокументАктСписанияМЗ.ДолжностьПредседательКомиссии,
ДокументАктСписанияМЗ.ФамилияПредседательКомиссии,
ДокументАктСписанияМЗ.ИмяПредседательКомиссии,
ДокументАктСписанияМЗ.ОтчествоПредседательКомиссии,
ДокументАктСписанияМЗ.НомерПриказа,
ДокументАктСписанияМЗ.ДатаПриказа,
ДокументАктСписанияМЗ.Руководитель,
ДокументАктСписанияМЗ.ЗаключениеКомиссии,
ДокументАктСписанияМЗ.КФО,
ДокументАктСписанияМЗ.СчетУчета,
ОперацииДокументов.СуммаОперации,
ОперацииДокументов.Операция,
ВЫБОР КОГДА ОперацииДокументов.ЕстьПроводки ЕСТЬ NULL ТОГДА ЗНАЧЕНИЕ(Перечисление.СостоянияОперацииДокумента.ОперацияНеВыбрана)
КОГДА НЕ ОперацииДокументов.ЕстьПроводки ТОГДА ЗНАЧЕНИЕ(Перечисление.СостоянияОперацииДокумента.ПроводкиОтсутствуют)
КОГДА ЕСТЬNULL(ОперацииДокументов.ДокументСторнирован, ЛОЖЬ) = ИСТИНА ТОГДА ЗНАЧЕНИЕ(Перечисление.СостоянияОперацииДокумента.ДокументСторнирован)
КОГДА ЕСТЬNULL(ОперацииДокументов.РучнаяКорректировка, ЛОЖЬ) = ИСТИНА ТОГДА ЗНАЧЕНИЕ(Перечисление.СостоянияОперацииДокумента.ПроводкиИзмененыВручную)
КОГДА ЕСТЬNULL(ОперацииДокументов.ИсправлениеОшибки, ЗНАЧЕНИЕ(Перечисление.ПорядокИсправленияОшибокПрошлыхЛет.ПустаяСсылка)) В (ЗНАЧЕНИЕ(Перечисление.ПорядокИсправленияОшибокПрошлыхЛет.ОшибкаПрошлогоГода), ЗНАЧЕНИЕ(Перечисление.ПорядокИсправленияОшибокПрошлыхЛет.ОшибкаРанееПрошлогоГода)) ТОГДА ЗНАЧЕНИЕ(Перечисление.СостоянияОперацииДокумента.ИсправлениеОшибки)
ИНАЧЕ ЗНАЧЕНИЕ(Перечисление.СостоянияОперацииДокумента.ПроводкиСформированы)
КОНЕЦ КАК СостояниеОперации,
ВЫБОР КОГДА ОперацииДокументов.ЕстьПроводки ЕСТЬ NULL ТОГДА 0
КОГДА НЕ ОперацииДокументов.ЕстьПроводки ТОГДА 1
КОГДА ЕСТЬNULL(ОперацииДокументов.ДокументСторнирован, ЛОЖЬ) = ИСТИНА ТОГДА 4
КОГДА ЕСТЬNULL(ОперацииДокументов.РучнаяКорректировка, ЛОЖЬ) = ИСТИНА ТОГДА 3
КОГДА ЕСТЬNULL(ОперацииДокументов.ИсправлениеОшибки, ЗНАЧЕНИЕ(Перечисление.ПорядокИсправленияОшибокПрошлыхЛет.ПустаяСсылка)) В (ЗНАЧЕНИЕ(Перечисление.ПорядокИсправленияОшибокПрошлыхЛет.ОшибкаПрошлогоГода), ЗНАЧЕНИЕ(Перечисление.ПорядокИсправленияОшибокПрошлыхЛет.ОшибкаРанееПрошлогоГода)) ТОГДА 5
ИНАЧЕ 2
КОНЕЦ КАК СостояниеОперацииКод,
ДокументАктСписанияМЗ.Ответственный,
ВЫБОР КОГДА НаличиеФайлов.ЕстьФайлы ЕСТЬ NULL ТОГДА 0
КОГДА НаличиеФайлов.ЕстьФайлы ТОГДА 1
ИНАЧЕ 0
КОНЕЦ КАК ЕстьФайлы,
СостоянияЭД.ПредставлениеСостояния КАК СостояниеЭДО,
NULL КАК СостояниеОригиналаПервичногоДокумента,
ИСТИНА КАК ОбщееСостояние,
0 КАК СостояниеОригиналПолучен
ИЗ
Документ.АктСписанияМЗ КАК ДокументАктСписанияМЗ
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СостоянияЭД КАК СостоянияЭД
ПО (СостоянияЭД.СсылкаНаОбъект = ДокументАктСписанияМЗ.Ссылка)}
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОперацииДокументов КАК ОперацииДокументов
ПО ДокументАктСписанияМЗ.Ссылка = ОперацииДокументов.Документ}
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НаличиеФайлов КАК НаличиеФайлов
ПО ДокументАктСписанияМЗ.Ссылка = НаличиеФайлов.ОбъектСФайлами}
Для добавления поля "Пометка" к этому запросу, нам нужно просто добавить соответствующую строку в список выбираемых полей, аналогично тому, как мы это делали для номенклатуры. Мы вставляем ее в начале списка полей, но ее расположение внутри оператора ВЫБРАТЬ не имеет критического значения, главное, чтобы она присутствовала:
ВЫБРАТЬ
ДокументАктСписанияМЗ.Ссылка,
ДокументАктСписанияМЗ.Ссылка В (&ВыбранныеДокументы) КАК Пометка, // Добавляем вычисляемое поле для отметки
ДокументАктСписанияМЗ.ВерсияДанных,
ДокументАктСписанияМЗ.ПометкаУдаления,
ДокументАктСписанияМЗ.Номер,
ДокументАктСписанияМЗ.Дата,
// ... остальные поля ...
ИЗ
Документ.АктСписанияМЗ КАК ДокументАктСписанияМЗ
// ... все ранее существовавшие соединения ...
Обратите внимание, что мы добавляем ДокументАктСписанияМЗ.Ссылка В (&ВыбранныеДокументы) КАК Пометка в список выбираемых полей. Параметр &ВыбранныеДокументы должен быть инициализирован и управляться аналогично параметру &ВыбраннаяНоменклатура, то есть в процедуре ПриСозданииНаСервере и обновляться при каждом изменении массива на клиенте с последующим вызовом Элементы.Список.Обновить. Для изучения различных подходов к добавлению колонок пригодится руководство по методам доработки динамических списков.
Еще одна распространенная задача – пометить документы в динамическом списке, если их номера (или ссылки) отсутствуют в определенном регистре сведений. При появлении соответствующей записи в регистре, пометка должна сниматься, и наоборот. В этом случае мы можем полностью отказаться от клиентского массива и передаваемых параметров, реализовав всю логику в запросе динамического списка.
Для решения этой задачи нам не нужно изменять клиентский код, обрабатывающий пометки, поскольку поле "Пометка" будет вычисляемым в запросе. Вместо этого мы модифицируем запрос динамического списка, используя левое соединение (ЛЕВОЕ СОЕДИНЕНИЕ) с нужным регистром сведений и проверку на NULL значения поля из этого регистра.
Предположим, у нас есть регистр сведений, который хранит ссылки на документы, которые должны быть "помечены" (или, наоборот, не должны быть помечены, в зависимости от логики). Пусть это будет условный регистр РегистрСведений.ДокументыДляПометки, который содержит измерение ДокументСсылка (тип Ссылка.ДокументАктСписанияМЗ).
Тогда запрос будет выглядеть следующим образом:
ВЫБРАТЬ
ДокументАктСписанияМЗ.Ссылка,
ДокументАктСписанияМЗ.ВерсияДанных,
// ... другие поля документа ...
// Показывает, есть ли запись в регистре: ИСТИНА, если запись есть, ЛОЖЬ, если нет
ЕСТЬNULL(ДокументыДляПометки.ДокументСсылка, ЛОЖЬ) КАК ДокументПрисутствуетВРегистре,
// Вычисляем финальную пометку: ИСТИНА, если документа НЕТ в регистре, ЛОЖЬ, если он ЕСТЬ
НЕ ЕСТЬNULL(ДокументыДляПометки.ДокументСсылка, ЛОЖЬ) КАК Пометка,
// ... остальные поля и вычисления ...
ИЗ
Документ.АктСписанияМЗ КАК ДокументАктСписанияМЗ
// Выполняем левое соединение с нашим регистром сведений
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДокументыДляПометки КАК ДокументыДляПометки
ПО ДокументАктСписанияМЗ.Ссылка = ДокументыДляПометки.ДокументСсылка
// ... другие соединения с регистрами ...
Разберем этот подход подробнее:
ЛЕВОЕ СОЕДИНЕНИЕ таблицы Документ.АктСписанияМЗ с нашим регистром сведений РегистрСведений.ДокументыДляПометки по полю ссылки на документ (ДокументАктСписанияМЗ.Ссылка = ДокументыДляПометки.ДокументСсылка).Документ.АктСписанияМЗ в регистре ДокументыДляПометки есть соответствующая запись, то все поля из ДокументыДляПометки (включая ДокументыДляПометки.ДокументСсылка) будут заполнены значениями из регистра.Документ.АктСписанияМЗ в регистре ДокументыДляПометки нет соответствующей записи (т.е., условие соединения не выполнилось), то все поля из ДокументыДляПометки будут иметь значение NULL.ЕСТЬNULL(ПолеИзРегистра, ЛОЖЬ), мы получаем ИСТИНА, если запись в регистре есть, и ЛОЖЬ, если ее нет. Мы можем использовать это как промежуточное поле, например, ДокументПрисутствуетВРегистре.ИСТИНА, если запись отсутствует в регистре (и, следовательно, документ нужно пометить), мы просто инвертируем это значение с помощью логического оператора НЕ: НЕ ЕСТЬNULL(ДокументыДляПометки.ДокументСсылка, ЛОЖЬ) КАК Пометка.Таким образом, поле "Пометка" будет автоматически вычисляться запросом динамического списка, и нам не потребуется массив на клиенте или серверные вызовы для установки параметра. Когда данные в регистре сведений изменятся (например, будут добавлены или удалены записи о документах), достаточно будет обновить динамический список (Элементы.Список.Обновить), и пометки будут пересчитаны автоматически на основе актуальных данных регистра. Этот подход значительно упрощает логику, повышает производительность и делает решение более масштабируемым, поскольку вся логика вычисления пометки перенесена на уровень запроса к базе данных.
Для эффективной и безошибочной работы с динамическими списками в 1С:Предприятии, особенно при использовании параметров и вычисляемых полей, мы рекомендуем придерживаться следующих принципов:
&НаКлиенте) или на сервере (&НаСервере). Операции, влияющие на данные информационной базы, запросы к ней, а также первичная инициализация сложных объектов формы (как в случае с динамическим списком и его параметрами), как правило, должны выполняться на сервере. Клиентский код предназначен для интерактивного взаимодействия с пользователем и управления формой.Ссылка В (&Параметр) или НЕ ЕСТЬNULL(ПолеИзРегистра, ЛОЖЬ)) значительно производительнее, чем вычислять их в обработчике ПриВыводеСтроки табличного поля. Последний вариант следует использовать только для очень сложной логики, которую невозможно выразить в запросе, или для небольших объемов данных, так как он выполняется для каждой отображаемой строки.Список.Параметры.Найти("ИмяПараметра"), что поможет избежать ошибок в рантайме.Следуя этим рекомендациям, мы сможем эффективно работать с динамическими списками, избегая распространенных ошибок, связанных с инициализацией параметров, отображением вычисляемых полей и общей производительностью приложения. Это позволяет создавать надежные и отзывчивые пользовательские интерфейсы в 1С:Предприятии.