Работа с таблицами на управляемой форме — одна из самых частых задач в разработке на платформе 1С:Предприятие 8.3. Начинающие разработчики часто сталкиваются с трудностями при заполнении табличных частей и таблиц значений: от простых синтаксических ошибок до критических проблем с производительностью системы и клиент-серверным взаимодействием. В этой статье мы подробно разберем, как правильно организовать программное заполнение колонок таблицы на форме, избежав типичных ошибок.
Рассмотрим стандартную задачу: на форме создана таблица (реквизит формы с типом ДанныеФормыКоллекция), содержащая колонки Номенклатура, Артикул и Группа. Нам необходимо заполнить ее данными из базы.
Типичная ошибка новичков — построчный обход выборки запроса и добавление строк вручную в цикле. Такой подход сильно замедляет работу системы. Вместо этого мы применим метод Загрузить(), который мгновенно переносит данные из таблицы значений в реквизит формы.
Разберем пример серверной процедуры, которая выполняет запрос к справочнику номенклатуры и заполняет таблицу на форме:
&НаСервере
Процедура ЗаполнитьТаблицуНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.Родитель КАК Группа
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа";
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
// Загружаем данные в реквизит формы (например, Объект.Товары или КаталогНСИ)
Объект.КаталогНСИ.Загрузить(ТаблицаРезультат);
КонецПроцедуры
Важное правило: Имена колонок в запросе (псевдонимы КАК ...) должны строго совпадать с именами реквизитов (колонок) вашей таблицы на форме. Метод Загрузить() автоматически сопоставит колонки по именам и очистит старые данные перед заполнением.
Проанализируем классическую ошибку, которую часто допускают начинающие программисты при попытке перенести данные из одной таблицы в другую вручную:
// ВНИМАНИЕ: Код содержит критическую ошибку!
Для каждого СтрокаТЧ из КаталогНСИ Цикл
НоваяСтрока = КаталогНСИ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЧ);
КонецЦикла;
Выясним причину, почему данный код приводит к зависанию конфигуратора или аварийному завершению работы программы. В цикле Для каждого обходится коллекция КаталогНСИ. Внутри этого же цикла в эту же самую коллекцию добавляется новая строка с помощью метода Добавить(). Это приводит к бесконечному росту коллекции прямо во время её итерации. Система зацикливается, пытаясь обойти бесконечно добавляемые строки.
Если вам действительно необходимо программно обработать каждую строку перед добавлением, всегда обходите исходную (неизменяемую) таблицу значений, а новые строки добавляйте в целевую таблицу формы:
// Правильный построчный перенос данных
ТаблицаИсточник = РезультатЗапроса.Выгрузить();
Для каждого СтрокаИсточник Из ТаблицаИсточник Цикл
НоваяСтрока = Объект.КаталогНСИ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточник);
КонецЦикла;
Обратите внимание на встроенную функцию ЗаполнитьЗначенияСвойств(). Она копирует значения свойств из источника в приемник для всех совпадающих имен реквизитов, избавляя вас от необходимости писать ручные присвоения для каждого поля.
Часто в процессе заполнения таблицы возникает необходимость получить дополнительные сведения или свойства номенклатуры (например, спец. цены, характеристики или дополнительные реквизиты БСП). Рассмотрим неоптимальный код, который часто пишут разработчики:
// ВНИМАНИЕ: Неэффективный код (проблема N+1 запросов)
Для каждого Строка Из Объект.Товары Цикл
неоСпецЦена = УправлениеСвойствами.ЗначениеСвойства(Строка.Номенклатура, ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоИмяни("СпецЦена"));
Строка.неоСпецЦена = неоСпецЦена;
КонецЦикла;
В чем заключается проблема данного подхода? Если в таблице Товары находится 500 строк, то функция ЗначениеСвойства() выполнит 500 отдельных запросов к базе данных. Это вызовет колоссальные задержки в работе интерфейса.
Для оптимизации этой операции мы должны получить все дополнительные свойства одним пакетным запросом или через левое соединение в основном запросе. Посмотрим на правильную реализацию с использованием связи в запросе:
&НаСервере
Процедура ЗаполнитьДанныеСвойствамиНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул,
| ЕСТЬNULL(ДопСвойства.Значение, 0) КАК СпецЦена
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ДополнительныеРеквизиты КАК ДопСвойства
| ПО ДопСвойства.Ссылка = Номенклатура.Ссылка
| И ДопСвойства.Свойство = &СвойствоСпецЦена
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа";
// Получаем ссылку на нужное свойство заранее
СвойствоСпецЦена = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоРеквизиту("Имя", "СпецЦена_Идентификатор");
Запрос.УстановитьПараметр("СвойствоСпецЦена", СвойствоСпецЦена);
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
Объект.Товары.Загрузить(ТаблицаРезультат);
КонецПроцедуры
Используя левое соединение (ЛЕВОЕ СОЕДИНЕНИЕ), мы получаем всю необходимую информацию за один-единственный запрос к СУБД, что обеспечивает мгновенный отклик системы даже при обработке десятков тысяч строк.
Иногда требуется обновить данные не во всей таблице, а только в той строке, которую выбрал пользователь на клиенте. Прямая передача строки таблицы (имеющей тип ДанныеФормыЭлементКоллекции) на сервер в качестве параметра невозможна, так как этот тип не сериализуется.
Для решения этой проблемы мы должны передавать на сервер уникальный идентификатор строки таблицы формы. Рассмотрим алгоритм реализации этой задачи:
&НаКлиенте
Процедура ТоварыПриИзмененииНоменклатуры(Элемент)
// Получаем идентификатор текущей строки на клиенте
ИдентификаторТекущейСтроки = Элементы.Товары.ТекущаяСтрока;
Если ИдентификаторТекущейСтроки <> Неопределено Тогда
ЗаполнитьРеквизитыТекущейСтрокиНаСервере(ИдентификаторТекущейСтроки);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьРеквизитыТекущейСтрокиНаСервере(ИдентификаторСтроки)
// Находим строку в данных формы по её уникальному идентификатору
СтрокаТаблицы = Объект.Товары.НайтиПоИдентификатору(ИдентификаторСтроки);
Если СтрокаТаблицы <> Неопределено Тогда
// Выполняем логику расчета или заполнения реквизитов на сервере
СтрокаТаблицы.неоСпецЦена = ПолучитьЦенуНоменклатуры(СтрокаТаблицы.Номенклатура);
КонецЕсли;
КонецПроцедуры
Благодаря использованию метода НайтиПоИдентификатору() мы корректно позиционируемся на нужной строке в серверном контексте и вносим изменения, которые сразу же отображаются на клиенте.
В процессе заполнения таблицы часто возникает задача преобразования или парсинга текстовых полей (например, когда характеристика номенклатуры записана в формате "Цвет: Красный; Размер: XL" и её нужно разделить на две разные колонки таблицы).
Для решения этой задачи в старых конфигурациях часто использовалась функция общего модуля РазложитьСтрокуВМассивПодстрок(). Однако, начиная с платформы 8.3.6, в 1С встроен высокопроизводительный системный метод СтрРазделить(), работающий на уровне платформенного ядра.
Рассмотрим пример разделения текстовой строки и заполнения соответствующих колонок таблицы:
&НаСервере
Процедура РазбитьЗначениеКолонкиНаСервере()
Для каждого Строка Из Объект.Товары Цикл
ИсходныйТекст = Строка.ПолноеОписаниеХарактеристики; // Например, "Красный|XL"
Если Не ПустаяСтрока(ИсходныйТекст) Тогда
// Разделяем строку по разделителю "|"
МассивПараметров = СтрРазделить(ИсходныйТекст, "|");
// Заполняем соответствующие колонки таблицы
Если МассивПараметров.Количество() >= 2 Тогда
Строка.Цвет = СокрЛП(МассивПараметров[0]);
Строка.Размер = СокрЛП(МассивПараметров[1]);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Использование встроенной функции СтрРазделить() гарантирует максимальную скорость обработки строк и избавляет конфигурацию от лишних зависимостей от общих модулей библиотек.
Бывают ситуации, когда количество колонок в таблице заранее неизвестно на этапе конфигурирования (например, при выводе динамических характеристик номенклатуры). В этом случае мы можем программно добавить реквизиты и элементы на форму "на лету".
Процесс динамического добавления колонок состоит из трех обязательных шагов:
ИзменитьРеквизиты().Рассмотрим пример реализации этой сложной, но крайне полезной технологии:
&НаСервере
Процедура СоздатьКолонкиИЗаполнитьНаСервере()
// Шаг 1. Описываем и добавляем новые реквизиты на форму
МассивДобавляемыхРеквизитов = Новый Массив;
// Путь к реквизиту формы: "ИмяТаблицыФормы.ИмяНовойКолонки"
РеквизитЦвет = Новый РеквизитФормы("ДинамическийЦвет", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(50)), "КаталогНСИ", "Цвет");
МассивДобавляемыхРеквизитов.Добавить(РеквизитЦвет);
// Применяем изменения структуры реквизитов формы
ЭтаФорма.ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);
// Шаг 2. Создаем визуальные элементы (колонки) на форме
НовыйЭлемент = Элементы.Добавить("КаталогНСИДинамическийЦвет", Тип("ПолеФормы"), Элементы.КаталогНСИ);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.ПутьКДанным = "КаталогНСИ.ДинамическийЦвет";
НовыйЭлемент.Заголовок = "Цвет (Динамический)";
// Шаг 3. Заполняем таблицу данными с учетом новых колонок
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул,
| ""Красный"" КАК ДинамическийЦвет
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа";
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
// Загружаем данные в динамически измененную таблицу формы
КаталогНСИ.Загрузить(ТаблицаДанных);
КонецПроцедуры
Этот подход незаменим при построении сложных интерактивных отчетов, параметрических матриц подбора товаров или динамических АРМ (автоматизированных рабочих мест) менеджеров по продажам.