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