При разработке и кастомизации документов в среде 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью массовой обработки строк табличных частей (ТЧ). Особую сложность представляют ситуации, когда в одном документе содержится множество схожих табличных частей (например, при партионном учете, распределении затрат или попозиционном планировании), и в каждую из них необходимо транслировать рассчитанный коэффициент или выполнить пересчет сумм.
В этой статье мы подробно разберем, как правильно организовать обход всех табличных частей документа, как избежать типичных ошибок производительности и "хрупкого кода", а также рассмотрим безопасные методы работы с элементами управляемой формы на клиенте.
Проанализируем классический пример кода, который часто пишут начинающие разработчики, выясним его слабые стороны и пошагово перепишем его в соответствии со стандартами разработки фирмы «1С».
Рассмотрим ситуацию: у нас есть самописный документ, содержащий несколько табличных частей с однотипными названиями: ТЧПос1, ТЧПос2, ТЧПос3 и так далее. Нам требуется во всех строках всех ТЧ рассчитать конечные суммы на основании коэффициентов.
Типичное быстрое решение, к которому прибегают разработчики на скорую руку, выглядит следующим образом:
&НаКлиенте
Процедура ПересчетТабличнойЧасти(Элемент = "", СтрочкаТЧ = "")
Для i = 1 По 6 Цикл
ИмяТЧ = "ТЧПос" + Строка(i);
// ... (логика получения строки и обхода)
Для Каждого Стр Из Объект[ИмяТЧ] Цикл
Стр.COST_TRANS = Стр.Количество * Стр.RUB_TRANS;
// расчет остальных колонок
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Выясним, почему этот код таит в себе серьезную опасность для стабильности информационной системы:
Для i = 1 По 6 Цикл означает, что если завтра заказчик попросит добавить седьмую табличную часть (например, ТЧПос7), система просто проигнорирует ее при расчетах. А если одна из табличных частей будет удалена или переименована (например, останется 5 ТЧ), код завершится критической ошибкой выполнения.&НаКлиенте при большом количестве строк вызовет зависание (фриз) интерфейса пользователя.Стр.RUB_TRANS) вызовет фатальную ошибку «Поле объекта не обнаружено».Давайте разберем по шагам, как решить эту задачу профессионально, гибко и безопасно.
Чтобы код оставался работоспособным при любом количестве и любых именах табличных частей, мы должны отказаться от жесткого кодирования индексов. Платформа 1С предоставляет мощный механизм метаданных, который позволяет анализировать структуру объектов прямо во время выполнения кода.
Перенесем логику расчетов на сервер и напишем универсальный алгоритм обхода:
&НаСервере
Процедура РассчитатьВсеТабличныеЧастиНаСервере()
// Получаем объект метаданных нашего документа
МетаданныеДокумента = Объект.Метаданные();
// Динамически обходим коллекцию всех табличных частей документа
Для Каждого ТЧМетаданные Из МетаданныеДокумента.ТабличныеЧасти Цикл
ИмяТЧ = ТЧМетаданные.Имя;
// Если нам нужно обрабатывать только ТЧ, соответствующие шаблону "ТЧПос"
Если Найти(ИмяТЧ, "ТЧПос") > 0 Тогда
// Получаем саму коллекцию строк конкретной табличной части из реквизита "Объект"
ТабличнаяЧасть = Объект[ИмяТЧ];
// Запускаем цикл по строкам текущей табличной части
Для Каждого ТекущаяСтрока Из ТабличнаяЧасть Цикл
// Выполняем расчеты с предварительной проверкой существования реквизитов
ЗаполнитьРасчетныеПоляСтроки(ТекущаяСтрока, ТЧМетаданные);
КонецЦикла;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Использование свойства Объект.Метаданные().ТабличныеЧасти полностью решает проблему масштабируемости системы. Сколько бы табличных частей ни добавил конфигуратор в будущем, код подхватит их автоматически.
Вторая частая задача, затронутая в обсуждении, — получение данных конкретной строки ТЧ, в которой пользователь изменил значение (например, в событии ПриИзменении реквизита колонки).
Иногда разработчики пытаются вычислить имя таблицы формы, поднимаясь вверх по иерархии элементов управления:
Элементы[Элемент.КонтекстноеМеню.Родитель.Родитель.Имя].ТекущиеДанные
Никогда так не делайте! Визуальная структура формы изменчива. Если вы решите поместить колонку внутрь группы колонок (для красивого визуального объединения на форме) или добавить декоративную панель, количество уровней вложенности («родителей») изменится. В результате код сломается, а пользователи увидят ошибку выполнения.
Рассмотрим два надежных способа получения родительской таблицы формы.
Способ А. Прямое обращение через родителя элемента колонки
Если колонка лежит непосредственно внутри таблицы формы (без группировок), самым простым и лаконичным решением будет обращение к свойству Родитель напрямую:
&НаКлиенте
Процедура ТоварыЦенаПриИзменении(Элемент)
// Родитель элемента колонки — это сама ТаблицаФормы
ТаблицаФормы = Элемент.Родитель;
ТекущиеДанные = ТаблицаФормы.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
// Производим локальный расчет для текущей строки
ТекущиеДанные.Сумма = ТекущиеДанные.Количество * ТекущиеДанные.Цена;
КонецЕсли;
КонецПроцедуры
Способ Б. Универсальный рекурсивный поиск таблицы (Рекомендуется)
Для того чтобы гарантировать стабильность кода даже при сложной верстке формы (наличии групп колонок, папок и вкладок), напишем вспомогательную функцию, которая будет рекурсивно подниматься вверх по дереву элементов до тех пор, пока не встретит тип ТаблицаФормы:
&НаКлиенте
Функция НайтиРодительскуюТаблицуФормы(ЭлементФормы)
ТекущийРодитель = ЭлементФормы.Родитель;
Пока ТекущийРодитель <> Неопределено Цикл
Если ТипЗнч(ТекущийРодитель) = Тип("ТаблицаФормы") Тогда
Возврат ТекущийРодитель;
КонецЕсли;
// Поднимаемся выше по иерархии элементов формы
ТекущийРодитель = ТекущийРодитель.Родитель;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Теперь в обработчике любого события на форме мы можем абсолютно безопасно получать данные строки:
&НаКлиенте
Процедура КолонкаТЧПриИзменении(Элемент)
ТаблицаФормы = НайтиРодительскуюТаблицуФормы(Элемент);
Если ТаблицаФормы <> Неопределено Тогда
СтрокаДанных = ТаблицаФормы.ТекущиеДанные;
// Безопасно работаем с данными строки...
КонецЕсли;
КонецПроцедуры
Проанализируем еще одну тонкую ошибку, допущенную в коде исходного сообщения:
Стр = СтрочкаТЧ;
// ...
Для Каждого Стр из Объект[ИмяТЧ] Цикл
Здесь переменная Стр сначала инициализируется значением переданного параметра СтрочкаТЧ, а в следующей строке используется в качестве итератора цикла Для Каждого. Это классическое «затенение» (перезапись) переменной. Внутри цикла первоначальное значение СтрочкаТЧ будет утеряно и заменено текущей строкой обходимой коллекции.
Всегда объявляйте переменные с уникальными и понятными именами, отражающими суть их содержимого. Например: ТекущаяСтрока, СтрокаТаблицы или ТекущиеДанные.
Если в вашем документе содержатся тысячи строк, то выполнение массовых расчетов на клиенте приведет к сильной нагрузке на CPU клиента. Платформа 1С при изменении каждого реквизита в строке пытается моментально перерисовать интерфейс.
Для оптимизации этого процесса применим два подхода:
1. Перенос тяжелой математики на сервер
Выполняйте общие расчеты всего документа на сервере. Контекстный вызов сервера выполнит обработку данных в оперативной памяти сервера приложений гораздо быстрее, после чего вернет обновленный объект на форму.
2. Временное отключение обновления отображения на клиенте
Если расчет необходимо провести именно на клиенте без обращения к серверу, обязательно используйте управление обновлением отображения формы. Это ускоряет работу циклов по ТЧ в разы:
&НаКлиенте
Процедура БыстрыйРасчетНаКлиенте()
// Отключаем перерисовку формы на время расчетов
ЭтаФорма.ОбновлениеОтображенияВключено = Ложь;
Попытка
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
// Выполняем массовый расчет
СтрокаТЧ.Сумма = СтрокаТЧ.Количество * СтрокаТЧ.Цена;
КонецЦикла;
Исключение
// Включаем обратно в случае непредвиденной ошибки
ЭтаФорма.ОбновлениеОтображенияВключено = Истина;
ВызватьИсключение КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
// Включаем отображение изменений на форме
ЭтаФорма.ОбновлениеОтображенияВключено = Истина;
КонецПроцедуры
Когда мы пишем универсальный код, обходящий все ТЧ документа, мы должны гарантировать, что обращение к реквизитам не вызовет падения системы, если структуры таблиц различаются. Для этого перед присвоением значений проверим состав колонок.
Рассмотрим пример безопасного заполнения реквизитов на сервере:
&НаСервере
Процедура ЗаполнитьРасчетныеПоляСтроки(СтрокаТЧ, МетаданныеТЧ)
// Проверяем существование реквизитов в метаданных данной конкретной ТЧ
ЕстьКоличество = (МетаданныеТЧ.Реквизиты.Найти("Количество") <> Неопределено);
ЕстьЦена = (МетаданныеТЧ.Реквизиты.Найти("ЦенаУЕ") <> Неопределено);
ЕстьСумма = (МетаданныеТЧ.Реквизиты.Найти("COST_TRANS") <> Неопределено);
Если ЕстьКоличество И ЕстьЦена И ЕстьСумма Тогда
СтрокаТЧ.COST_TRANS = СтрокаТЧ.Количество * СтрокаТЧ.ЦенаУЕ;
КонецЕсли;
КонецПроцедуры
Если в вашей конфигурации используется Библиотека Стандартных Подсистем (БСП), проверку можно сделать еще изящнее с помощью функций общего назначения:
ОбщегоНазначения.ЕстьРеквизитОбъекта("COST_TRANS", МетаданныеТЧ)
Для быстрого заполнения колонок табличной части одинаковыми статическими значениями (например, при установке общего коэффициента или признака для всех строк) вместо написания циклов эффективнее использовать метод платформы ЗаполнитьЗначения() у объекта ТабличнаяЧасть, либо воспользоваться системной функцией ЗаполнитьЗначенияСвойств().
Посмотрим на пример заполнения колонки во всех строках ТЧ без использования циклов:
&НаСервере
Процедура УстановитьКоэффициентВоВсеТЧ(ЗначениеКоэффициента)
Для Каждого ТЧМетаданные Из Объект.Метаданные().ТабличныеЧасти Цикл
ИмяТЧ = ТЧМетаданные.Имя;
// Проверяем, есть ли у текущей ТЧ нужная колонка коэффициента
Если ТЧМетаданные.Реквизиты.Найти("K_TRANS") <> Неопределено Тогда
// Быстрое заполнение всей колонки ТЧ без явного цикла "Для Каждого"
Объект[ИмяТЧ].ЗаполнитьЗначения(ЗначениеКоэффициента, "K_TRANS");
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Применение метода ЗаполнитьЗначения() работает на уровне платформы значительно быстрее ручного обхода строк в коде BSL, особенно на больших массивах данных.
Подводя итог, отметим: написание гибкого кода в 1С требует понимания взаимодействия клиент-серверной архитектуры, грамотного использования объектной модели метаданных и отказа от "магических чисел" и жестко прописанных констант. Проектируйте ваши алгоритмы так, чтобы они оставались стабильными при будущих доработках структуры конфигурации!