При работе с платформой 1С:Предприятие 8.3 иногда возникает необходимость в создании форм, состав реквизитов и элементов которых не определен заранее, а должен динамически формироваться в зависимости от каких-либо условий, например, от выбранного пользователем периода. Такая задача является достаточно распространенной для разработчиков, поскольку позволяет создавать гибкие и адаптивные интерфейсы, не перегружая конфигурацию избыточными статическими объектами. В этой статье мы подробно разберем, как создать внешнюю обработку с управляемой формой, которая будет динамически формировать колонки таблицы по выбранному диапазону дат.
Прежде чем мы приступим к динамическому формированию элементов, давайте создадим основу нашей внешней обработки. Начнем с самых базовых шагов:
Для того чтобы пользователь мог выбрать период, нам потребуются два статических реквизита на форме: «ДатаНачала» и «ДатаОкончания». Также добавим команду и кнопку для запуска процесса формирования таблицы.
На закладке «Реквизиты» формы добавьте следующие реквизиты:
ДатаНачала, Тип: Дата (Состав даты: Дата).ДатаОкончания, Тип: Дата (Состав даты: Дата).Затем перейдите на закладку «Команды» и создайте новую команду:
Сформировать.&НаКлиенте и &НаСервере для вызова процедуры СформироватьНаСервере().На закладке «Элементы» формы создадим группу для размещения полей ввода дат и кнопку:
ГруппаФормы (например, «ГруппаПараметры») с видом «Обычная группа».ПолеФормы:
ДатаНачала.ДатаОкончания.КнопкаФормы, связанный с командой Сформировать.На данном этапе структура нашей формы будет выглядеть примерно так:
// В конфигураторе:
// Реквизиты формы:
// - ДатаНачала (Тип: Дата)
// - ДатаОкончания (Тип: Дата)
// Команды формы:
// - Сформировать (Действие: СформироватьНаСервере)
// Элементы формы:
// - ГруппаПараметры (Тип: ГруппаФормы)
// - ПолеДатаНачала (Тип: ПолеФормы, ПутьКДанным: ДатаНачала)
// - ПолеДатаОкончания (Тип: ПолеФормы, ПутьКДанным: ДатаОкончания)
// - КнопкаСформировать (Тип: КнопкаФормы, ИмяКоманды: Сформировать)
В управляемых формах 1С динамическое изменение состава реквизитов и элементов формы осуществляется с помощью двух ключевых методов и одного служебного объекта:
ИзменитьРеквизиты(): Этот метод формы позволяет программно добавлять, изменять или удалять реквизиты формы. Он принимает массив объектов типа РеквизитФормы. Критически важно понимать, что вызов этого метода ведет к частичному пересозданию формы на сервере, поэтому его следует использовать обдуманно и, как правило, в обработчике события ПриСозданииНаСервере().ЭтаФорма.Элементы.Добавить(): Этот метод коллекции элементов формы позволяет программно добавлять новые элементы управления (поля, кнопки, группы, таблицы и т.д.) на форму. Он возвращает объект ЭлементФормы, свойства которого можно настроить.РеквизитФормы: Это служебный объект, который описывает реквизит, добавляемый на форму. При его создании мы указываем имя, тип, путь к данным (если это реквизит подчиненной таблицы) и заголовок.Основным местом для выполнения операций по динамическому созданию реквизитов и элементов является процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка) формы. Именно здесь форма находится в подходящем состоянии для таких глубоких изменений своей структуры.
Теперь перейдем к реализации основной задачи: созданию таблицы с динамическими колонками, соответствующими дням выбранного периода. Нам потребуется следующее:
Весь код, ответственный за динамическое формирование, мы разместим в процедуре ПриСозданииНаСервере(), которая будет вызываться при открытии формы.
// В модуле формы обработки
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем, заданы ли даты. Если нет, то динамическую часть не формируем.
// Предполагаем, что ДатаНачала и ДатаОкончания - это реквизиты формы,
// которые могут быть заполнены из параметров при открытии или по умолчанию.
Если Не ЗначениеЗаполнено(ДатаНачала) Или Не ЗначениеЗаполнено(ДатаОкончания) Тогда
// Для демонстрации зададим значения по умолчанию, если они не заполнены
Если Не ЗначениеЗаполнено(ДатаНачала) Тогда
ДатаНачала = НачалоДня(ТекущаяДата());
КонецЕсли;
Если Не ЗначениеЗаполнено(ДатаОкончания) Тогда
ДатаОкончания = КонецДня(ТекущаяДата() + 60*60*24*6); // На неделю вперед
КонецЕсли;
// Если даты все равно не удалось установить, выходим
Если Не ЗначениеЗаполнено(ДатаНачала) Или Не ЗначениеЗаполнено(ДатаОкончания) Тогда
Возврат;
КонецЕсли;
КонецЕсли;
// --- Шаг 1: Подготовка массива реквизитов для изменения формы ---
МассивИзменяемыхРеквизитов = Новый Массив;
// Определяем имя для нашего реквизита типа "ТаблицаЗначений"
ИмяТаблицыДанных = "ДанныеПоДням";
// 1.1 Добавляем основной реквизит формы типа ТаблицаЗначений, если его нет
// Это наш контейнер для данных.
Если ЭтаФорма.Реквизиты.Найти(ИмяТаблицыДанных) = Неопределено Тогда
РеквизитТаблицы = Новый РеквизитФормы(ИмяТаблицыДанных, Новый ОписаниеТипов("ТаблицаЗначений"), , "Динамические данные по дням");
МассивИзменяемыхРеквизитов.Добавить(РеквизитТаблицы);
КонецЕсли;
// Для наглядности добавим статическую колонку "Наименование" в нашу таблицу
// (помимо динамических колонок с датами)
ИмяСтатическойКолонки = "Наименование";
Если ЭтаФорма.Реквизиты.Найти(ИмяТаблицыДанных + "." + ИмяСтатическойКолонки) = Неопределено Тогда
РеквизитСтатическойКолонки = Новый РеквизитФормы(ИмяСтатическойКолонки, Новый ОписаниеТипов("Строка"), ИмяТаблицыДанных, "Наименование");
МассивИзменяемыхРеквизитов.Добавить(РеквизитСтатическойКолонки);
КонецЕсли;
// 1.2 Добавляем реквизиты формы, которые будут колонками в ТаблицеЗначений
// Проходим по каждому дню в выбранном периоде
ДатаТекущая = ДатаНачала;
Пока ДатаТекущая <= ДатаОкончания Цикл
// Формируем уникальное имя для колонки. Например, "День_01_01_1999"
// Важно, чтобы имя было валидным идентификатором 1С.
ИмяКолонки = "День_" + Формат(ДатаТекущая, "ДФ=dd_MM_yyyy");
ЗаголовокКолонки = Формат(ДатаТекущая, "ДФ=dd.MM.yyyy");
// Если реквизит с таким именем уже существует, пропускаем его добавление.
// Это важно при повторных вызовах (например, после очистки).
Если ЭтаФорма.Реквизиты.Найти(ИмяТаблицыДанных + "." + ИмяКолонки) = Неопределено Тогда
// Создаем объект РеквизитФормы для колонки.
// ПутьКДанным для колонок таблицы значений указывается как "ИмяРеквизитаТаблицы.ИмяКолонки"
// В данном случае, тип данных для колонки выбран "Число" (можно использовать любой подходящий)
РеквизитКолонки = Новый РеквизитФормы(ИмяКолонки, Новый ОписаниеТипов("Число"), ИмяТаблицыДанных, ЗаголовокКолонки);
МассивИзменяемыхРеквизитов.Добавить(РеквизитКолонки);
КонецЕсли;
ДатаТекущая = ДатаТекущая + 60 * 60 * 24; // Переходим к следующему дню
КонецЦикла;
// 1.3 Применяем изменения к реквизитам формы
// Этот вызов приводит к пересозданию формы на сервере с новыми реквизитами.
Если МассивИзменяемыхРеквизитов.Количество() > 0 Тогда
ИзменитьРеквизиты(МассивИзменяемыхРеквизитов);
КонецЕсли;
// --- Шаг 2: Удаление старых динамических элементов и добавление новых ---
// Это важно, если ПриСозданииНаСервере может быть вызвано повторно
// или если на форме уже есть какая-то динамическая группа.
Если ЭтаФорма.Элементы.Найти("ГруппаДинамическаяТаблица") Тогда
ЭтаФорма.Элементы.Удалить(ЭтаФорма.Элементы.ГруппаДинамическаяТаблица);
КонецЕсли;
// 2.1 Создаем группу для размещения динамической таблицы.
// Это помогает структурировать элементы формы.
ГруппаДинамическая = ЭтаФорма.Элементы.Добавить("ГруппаДинамическаяТаблица", Тип("ГруппаФормы"));
ГруппаДинамическая.Вид = ВидГруппыФормы.ОбычнаяГруппа;
ГруппаДинамическая.Отображение = ОтображениеГруппыФормы.Обычное;
ГруппаДинамическая.Заголовок = "Результаты по дням";
// Располагаем динамическую группу после статических элементов
Если ЭтаФорма.Элементы.Найти("ГруппаПараметры") Тогда
ГруппаДинамическая.Положение = ПоложениеЭлементаФормы.Под;
ГруппаДинамическая.ОтносительныйЭлемент = ЭтаФорма.Элементы.ГруппаПараметры;
КонецЕсли;
// 2.2 Добавляем элемент "ТаблицаФормы", который будет отображать нашу ТаблицуЗначений.
ЭлементТаблица = ЭтаФорма.Элементы.Добавить(ИмяТаблицыДанных, Тип("ТаблицаФормы"), ГруппаДинамическая);
ЭлементТаблица.ПутьКДанным = ИмяТаблицыДанных; // Связываем элемент с реквизитом формы
ЭлементТаблица.ИзменятьСоставКолонок = Ложь; // Запрещаем пользователю менять состав колонок
ЭлементТаблица.ИзменятьПорядокКолонок = Истина; // Разрешаем менять порядок
ЭлементТаблица.Заголовок = "Данные по дням";
ЭлементТаблица.Высота = 15; // Примерная высота таблицы
// 2.3 Добавляем элементы "ПолеФормы" для каждой колонки в таблице.
// Эти элементы представляют собой визуальные колонки внутри элемента ТаблицаФормы.
// Добавляем статическую колонку "Наименование"
ЭлементПолеСтатическойКолонки = ЭтаФорма.Элементы.Добавить(ИмяСтатическойКолонки, Тип("ПолеФормы"), ЭлементТаблица);
ЭлементПолеСтатическойКолонки.ПутьКДанным = ИмяТаблицыДанных + "." + ИмяСтатическойКолонки;
ЭлементПолеСтатическойКолонки.Вид = ВидПоляФормы.ПолеВвода;
ЭлементПолеСтатическойКолонки.Заголовок = "Наименование";
ЭлементПолеСтатическойКолонки.Ширина = 20;
// Добавляем динамические колонки для каждого дня
ДатаТекущая = ДатаНачала;
Пока ДатаТекущая <= ДатаОкончания Цикл
ИмяКолонки = "День_" + Формат(ДатаТекущая, "ДФ=dd_MM_yyyy");
ЭлементПолеКолонки = ЭтаФорма.Элементы.Добавить(ИмяКолонки, Тип("ПолеФормы"), ЭлементТаблица);
ЭлементПолеКолонки.ПутьКДанным = ИмяТаблицыДанных + "." + ИмяКолонки; // Связываем с реквизитом-колонкой
ЭлементПолеКолонки.Вид = ВидПоляФормы.ПолеВвода;
ЭлементПолеКолонки.Заголовок = Формат(ДатаТекущая, "ДФ=dd.MM.yyyy");
ЭлементПолеКолонки.Ширина = 10;
ДатаТекущая = ДатаТекущая + 60 * 60 * 24;
КонецЦикла;
// --- Шаг 3: Заполнение ТаблицыЗначений данными (пример) ---
// Получаем доступ к программно созданной таблице реквизита формы
ДинамическаяТаблица = ЭтаФорма[ИмяТаблицыДанных];
Если ДинамическаяТаблица = Неопределено Тогда
Сообщить("Ошибка: Динамическая таблица не была создана.", СтатусСообщения.Важное);
Возврат;
КонецЕсли;
// Добавляем несколько строк для демонстрации данных
Для Инд = 1 По 3 Цикл // Например, 3 строки с данными
НоваяСтрока = ДинамическаяТаблица.Добавить();
НоваяСтрока.Наименование = "Строка " + Инд; // Заполняем статическую колонку
ДатаТекущая = ДатаНачала;
Пока ДатаТекущая <= ДатаОкончания Цикл
ИмяКолонки = "День_" + Формат(ДатаТекущая, "ДФ=dd_MM_yyyy");
// Заполняем динамические колонки. Здесь может быть любая логика.
// Для примера используем комбинацию даты и номера строки.
НоваяСтрока[ИмяКолонки] = Число(Формат(ДатаТекущая, "ДФ=ddMM")) * 10 + Инд;
ДатаТекущая = ДатаТекущая + 60 * 60 * 24;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура Сформировать(Команда)
// При изменении периода на уже открытой форме,
// самый простой способ обновить динамическую часть -
// это пересоздать форму. В реальных задачах можно использовать
// сложные механизмы удаления старых и добавления новых элементов,
// но это требует аккуратной работы с ИзменитьРеквизиты()
// и полным пониманием жизненного цикла формы.
// В данном примере, для простоты, мы предполагаем, что динамика
// формируется при первом открытии или при явном переоткрытии формы.
// Для более интерактивного изменения, можно программно закрыть
// и открыть форму с новыми параметрами.
Сообщить("Для изменения динамических колонок требуется перезапустить форму с новыми датами.", СтатусСообщения.Информация);
КонецПроцедуры
Давайте подробнее рассмотрим каждый из этапов, представленных в коде:
ПриСозданииНаСервере() мы получаем значения ДатаНачала и ДатаОкончания. Эти реквизиты могут быть заполнены пользователем на форме до ее создания (например, через параметры открытия), или мы можем задать значения по умолчанию, как показано в примере.МассивИзменяемыхРеквизитов. В него будут добавляться все реквизиты, которые мы хотим динамически создать или изменить на форме.ДанныеПоДням с типом ТаблицаЗначений. Это будет хранилище данных нашей будущей таблицы. Мы проверяем его наличие, чтобы не добавлять повторно при переоткрытии формы.Наименование, чтобы показать, как можно комбинировать статические и динамические колонки в одной таблице. Ее тип — Строка.Мы используем цикл Пока ДатаТекущая <= ДатаОкончания Цикл для итерации по каждому дню в выбранном диапазоне. Внутри цикла:
РеквизитФормы. Для колонок таблицы его ПутьКДанным указывается как "ИмяРеквизитаТаблицы.ИмяКолонки". В нашем случае это ИмяТаблицыДанных + "." + ИмяКолонки. Тип колонки выбираем Число, но вы можете использовать любой подходящий тип данных.РеквизитФормы добавляется в МассивИзменяемыхРеквизитов.ИзменитьРеквизиты(): После того как все необходимые реквизиты (и сама таблица, и ее колонки) были описаны и добавлены в массив, мы вызываем метод ИзменитьРеквизиты(МассивИзменяемыхРеквизитов). Этот вызов фактически перестраивает структуру реквизитов формы на сервере.ГруппаДинамическаяТаблица, если она уже существует. Это обеспечивает чистоту формы при повторных вызовах.ГруппаФормы (ГруппаДинамическаяТаблица), чтобы логически сгруппировать нашу динамическую таблицу и разместить ее на форме. Мы также указываем ее заголовок и положение относительно других элементов.ТаблицаФормы. Его ПутьКДанным связывается с нашим программно созданным реквизитом ИмяТаблицыДанных. Мы также можем настроить его свойства, такие как ИзменятьСоставКолонок, ИзменятьПорядокКолонок и Заголовок.Аналогично динамическому формированию реквизитов, мы проходим по всем дням периода (и статической колонке) и для каждой из них добавляем элемент ПолеФормы. Каждый такой элемент:
ПутьКДанным, указывающий на соответствующую колонку в реквизите таблицы (например, "ДанныеПоДням.Наименование" или "ДанныеПоДням.День_01_01_1999").Родитель, который является нашим элементом ТаблицаФормы (ЭлементТаблица), чтобы поля отображались как колонки таблицы.ДанныеПоДням (через ЭтаФорма[ИмяТаблицыДанных]) и заполнить его данными. В примере мы добавляем несколько строк и заполняем статическую и динамические колонки тестовыми значениями.Как мы уже упоминали, метод ИзменитьРеквизиты() должен вызываться в фазе инициализации формы (ПриСозданииНаСервере()). Если пользователь изменяет ДатаНачала или ДатаОкончания на уже открытой форме и нажимает кнопку «Сформировать», прямое повторное использование ИзменитьРеквизиты() в обработчике команды может привести к непредсказуемым результатам или ошибкам, поскольку форма уже создана и отображается.
Для обработки изменения периода на уже открытой форме есть несколько подходов:
ДатаНачала и ДатаОкончания в качестве параметров. Тогда при новом открытии формы вся логика в ПриСозданииНаСервере() будет выполнена заново с учетом новых параметров.ЭтаФорма.Элементы.Удалить()) и вызвать ИзменитьРеквизиты() с пустым массивом для удаления старых реквизитов, а затем с новыми. Однако это очень сложный и ресурсоемкий процесс, требующий глубокого понимания жизненного цикла формы и аккуратной работы, чтобы избежать ошибок и утечек памяти. Для большинства задач перезапуск формы является более надежным и простым решением.В нашем примере обработчик команды Сформировать() на клиенте просто информирует пользователя о необходимости перезапуска формы, что соответствует более простому и безопасному подходу.
ИзменитьРеквизиты() и массовым добавлением/удалением элементов формы являются ресурсоемкими. Используйте их только тогда, когда статический дизайн формы невозможен. Избегайте частых вызовов этих методов.ОписаниеТипов: При создании объектов РеквизитФормы и других динамических элементов, всегда указывайте полный тип данных с помощью Новый ОписаниеТипов("ИмяТипа"). Это гарантирует корректное поведение элементов.ГруппаФормы) для логического и визуального структурирования динамически создаваемых элементов. Свойства Положение и ОтносительныйЭлемент у элементов формы позволяют управлять их расположением на форме.ПриСозданииНаСервере() и инспектируйте коллекции ЭтаФорма.Реквизиты и ЭтаФорма.Элементы.Итак, мы рассмотрели, как создать внешнюю обработку в 1С с управляемой формой, которая динамически формирует реквизиты и элементы (в частности, колонки таблицы) на основе выбранного периода. Этот подход обеспечивает гибкость и позволяет адаптировать интерфейс программы под конкретные нужды пользователей, делая ее более функциональной и удобной.