При разработке пользовательских интерфейсов на управляемых формах 1С:Предприятия часто возникает задача отображения дополнительной информации о текущей строке динамического списка в другом элементе формы, например, в таблице значений. В этом случае мы сталкиваемся с необходимостью корректно обработать событие активации строки, выполнить запрос к базе данных на сервере и обновить данные на клиенте. Разберем подробнее, как решить эту задачу, а также какие нюансы могут возникнуть на пути.
Наш пользователь столкнулся с ситуацией, когда серверная процедура, отвечающая за заполнение таблицы значений, по его мнению, «не отрабатывает». Цель — при нажатии на строку динамического списка на форме вывести детализированные данные по этой строке в таблице значений на той же форме. Для этого использовано событие ПриАктивизацииСтроки.
Давайте проанализируем предоставленный код:
&НаКлиенте
Процедура РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПосле(Элемент)
ТекущаяСтрока = Элементы.СписокРасширенныйПоискНоменклатура.ТекущаяСтрока;
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПослеНаСервере(ТекущаяСтрока);
КонецПроцедуры
&НаСервере
Процедура РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПослеНаСервере(ТекущаяСтрока)
ЗапросДляТЗ = Новый Запрос;
ЗапросДляТЗ.Текст = "ВЫБРАТЬ
| ТоварыКОтгрузкеОстатки.Номенклатура КАК Номенклатура,
| ТоварыКОтгрузкеОстатки.Характеристика КАК Характеристика,
| ТоварыКОтгрузкеОстатки.Склад КАК Склад,
| ТоварыКОтгрузкеОстатки.КОтгрузкеОстаток КАК КОтгрузкеОстаток
|ПОМЕСТИТЬ ТоварыКОтгрузке
|ИЗ
| РегистрНакопления.ТоварыКОтгрузке.Остатки КАК ТоварыКОтгрузкеОстатки
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СвободныеОстаткиОстатки.Номенклатура КАК Номенклатура,
| СвободныеОстаткиОстатки.ВНаличииОстаток КАК ВНаличииОстаток,
| СвободныеОстаткиОстатки.ВРезервеСоСкладаОстаток КАК ВРезервеСоСкладаОстаток
|ПОМЕСТИТЬ СвободныеОстатки
|ИЗ
| РегистрНакопления.СвободныеОстатки.Остатки КАК СвободныеОстаткиОстатки
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТоварыКОтгрузке.Номенклатура КАК Номенклатура,
| ТоварыКОтгрузке.Склад КАК Склад,
| ТоварыКОтгрузке.Характеристика КАК Характеристика,
| СвободныеОстатки.ВНаличииОстаток КАК ВНаличии,
| СвободныеОстатки.ВРезервеСоСкладаОстаток КАК ВРезерве,
| ТоварыКОтгрузке.КОтгрузкеОстаток КАК Отгружается,
| СвободныеОстатки.ВНаличииОстаток - СвободныеОстатки.ВРезервеСоСкладаОстаток - ТоварыКОтгрузке.КОтгрузкеОстаток КАК Доступно
|ИЗ
| ТоварыКОтгрузке КАК ТоварыКОтгрузке
| ЛЕВОЕ СОЕДИНЕНИЕ СвободныеОстатки КАК СвободныеОстатки
| ПО ТоварыКОтгрузке.Номенклатура = СвободныеОстатки.Номенклатура
|ГДЕ
| ТоварыКОтгрузке.Номенклатура.Ссылка = &ТекущаяСтрока";
ЗапросДляТЗ.УстановитьПараметр("ТекущаяСтрока", ТекущаяСтрока);
РезультатДляТЗ = ЗапросДляТЗ.Выполнить.Выгрузить();
ТаблицаСтроки.Загрузить(РезультатДляТЗ);
КонецПроцедуры
На первый взгляд, логика выстроена правильно: на клиенте получаем текущую строку, передаем на сервер, где выполняется запрос и данные загружаются в таблицу. Однако в процессе обсуждения выяснилось несколько важных моментов.
Первое, что мы должны выяснить, когда сталкиваемся с сообщением «серверная процедура не отрабатывает», это действительно ли она не вызывается или просто не дает ожидаемого результата. Для этого нам понадобится отладчик.
1C:Enterprise 8.2 Server Agent (или 8.3, в зависимости от версии).Windows + R, введите regedit и нажмите Enter).[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\services\1C:Enterprise 8.X Server Agent\].ImagePath. К его значению добавьте параметр -debug в конце строки."C:\Program Files (x86)\1cv8\8.3.XX.XXXX\bin\ragent.exe" -srvc -agent -regport 1541 -port 1540 -range 1560:1591 -d "C:\Program Files (x86)\1cv8\srvinfo""C:\Program Files (x86)\1cv8\8.3.XX.XXXX\bin\ragent.exe" -srvc -agent -regport 1541 -port 1540 -range 1560:1591 -d "C:\Program Files (x86)\1cv8\srvinfo" -debugРасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПослеНаСервере.Важно: Включение режима отладки может повлиять на производительность сервера, поэтому на продуктивных системах его рекомендуется включать только для диагностики.
Одна из наиболее частых ошибок при работе с динамическими списками — это некорректная передача параметра текущей строки в запрос. Свойство Элементы.Список.ТекущаяСтрока возвращает *идентификатор* текущей строки динамического списка. В большинстве случаев, когда мы хотим отфильтровать запрос по конкретному элементу, нам нужна *ссылка* на этот элемент.
В оригинальном коде параметр запроса устанавливается так:
ЗапросДляТЗ.УстановитьПараметр("ТекущаяСтрока", ТекущаяСтрока);
А в запросе используется ГДЕ ТоварыКОтгрузке.Номенклатура.Ссылка = &ТекущаяСтрока.
Если ТекущаяСтрока, полученная из Элементы.СписокРасширенныйПоискНоменклатура.ТекущаяСтрока, является просто идентификатором строки списка, а не ссылкой на объект "Номенклатура", то прямое сравнение с полем Номенклатура.Ссылка будет работать некорректно или приводить к ошибкам типа несоответствия типов.
Исправляем передачу параметра: Мы должны получить ссылку на объект, к которому относится текущая строка динамического списка.
Если динамический список основан на регистре или справочнике, и поле Номенклатура является ссылкой, то мы можем получить ссылку следующим образом:
&НаКлиенте
Процедура РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПосле(Элемент)
// Получаем текущие данные строки динамического списка
ТекущиеДанныеСтроки = Элемент.ТекущиеДанные;
Если ТекущиеДанныеСтроки = Неопределено Тогда
Возврат;
КонецЕсли;
// Передаем ссылку на номенклатуру из текущих данных
// Предполагаем, что в текущих данных списка есть поле "Номенклатура",
// которое содержит ссылку на справочник "Номенклатура".
СсылкаНоменклатуры = ТекущиеДанныеСтроки.Номенклатура;
РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПослеНаСервере(СсылкаНоменклатуры);
КонецПроцедуры
&НаСервере
Процедура РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПослеНаСервере(СсылкаНоменклатуры)
ЗапросДляТЗ = Новый Запрос;
ЗапросДляТЗ.Текст = "ВЫБРАТЬ
| ТоварыКОтгрузкеОстатки.Номенклатура КАК Номенклатура,
| ТоварыКОтгрузкеОстатки.Характеристика КАК Характеристика,
| ТоварыКОтгрузкеОстатки.Склад КАК Склад,
| ТоварыКОтгрузкеОстатки.КОтгрузкеОстаток КАК КОтгрузкеОстаток
|ПОМЕСТИТЬ ТоварыКОтгрузке
|ИЗ
| РегистрНакопления.ТоварыКОтгрузке.Остатки КАК ТоварыКОтгрузкеОстатки
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СвободныеОстаткиОстатки.Номенклатура КАК Номенклатура,
| СвободныеОстаткиОстатки.ВНаличииОстаток КАК ВНаличииОстаток,
| СвободныеОстаткиОстатки.ВРезервеСоСкладаОстаток КАК ВРезервеСоСкладаОстаток
|ПОМЕСТИТЬ СвободныеОстатки
|ИЗ
| РегистрНакопления.СвободныеОстатки.Остатки КАК СвободныеОстаткиОстатки
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТоварыКОтгрузке.Номенклатура КАК Номенклатура,
| ТоварыКОтгрузке.Склад КАК Склад,
| ТоварыКОтгрузке.Характеристика КАК Характеристика,
| СвободныеОстатки.ВНаличииОстаток КАК ВНаличии,
| СвободныеОстатки.ВРезервеСоСкладаОстаток КАК ВРезерве,
| ТоварыКОтгрузке.КОтгрузкеОстаток КАК Отгружается,
| СвободныеОстатки.ВНаличииОстаток - СвободныеОстатки.ВРезервеСоСкладаОстаток - ТоварыКОтгрузке.КОтгрузкеОстаток КАК Доступно
|ИЗ
| ТоварыКОтгрузке КАК ТоварыКОтгрузке
| ЛЕВОЕ СОЕДИНЕНИЕ СвободныеОстатки КАК СвободныеОстатки
| ПО ТоварыКОтгрузке.Номенклатура = СвободныеОстатки.Номенклатура
|ГДЕ
| ТоварыКОтгрузке.Номенклатура = &СсылкаНоменклатуры"; // Изменено условие для параметра
ЗапросДляТЗ.УстановитьПараметр("СсылкаНоменклатуры", СсылкаНоменклатуры); // Изменено имя параметра
РезультатДляТЗ = ЗапросДляТЗ.Выполнить().Выгрузить();
// Дальнейший код для загрузки в ТаблицаСтроки
КонецПроцедуры
В этом примере мы используем Элемент.ТекущиеДанные, чтобы получить структуру или объект с данными текущей строки, и из него извлекаем поле Номенклатура, которое, как мы предполагаем, содержит ссылку. Это более надежный способ, чем прямое использование Элементы.Список.ТекущаяСтрока, особенно если список произвольный.
После того как мы убедились, что серверная процедура вызывается и параметры передаются корректно, следующим шагом является проверка данных, возвращаемых запросом.
РезультатДляТЗ = ЗапросДляТЗ.Выполнить().Выгрузить();.РезультатДляТЗ: После выполнения запроса в окне отладчика изучите содержимое переменной РезультатДляТЗ.В обсуждаемом случае, именно на этом этапе выяснилось, что запрос возвращал пустую таблицу значений. Это означало, что по выбранной номенклатуре просто не было остатков к отгрузке или свободных остатков, удовлетворяющих условиям запроса. Следовательно, процедура работала корректно, но нечего было отображать.
Важно: Всегда проверяйте, возвращает ли ваш запрос данные, которые вы ожидаете. Это поможет избежать ложных выводов о «неработоспособности» кода.
Когда данные получены на сервере, их нужно передать обратно на клиент и отобразить в элементе формы. Способ обновления зависит от того, как элемент формы связан с данными.
В исходном коде использовалось: ТаблицаСтроки.Загрузить(РезультатДляТЗ);.
Здесь возможны два основных сценария:
Если ТаблицаСтроки — это просто реквизит формы типа "ТаблицаЗначений", который отображается элементом формы типа "Таблица", то после заполнения этого реквизита на сервере необходимо явно указать платформе обновить его на клиенте. Для этого используется функция ЗначениеВРеквизитФормы().
&НаСервере
Процедура РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПослеНаСервере(СсылкаНоменклатуры)
// ... Ваш запрос ...
РезультатДляТЗ = ЗапросДляТЗ.Выполнить().Выгрузить();
// Загружаем данные в реквизит формы "ТаблицаСтроки"
// Предполагаем, что "ТаблицаСтроки" - это реквизит формы типа ТаблицаЗначений
ТаблицаСтроки.Очистить(); // Очищаем старые данные
ТаблицаСтроки.Загрузить(РезультатДляТЗ);
// Передаем обновленные данные реквизита формы на клиент
ЗначениеВРеквизитФормы(ТаблицаСтроки, "ТаблицаСтроки");
КонецПроцедуры
Важно: Функция ЗначениеВРеквизитФормы() требует два параметра: сам объект (реквизит формы) и его имя в виде строки. Ошибка "Недопустимое значение параметра (параметр номер '2')" может возникать, если имя реквизита указано неверно или функция вызывается в неподходящем контексте (например, с табличной частью объекта).
Если ТаблицаСтроки на форме — это элемент, связанный с табличной частью основного объекта формы (например, Объект.ТабличнаяЧасть1), то подход будет иным. В этом случае мы работаем непосредственно с данными объекта формы.
&НаСервере
Процедура РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПослеНаСервере(СсылкаНоменклатуры)
// ... Ваш запрос ...
РезультатДляТЗ = ЗапросДляТЗ.Выполнить().Выгрузить();
// Предполагаем, что на форме есть основной реквизит формы "Объект"
// и у него есть табличная часть "ТаблицаСтроки".
Объект.ТаблицаСтроки.Очистить(); // Очищаем старые данные
Объект.ТаблицаСтроки.Загрузить(РезультатДляТЗ);
// В этом случае обычно не требуется явный вызов ЗначениеВРеквизитФормы(),
// платформа сама обновит отображение при изменении данных объекта.
КонецПроцедуры
Такой подход часто работает "из коробки", и платформа автоматически обновляет связанный элемент на клиенте при изменении данных в серверном контексте. Именно этот вариант, судя по сообщению 20, в итоге и помог пользователю.
ТекущиеДанные вместо ТекущаяСтрока:
Для динамических списков, особенно при работе с произвольными запросами, свойство Элементы.Список.ТекущиеДанные может быть более предпочтительным, чем Элементы.Список.ТекущаяСтрока. ТекущиеДанные возвращает объект (или структуру), содержащий значения полей текущей строки динамического списка, что более удобно для доступа к данным, в том числе к ссылкам. Именно его мы использовали в Шаге 2.
Хотя в данном случае проблема была не в этом, для повышения производительности и соблюдения архитектурных принципов, если серверная процедура не обращается к реквизитам формы, её рекомендуется делать бесконтекстной. Для этого используем директиву &НаСервереБезКонтекста. Это означает, что при вызове процедуры контекст формы (её реквизиты) не будет передаваться на сервер и обратно, что сокращает трафик.
&НаКлиенте
Процедура РасшФормы_СписокРасширенныйПоискНоменклатураПриАктивизацииСтрокиПосле(Элемент)
ТекущиеДанныеСтроки = Элемент.ТекущиеДанные;
Если ТекущиеДанныеСтроки = Неопределено Тогда
Возврат;
КонецЕсли;
СсылкаНоменклатуры = ТекущиеДанныеСтроки.Номенклатура; // Получаем ссылку
// Вызов бесконтекстной процедуры
РезультатЗапроса = РасшФормы_СписокРасширенныйПоискНоменклатураПолучитьДанныеНаСервереБезКонтекста(СсылкаНоменклатуры);
// Загрузка результата на клиенте, если ТаблицаСтроки - это реквизит формы
Если РезультатЗапроса <> Неопределено Тогда
ЭтаФорма.ТаблицаСтроки.Очистить();
ЭтаФорма.ТаблицаСтроки.Загрузить(РезультатЗапроса);
// Если ТаблицаСтроки - это табличная часть объекта, то логика меняется
// Объект.ТаблицаСтроки.Загрузить(РезультатЗапроса);
// В этом случае серверная процедура должна принимать ссылку на объект и изменять его
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция РасшФормы_СписокРасширенныйПоискНоменклатураПолучитьДанныеНаСервереБезКонтекста(СсылкаНоменклатуры)
ЗапросДляТЗ = Новый Запрос;
ЗапросДляТЗ.Текст = "ВЫБРАТЬ // ... ваш запрос ...";
ЗапросДляТЗ.УстановитьПараметр("СсылкаНоменклатуры", СсылкаНоменклатуры);
Возврат ЗапросДляТЗ.Выполнить().Выгрузить();
КонецФункции
В этом примере серверная процедура становится функцией, которая возвращает результат запроса на клиент, где уже происходит обновление элементов формы.
ПриАктивизацииСтроки:
Событие ПриАктивизацииСтроки срабатывает, когда изменяется *индекс* активной строки в таблице формы. Если данные в текущей строке изменились (например, из-за фильтрации или поиска), но сама строка (её позиция) осталась той же, событие может не сработать. Для более надежного обновления данных при изменении содержимого текущей строки (например, после поиска), возможно, потребуется использовать дополнительные механизмы, такие как сохранение идентификатора текущей строки в реквизит формы и периодическая проверка его изменения с помощью ОбработчикаОжидания на клиенте, или принудительный вызов обновления после действий, которые могли изменить состав списка.
Если после серверного вызова данные на форме не обновляются автоматически, мы можем принудительно обновить их. Для этого существуют методы:
ЭтаФорма.ОбновитьОтображениеДанных(): Обновляет все элементы формы, привязанные к реквизитам формы.Элементы.ИмяЭлементаФормы.Обновить(): Обновляет конкретный элемент формы.Эти методы вызываются на клиенте после получения данных с сервера.
Частые вызовы серверных процедур по событию ПриАктивизацииСтроки, особенно с ресурсоемкими запросами, могут негативно сказаться на производительности интерфейса. Старайтесь оптимизировать запросы и минимизировать объем передаваемых данных. Если возможно, кэшируйте данные или используйте отложенную загрузку.
Таким образом, решение проблемы заключалось в комплексном подходе: отладочная проверка серверного кода, коррекция передачи параметров в запрос, анализ результатов запроса и, при необходимости, правильное обновление элементов формы. Все эти шаги позволяют добиться желаемого результата и обеспечить стабильную работу приложения.