При разработке интерфейсов на платформе 1С:Предприятие часто возникает задача динамического изменения структуры табличной части или таблицы значений на управляемой форме. Типичный пример — интерактивное планирование, когда пользователь указывает в шапке документа дату начала и дату окончания периода, а в табличной части (или таблице значений) должны автоматически появиться колонки, соответствующие месяцам, попавшим в этот интервал.
В этой статье мы подробно разберем, как правильно реализовать этот механизм, детально проанализируем распространенные ошибки начинающих разработчиков (включая ошибку «Неуникальное имя реквизита. Имя: "Массив"») и предоставим готовый оптимизированный код, соответствующий стандартам разработки фирмы «1С».
Давайте разберем типичные ошибки, с которыми сталкиваются программисты при попытке динамического добавления колонок на форму.
1. Ошибка неверного использования функции форматирования
В коде автора темы присутствует строка:
НазваниеКолонки = Формат(МассивМесяцев, "ДФ=MMMM");
Переменная МассивМесяцев является объектом типа Массив. Функция Формат() предназначена для преобразования в строку простых типов данных (Число, Дата, Булево). Когда платформа получает на вход объект сложного типа (например, Массив), она не может применить к нему маску даты и возвращает стандартное строковое представление типа — "Массив".
В результате на первой итерации цикла успешно создается колонка с именем "Массив". Но на второй итерации система пытается создать еще одну колонку с тем же именем "Массив", что мгновенно вызывает исключение: «Неуникальное имя реквизита. Имя: "Массив"».
2. Ошибка производительности при изменении реквизитов формы
Вызов метода ИзменитьРеквизиты() внутри цикла (на каждой итерации для каждого отдельного месяца) — это серьезная архитектурная ошибка. Этот метод заставляет платформу перестраивать всю структуру данных формы на сервере и отправлять изменения на клиент. Если период составляет, например, 12 месяцев, форма будет перерисована 12 раз, что вызовет сильное зависание интерфейса.
3. Отсутствие очистки старых колонок
Пользователь может менять даты начала и окончания периода несколько раз. Если перед генерацией новых колонок не удалять старые, при повторном изменении периода программа выдаст ошибку уникальности имен реквизитов формы.
4. Проблема перехода через год (конфликт имен)
Если период охватывает границы разных лет (например, с декабря 2022 по февраль 2024 года), в интервале встретятся два января или два февраля. Если называть колонки просто по имени месяца ("Январь", "Февраль"), имена реквизитов продублируются, и платформа выдаст ошибку. Нам необходимо разделять уникальное техническое имя реквизита (содержащее год) и понятный пользователю заголовок.
Для успешного динамического изменения формы важно понимать концепцию разделения данных и их визуального отображения в управляемом приложении:
ИзменитьРеквизиты().ПутьКДанным. Для управления ими используется коллекция Элементы.Соответственно, наш алгоритм должен состоять из трех крупных шагов: сначала удалить старые элементы и реквизиты, затем пакетно добавить новые реквизиты, а затем создать визуальные элементы управления.
Рассмотрим детально готовое программное решение, написанное с учетом всех правил оптимизации. Этот код необходимо разместить в модуле управляемой формы документа.
Создадим процедуру, которая будет вызываться при изменении дат периода в шапке документа:
&НаСервере
Процедура ОбновитьДинамическиеКолонкиНаСервере()
НачалоИнтервала = Объект.ДатаНачала;
КонецИнтервала = Объект.ДатаОкончания;
// Проверим корректность заполнения дат
Если Не ЗначениеЗаполнено(НачалоИнтервала) Или Не ЗначениеЗаполнено(КонецИнтервала) Тогда
Возврат;
КонецЕсли;
Если НачалоИнтервала > КонецИнтервала Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Дата начала периода не может быть больше даты окончания!");
Возврат;
КонецЕсли;
ИмяТаблицыФормы = "ПроизводственныеПланы"; // Имя таблицы значений на форме
ПрефиксКолонок = "ДинамическийМесяц_"; // Префикс для уникальности технических имен
// Шаг 1. ОЧИСТКА СТАРЫХ ДИНАМИЧЕСКИХ КОЛОНОК
// Нам нужно собрать массивы для удаления элементов и реквизитов
МассивУдаляемыхРеквизитов = Новый Массив;
ТаблицаФормы = Элементы[ИмяТаблицыФормы];
// Перебираем визуальные колонки таблицы формы в обратном порядке, чтобы избежать проблем с индексами при удалении
Индекс = ТаблицаФормы.ПодчиненныеЭлементы.Количество() - 1;
Пока Индекс >= 0 Цикл
Колонка = ТаблицаФормы.ПодчиненныеЭлементы[Индекс];
// Проверяем, что это динамически созданная нами колонка по ее имени
Если Найти(Колонка.Имя, ПрефиксКолонок) = 1 Тогда
// Сохраняем путь к данным реквизита (например, "ПроизводственныеПланы.ДинамическийМесяц_201801")
МассивУдаляемыхРеквизитов.Добавить(ИмяТаблицыФормы + "." + Колонка.Имя);
// Удаляем визуальный элемент с формы
Элементы.Удалить(Колонка);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
// Удаляем сами реквизиты из структуры данных формы
Если МассивУдаляемыхРеквизитов.Количество() > 0 Тогда
ИзменитьРеквизиты(, МассивУдаляемыхРеквизитов);
КонецЕсли;
// Шаг 2. ПОДГОТОВКА НОВЫХ РЕКВИЗИТОВ И ОБХОД ПЕРИОДА
МассивНовыхРеквизитов = Новый Массив;
СписокСоздаваемыхКолонок = Новый СписокЗначений; // Для передачи информации на шаг создания визуальных элементов
ТекущийМесяц = НачалоМесяца(НачалоИнтервала);
КонецИнтервалаМесяц = НачалоМесяца(КонецИнтервала);
// Используем универсальный цикл "Пока" для обхода периода любой длины
Пока ТекущийМесяц <= КонецИнтервалаМесяц Цикл
// Формируем уникальное имя реквизита (английские буквы, цифры, без спецсимволов)
// Пример: "ДинамическийМесяц_201801"
ИмяРеквизита = ПрефиксКолонок + Формат(ТекущийМесяц, "ДФ=yyyyMM");
// Формируем красивый пользовательский заголовок (например, "Январь 2018")
ЗаголовокКолонки = Формат(ТекущийМесяц, "ДФ='MMMM yyyy'");
// Создаем описание нового реквизита формы
// РеквизитФормы(Имя, Тип, Путь, Заголовок, СохраняемыеДанные)
НовыйРеквизит = Новый РеквизитФормы(
ИмяРеквизита,
Новый ОписаниеТипов("Число"),
ИмяТаблицыФормы,
ЗаголовокКолонки,
Истина
);
МассивНовыхРеквизитов.Добавить(НовыйРеквизит);
// Запоминаем имя и заголовок для последующего добавления элементов формы
СписокСоздаваемыхКолонок.Добавить(ИмяРеквизита, ЗаголовокКолонки);
// Инкрементируем счетчик цикла ровно на 1 месяц
ТекущийМесяц = ДобавитьМесяц(ТекущийМесяц, 1);
КонецЦикла;
// Шаг 3. ЕДИНОРАЗОВОЕ ДОБАВЛЕНИЕ РЕКВИЗИТОВ (Оптимизация!)
Если МассивНовыхРеквизитов.Количество() > 0 Тогда
ИзменитьРеквизиты(МассивНовыхРеквизитов);
КонецЕсли;
// Шаг 4. СОЗДАНИЕ ВИЗУАЛЬНЫХ ЭЛЕМЕНТОВ НА ФОРМЕ
Для Каждого КолонкаИнформация Из СписокСоздаваемыхКолонок Цикл
ТехническоеИмя = КолонкаИнформация.Значение;
ПользовательскийЗаголовок = КолонкаИнформация.Представление;
// Добавляем элемент "Поле формы" в коллекцию элементов таблицы
НоваяКолонка = Элементы.Добавить(ТехническоеИмя, Тип("ПолеФормы"), ТаблицаФормы);
НоваяКолонка.Вид = ВидПоляФормы.ПолеВвода;
НоваяКолонка.Заголовок = ПользовательскийЗаголовок;
// Настраиваем путь к данным созданного реквизита формы
НоваяКолонка.ПутьКДанным = ИмяТаблицыФормы + "." + ТехническоеИмя;
// Дополнительные свойства отображения (например, ширина и формат редактирования чисел)
НоваяКолонка.Ширина = 12;
НоваяКолонка.ФорматРедактирования = "ЧДЦ=2";
КонецЦикла;
КонецПроцедуры
Чтобы описанный выше механизм работал при изменении дат пользователем, нам нужно настроить обработчики событий элементов формы.
Разберем по шагам, как привязать процедуру к событиям формы:
ДатаНачала) событие ПриИзменении и создайте клиентский обработчик.ДатаОкончания).Пример клиентских процедур-обработчиков выглядит следующим образом:
&НаКлиенте
Процедура ДатаНачалаПриИзменении(Элемент)
ОбновитьДинамическиеКолонкиНаСервере();
КонецПроцедуры
&НаКлиенте
Процедура ДатаОкончанияПриИзменении(Элемент)
ОбновитьДинамическиеКолонкиНаСервере();
КонецПроцедуры
Проанализируем ключевые моменты и функции, которые мы использовали для повышения надежности и скорости работы алгоритма:
ДобавитьМесяц(НачалоМесяца(Дата), КоличествоИтераций) — эта связка позволяет нам строго позиционировать шаг цикла на начало каждого месяца. Использование функции НачалоМесяца() защищает нас от потенциальных ошибок смещения дней (например, при переходе с 31 января на февраль).Формат(ТекущийМесяц, "ДФ=yyyyMM") — создает уникальную строку (например, "201801"). Применение префикса "ДинамическийМесяц_" гарантирует, что имя реквизита будет соответствовать правилам именования платформы (не начнется с цифры) и будет уникальным при любом периоде планирования.ИзменитьРеквизиты(МассивНовыхРеквизитов, МассивУдаляемыхРеквизитов) — единственный метод платформы для динамического изменения состава полей формы. Мы передаем в него сразу сформированный пакет реквизитов, что обеспечивает мгновенную загрузку и перерисовку формы.Элементы.Удалить() индексы оставшихся элементов сдвигаются. Если использовать стандартный обход Для Каждого..., можно пропустить элементы или получить ошибку индексации. Обход коллекции с конца (от максимального индекса к нулю) решает эту проблему.Используя данный подход, вы сможете безопасно и эффективно реализовывать динамические интерактивные отчеты и формы ввода плановых показателей любой сложности.