В практике разработки на платформе 1С:Предприятие 8.3 часто возникает необходимость уйти от статичной структуры данных. Типичный сценарий — создание интерфейсов, в которых колонки табличной части или реквизиты формы добавляются программно в зависимости от настроек пользователя. Например, это может быть расчет различных видов цен, матрица скидок или динамический вывод аналитики.
В таких задачах разработчики неизбежно сталкиваются с проблемой: как обратиться к реквизиту объекта или колонке строки, если их имена заранее неизвестны и хранятся в строковых переменных? В этой статье мы подробно разберем, как решать такие задачи с помощью оператора квадратных скобок, проанализируем частые ошибки производительности при динамическом изменении формы и напишем оптимальный алгоритм расчета.
Представим сценарий, с которым столкнулся автор темы. В обработке есть табличная часть КонтрольЦен. В ней присутствует базовая колонка Себестоимость, созданная статически в конфигураторе. Также на форме динамически создаются дополнительные колонки для различных видов цен (например, ЦенаА, ЦенаБ, ЦенаОпт и т.д.).
Пользователь вводит проценты наценки для каждого вида цены во вспомогательном окне. Эти данные возвращаются в основную форму в виде коллекции Соответствие, где:
Нам необходимо обойти строки табличной части и рассчитать значения в динамических колонках по формуле: Цена = Себестоимость * (ПроцентНаценки / 100).
Попытка обратиться к свойству строки напрямую через точку, используя переменную, приведет к синтаксической ошибке:
// Ошибка! Платформа будет искать реквизит с физическим именем "ИмяПеременной"
Для Каждого СтрТаб Из Объект.КонтрольЦен Цикл
ИмяПеременной = "ЦенаА";
СтрТаб.ИмяПеременной = СтрТаб.Себестоимость * 1.2;
КонецЦикла;
Для решения этой проблемы во встроенном языке 1С предусмотрен механизм динамического обращения к свойствам объектов.
В 1С динамическое обращение к свойствам объектов, коллекций и строк табличных частей реализуется с помощью оператора квадратных скобок []. Вместо того чтобы писать имя свойства через точку, мы передаем строковое выражение внутрь скобок. Платформа вычисляет это выражение во время выполнения и обращается к соответствующему свойству.
Давайте посмотрим, как выглядит базовый рабочий цикл для нашей задачи:
// Пример динамического заполнения колонок
Для Каждого СтрТаб Из Объект.КонтрольЦен Цикл
// Ключ соответствия содержит точное имя колонки, например "ЦенаА"
Для Каждого КлючЗнач Из СоответствиеПроцентов Цикл
ИмяКолонки = КлючЗнач.Ключ;
Процент = КлючЗнач.Значение;
// Обращаемся к колонке строки ТЧ динамически по ее имени
СтрТаб[ИмяКолонки] = СтрТаб.Себестоимость * (Процент / 100);
КонецЦикла;
КонецЦикла;
Этот простой прием позволяет легко связывать программно созданные элементы интерфейса и динамические данные. Квадратные скобки поддерживаются следующими типами данных в 1С:
Проанализируем код, который автор темы использовал для создания элементов формы во второй вспомогательной форме (из Сообщения 4):
// Опасный антипаттерн в цикле!
Для Каждого НовРек Из МассивРеквизитов Цикл
СоздатьПолеНаСервере(НовРек); // Внутри вызывается ИзменитьРеквизиты()
КонецЦикла;
Внутри процедуры СоздатьПолеНаСервере на каждом шаге цикла вызывался метод формы ИзменитьРеквизиты(). Это серьезная ошибка проектирования, которая может «повесить» систему при большом количестве колонок!
Вызов ИзменитьРеквизиты() — это крайне ресурсоемкая операция. При ее вызове платформа полностью перестраивает контекст формы на сервере, выполняет сериализацию данных и передает измененное состояние обратно на клиент для полной перерисовки интерфейса. Вызов этого метода в цикле кратно увеличивает нагрузку на сервер и время ожидания пользователя.
Выясним, как сделать это правильно. Нам необходимо собрать все новые реквизиты в один массив и вызвать метод ИзменитьРеквизиты() строго один раз. Разберем по шагам правильный алгоритм программного создания элементов:
&НаСервере
Процедура СоздатьЭлементыФормыНаСервере(МассивРеквизитов)
// Шаг 1. Готовим массив для добавления новых реквизитов формы
ДобавляемыеРеквизиты = Новый Массив;
Для Каждого ИмяИсточника Из МассивРеквизитов Цикл
// Очищаем имя от пробелов, так как имя реквизита не должно их содержать
ИмяРеквизита = СтрЗаменить(ИмяИсточника, " ", "");
// Создаем описание типа данных (в нашем случае Число)
ОписаниеТиповЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
// Создаем объект РеквизитФормы
// Параметры: Имя, Тип, Путь, Заголовок, СохраняемыеДанные
НовыйРеквизит = Новый РеквизитФормы(ИмяРеквизита, ОписаниеТиповЧисло, , "Процент для " + ИмяИсточника, Истина);
ДобавляемыеРеквизиты.Добавить(НовыйРеквизит);
КонецЦикла;
// Шаг 2. Изменяем структуру реквизитов формы ОДИН раз за вызов процедуры
ЭтаФорма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
// Шаг 3. Добавляем элементы управления (Поля ввода) на форму для созданных реквизитов
Для Каждого ИмяИсточника Из МассивРеквизитов Цикл
ИмяРеквизита = СтрЗаменить(ИмяИсточника, " ", "");
// Добавляем поле ввода в группу на форме
НовыйЭлемент = ЭтаФорма.Элементы.Добавить(ИмяРеквизита, Тип("ПолеФормы"), ЭтаФорма.Элементы.ГруппаПроценты);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.ПутьКДанным = ИмяРеквизита;
КонецЦикла;
КонецПроцедуры
Этот шаблон гарантирует быструю и стабильную работу интерфейса без зависаний даже при создании десятков динамических полей.
Теперь соберем воедино весь пазл и напишем надежный обработчик оповещения в основной форме, который принимает таблицу (соответствие) процентов, проверяет данные на корректность и производит расчет с использованием квадратных скобок.
Обратите внимание на проверку заполнения себестоимости и защиту от деления на ноль. Это правила хорошего тона в программировании 1С.
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
// Проверяем имя события, которое пришло из формы ввода процентов
Если ИмяСобытия = "ФиксацияПроцентов" Тогда
ТаблицаПроцентов = Параметр; // Тип: Соответствие
Если ТаблицаПроцентов <> Неопределено Тогда
ВыполнитьРасчетЦенНаСервере(ТаблицаПроцентов);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ВыполнитьРасчетЦенНаСервере(ТаблицаПроцентов)
// Обходим строки табличной части "КонтрольЦен"
Для Каждого СтрокаТЧ Из Объект.КонтрольЦен Цикл
// Если себестоимость не заполнена, переходим к следующей строке
Если Не ЗначениеЗаполнено(СтрокаТЧ.Себестоимость) Тогда
Продолжить;
КонецЕсли;
// Перебираем установленные проценты наценок
Для Каждого ЭлементСоответствия Из ТаблицаПроцентов Цикл
ИмяКолонкиЦены = ЭлементСоответствия.Ключ;
ПроцентНаценки = ЭлементСоответствия.Значение;
// Если процент не числовой или равен 0, то записываем просто себестоимость (или 0)
Если ТипЗнч(ПроцентНаценки) <> Тип("Число") Тогда
ПроцентНаценки = 0;
КонецЕсли;
// Производим расчет и динамически пишем результат в колонку строки
// Формула расчета конечной цены с учетом наценки:
НоваяЦена = СтрокаТЧ.Себестоимость * (1 + ПроцентНаценки / 100);
// Запись в динамическую колонку
СтрокаТЧ[ИмяКолонкиЦены] = НоваяЦена;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
В данном серверном методе мы совершаем обход по коллекции ТаблицаПроцентов. Поскольку типом коллекции является Соответствие, при обходе через цикл Для Каждого мы получаем системный тип КлючИДваЗначения. Мы сразу забираем и имя колонки (из свойства Ключ), и сам процент (из свойства Значение), избегая лишних вызовов метода Получить(), что также положительно сказывается на производительности алгоритма.
Рассмотрим альтернативные ситуации. Что делать, если вам нужно не рассчитывать значения по математической формуле, а просто перенести значения одноименных колонок или реквизитов из одного объекта в другой?
В платформе 1С для этих целей существует мощный встроенный метод ЗаполнитьЗначенияСвойств(). Системный синтаксис выглядит так:
// ЗаполнитьЗначенияСвойств(Приемник, Источник, [СписокСвойств], [ИсключаяСвойства])
ЗаполнитьЗначенияСвойств(СтрокаПолучатель, СтрокаИсточник);
Этот метод сопоставляет имена свойств (колонок) у источника и приемника и автоматически копирует данные по совпадающим именам. Он работает на уровне платформы (написан на C++) и выполняется в разы быстрее аналогичных циклов на встроенном языке. Однако для нашей текущей задачи (где требуется вычисление и применение формул на лету) использование квадратных скобок [] остается самым гибким и правильным решением.
Используйте динамическое обращение обдуманно, не забывайте группировать вызовы тяжелых методов метаданных формы, и ваши решения всегда будут работать быстро и надежно!