При разработке интерфейсов на обычных формах в 1С:Предприятие 8 часто возникает классическая задача: связать два табличных поля по принципу «главный — подчиненный» (Master-Detail). Например, при выборе номенклатуры в первом списке во втором списке должны отображаться только ее аналоги, хранящиеся в независимом регистре сведений.
В этой статье мы подробно разберем, как правильно реализовать такой механизм, избежав при этом типичных ошибок начинающих разработчиков, которые могут привести к катастрофическому падению производительности всей системы.
---
Для реализации динамической фильтрации подчиненного списка нам понадобится обрабатывать событие активизации строки в основном списке номенклатуры. Давайте разберем этот процесс по шагам.
Основная ошибка, которую часто совершают новички (как показано в Сообщении 6 темы), заключается в попытке передать текстовое наименование вместо ссылки в параметры запроса или отбора, либо в неверном обращении к свойствам элементов формы. Динамические списки в 1С работают со ссылочными типами данных, поэтому передавать в отбор нужно именно ссылку на элемент справочника.
Выполним пошаговую настройку механизма:
ПриАктивизацииСтроки.Рассмотрим правильный и безопасный шаблон кода для реализации этой логики:
Процедура СправочникСписокПриАктивизацииСтроки(Элемент)
// Получаем текущую строку основного списка номенклатуры
ТекущиеДанные = Элемент.ТекущаяСтрока;
// Обязательно проверяем на Неопределено (например, если список пуст)
Если ТекущиеДанные = Неопределено Тогда
// Если строка не выбрана, отключаем отбор в подчиненном списке
ЭлементыФормы.ТабличноеПолеАналоги.Значение.Отбор.Номенклатура.Использование = Ложь;
Возврат;
КонецЕсли;
// Проверяем, что это ссылка на элемент, а не группа номенклатуры
Если ТекущиеДанные.ЭтоГруппа Тогда
ЭлементыФормы.ТабличноеПолеАналоги.Значение.Отбор.Номенклатура.Использование = Ложь;
Возврат;
КонецЕсли;
// Устанавливаем быстрый отбор по измерению "Номенклатура"
// Метод Установить() принимает два параметра: Значение и Использование (по умолчанию Истина)
ЭлементыФормы.ТабличноеПолеАналоги.Значение.Отбор.Номенклатура.Установить(ТекущиеДанные, Истина);
КонецПроцедуры
Почему это работает быстро? Тип данных подчиненного поля — РегистрСведенийСписок. Платформа 1С оптимизирована для работы со стандартными отборами динамических списков. При установке отбора через объект Отбор СУБД выполняет быстрый параметризованный запрос по индексированному полю (измерения регистра сведений всегда индексируются по умолчанию).
---
В Сообщении 12 автор пытается решить более сложную задачу — вывести текущие складские остатки и артикулы для найденных аналогов. Для этого он использует событие ПриВыводеСтроки, в котором выполняет запрос к базе данных для каждой строки.
Внимание! Это критическая ошибка проектирования в 1С.
Проанализируем ситуацию: событие ПриВыводеСтроки вызывается платформой интерактивно при отрисовке интерфейса. Оно срабатывает при каждом скроллинге списка, изменении размеров окна, простом наведении мыши или активации ячейки. Если на экране отображается 30 строк аналогов, система выполнит 30 отдельных запросов к СУБД. При прокрутке списка счетчик запросов пойдет на тысячи. Пользователь гарантированно столкнется с "зависанием" интерфейса и эффектом слайд-шоу.
Для решения подобных задач в обычных формах существует специализированное событие — ПриПолученииДанных. Разберем его ключевое отличие:
ПриВыводеСтроки вызывается поочередно для каждой отдельной строки.ПриПолученииДанных вызывается один раз для всего пакета строк, подготовленных к отображению на экране (обычно это порция из 30-45 строк).Посмотрим на пример правильной реализации получения остатков и артикулов в событии ПриПолученииДанных подчиненного списка аналогов:
Процедура ТабличноеПолеАналогиПриПолученииДанных(Элемент, ОформленияСтрок)
// Создаем массив для сбора уникальных ссылок на аналоги
МассивАналогов = Новый Массив;
// Шаг 1: Собираем только видимые на экране ссылки
Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
ДанныеСтроки = ОформлениеСтроки.ДанныеСтроки;
Если ДанныеСтроки <> Неопределено И ЗначениеЗаполнено(ДанныеСтроки.Аналог) Тогда
МассивАналогов.Добавить(ДанныеСтроки.Аналог);
КонецЕсли;
КонецЦикла;
// Если список пуст — делать запрос нет смысла
Если МассивАналогов.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Шаг 2: Выполняем ОДИН пакетный запрос ко всей порции данных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Аналог,
| Номенклатура.Артикул КАК Артикул,
| ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивАналогов)) КАК ТоварыНаСкладахОстатки
| ПО Номенклатура.Ссылка = ТоварыНаСкладахОстатки.Номенклатура
|ГДЕ
| Номенклатура.Ссылка В(&МассивАналогов)";
Запрос.УстановитьПараметр("МассивАналогов", МассивАналогов);
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
// Шаг 3: Переносим результат в Соответствие для сверхбыстрого поиска в памяти
КэшДанных = Новый Соответствие;
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
СтруктураСвойств = Новый Структура("Артикул, Остаток", СтрокаДанных.Артикул, СтрокаДанных.Остаток);
КэшДанных.Вставить(СтрокаДанных.Аналог, СтруктураСвойств);
КонецЦикла;
// Шаг 4: Быстро распределяем данные по ячейкам оформления строк
Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
ДанныеСтроки = ОформлениеСтроки.ДанныеСтроки;
Если ДанныеСтроки <> Неопределено Тогда
НайденныеДанные = КэшДанных.Получить(ДанныеСтроки.Аналог);
Если НайденныеДанные <> Неопределено Тогда
// Заполняем виртуальные колонки формы текстом
ОформлениеСтроки.Ячейки.Артикул.УстановитьТекст(НайденныеДанные.Артикул);
ОформлениеСтроки.Ячейки.Остаток.УстановитьТекст(НайденныеДанные.Остаток);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важное архитектурное замечание: Колонки "Артикул" и "Остаток" в подчиненном табличном поле должны быть добавлены вручную в свойствах элемента формы в конфигураторе. Они не должны быть связаны с данными самого регистра (свойство Данные у этих колонок должно оставаться пустым). Заполнение их значений происходит исключительно программно через метод УстановитьТекст() объекта оформления строки.
---
В Сообщении 14 затрагивается еще одна важная проблема обычных форм: невозможность наложения платформенных отборов на расчетные виртуальные данные (такие как текущие остатки товаров).
В отличие от управляемых форм 1С (где мы можем произвольно изменить схему компоновки данных или текст запроса в свойствах ДинамическийСписок), объект типа РегистрСведенийСписок в обычных формах жестко привязан к одной таблице базы данных. Наложить стандартный отбор мы можем только по тем колонкам, которые физически присутствуют в самом регистре сведений.
Для реализации отбора «скрывать аналоги с нулевыми остатками» у нас есть два пути решения.
Вместо использования динамического списка регистра сведений, мы можем сделать подчиненное табличное поле обычной таблицей значений на форме. При активизации строки основного списка мы будем полностью перезаполнять эту таблицу с помощью запроса, который сразу отсечет строки с нулевыми остатками.
Рассмотрим пример реализации этого подхода в процедуре ПриАктивизацииСтроки:
Процедура СправочникСписокПриАктивизацииСтроки(Элемент)
ТекущаяНоменклатура = Элемент.ТекущаяСтрока;
Если ТекущаяНоменклатура = Неопределено Или ТекущаяНоменклатура.ЭтоГруппа Тогда
ТабличноеПолеАналоги.Очистить();
Возврат;
КонецЕсли;
// Запрос соединяет аналоги с остатками и сразу фильтрует по остатку > 0
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Аналоги.Аналог КАК Аналог,
| Аналоги.Аналог.Артикул КАК Артикул,
| ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
|ИЗ
| РегистрСведений.Аналоги КАК Аналоги
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, ) КАК ТоварыНаСкладахОстатки
| ПО Аналоги.Аналог = ТоварыНаСкладахОстатки.Номенклатура
|ГДЕ
| Аналоги.Номенклатура = &Номенклатура
| И ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) > 0";
Запрос.УстановитьПараметр("Номенклатура", ТекущаяНоменклатура);
// Выгружаем данные напрямую в ТаблицуЗначений, связанную с элементом формы
ТабличноеПолеАналоги = Запрос.Combine().Выполнить().Выгрузить();
КонецПроцедуры
Преимущества: Высокая скорость работы, гибкость построения любых отборов средствами языка запросов SQL, простота реализации.
Недостатки: Теряется встроенный интерактивный функционал динамического списка (добавление, удаление, редактирование строк пользователем непосредственно в табличном поле потребует дополнительного программного кода для синхронизации изменений с базой данных).
Если вам обязательно нужно сохранить именно тип данных РегистрСведенийСписок для подчиненного поля (например, для обеспечения возможности быстрого редактирования регистра пользователем), можно использовать динамический отбор по массиву подходящих ключей.
Разберем логику:
Аналог с видом сравнения В Списке.
Процедура СправочникСписокПриАктивизацииСтроки(Элемент)
ТекущаяНоменклатура = Элемент.ТекущаяСтрока;
Если ТекущаяНоменклатура = Неопределено Тогда
ЭлементыФормы.ТабличноеПолеАналоги.Значение.Отбор.Аналог.Использование = Ложь;
Возврат;
КонецЕсли;
// Ищем аналоги с ненулевым остатком
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Аналоги.Аналог КАК Аналог
|ИЗ
| РегистрСведений.Аналоги КАК Аналоги
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, ) КАК ТоварыНаСкладахОстатки
| ПО Аналоги.Аналог = ТоварыНаСкладахОстатки.Номенклатура
|ГДЕ
| Аналоги.Номенклатура = &Номенклатура
| И ТоварыНаСкладахОстатки.ВНаличииОстаток > 0";
Запрос.УстановитьПараметр("Номенклатура", ТекущаяНоменклатура);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
МассивАналогов = РезультатЗапроса.ВыгрузитьКолонку("Аналог");
// Если подходящих аналогов нет, передаем пустой массив, чтобы список очистился
ОтборПоАналогам = ЭлементыФормы.ТабличноеПолеАналоги.Значение.Отбор.Аналог;
ОтборПоАналогам.ВидСравнения = ВидСравнения.ВСписке;
ОтборПоАналогам.Использование = Истина;
ОтборПоАналогам.Значение.ЗагрузитьЗначения(МассивАналогов);
КонецПроцедуры
Преимущества: Сохраняется полноценный платформенный динамический список со всеми его интерактивными возможностями редактирования.
Недостатки: Если количество подходящих аналогов составит сотни или тысячи элементов, СУБД может дольше обрабатывать условие IN (список_значений) в генерируемом SQL-запросе под капотом.
---
Мы проанализировали классическую задачу связи динамических списков на обычных формах 1С и выявили три ключевых правила для любого разработчика:
СправочникСсылка, а не текстовые представления или наименования.ПриВыводеСтроки. Для любых расчетных данных, выводимых на форму (артикулы, остатки, цены), используйте исключительно событие ПриПолученииДанных с обработкой пачки строк за один запрос.РегистрСведенийСписок в пользу классической таблицы значений, заполняемой через оптимизированный запрос.