Как программно добавить реквизиты и элементы управления на управляемую форму 1С в зависимости от выбранного периода?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с платформой 1С:Предприятие 8.3 иногда возникает необходимость в создании форм, состав реквизитов и элементов которых не определен заранее, а должен динамически формироваться в зависимости от каких-либо условий, например, от выбранного пользователем периода. Такая задача является достаточно распространенной для разработчиков, поскольку позволяет создавать гибкие и адаптивные интерфейсы, не перегружая конфигурацию избыточными статическими объектами. В этой статье мы подробно разберем, как создать внешнюю обработку с управляемой формой, которая будет динамически формировать колонки таблицы по выбранному диапазону дат.

Создание внешней обработки и статических элементов формы

Прежде чем мы приступим к динамическому формированию элементов, давайте создадим основу нашей внешней обработки. Начнем с самых базовых шагов:

  1. Создание внешней обработки: В конфигураторе 1С выберите меню «Файл» – «Новый...» и в появившемся окне выберите «Внешняя обработка». Сохраните ее на диск.
  2. Добавление управляемой формы: В дереве объектов новой обработки правой кнопкой мыши кликните на ветку «Формы» и выберите «Добавить...». Укажите имя формы (например, «Форма») и выберите тип «Форма обработки».

Для того чтобы пользователь мог выбрать период, нам потребуются два статических реквизита на форме: «ДатаНачала» и «ДатаОкончания». Также добавим команду и кнопку для запуска процесса формирования таблицы.

На закладке «Реквизиты» формы добавьте следующие реквизиты:

Затем перейдите на закладку «Команды» и создайте новую команду:

На закладке «Элементы» формы создадим группу для размещения полей ввода дат и кнопку:

  1. Добавьте элемент ГруппаФормы (например, «ГруппаПараметры») с видом «Обычная группа».
  2. Внутрь этой группы добавьте два элемента ПолеФормы:
    • Для реквизита ДатаНачала.
    • Для реквизита ДатаОкончания.
  3. Добавьте элемент КнопкаФормы, связанный с командой Сформировать.

На данном этапе структура нашей формы будет выглядеть примерно так:


// В конфигураторе:
// Реквизиты формы:
//  - ДатаНачала (Тип: Дата)
//  - ДатаОкончания (Тип: Дата)

// Команды формы:
//  - Сформировать (Действие: СформироватьНаСервере)

// Элементы формы:
//  - ГруппаПараметры (Тип: ГруппаФормы)
//    - ПолеДатаНачала (Тип: ПолеФормы, ПутьКДанным: ДатаНачала)
//    - ПолеДатаОкончания (Тип: ПолеФормы, ПутьКДанным: ДатаОкончания)
//    - КнопкаСформировать (Тип: КнопкаФормы, ИмяКоманды: Сформировать)

Основные принципы динамического изменения управляемой формы

В управляемых формах 1С динамическое изменение состава реквизитов и элементов формы осуществляется с помощью двух ключевых методов и одного служебного объекта:

  1. ИзменитьРеквизиты(): Этот метод формы позволяет программно добавлять, изменять или удалять реквизиты формы. Он принимает массив объектов типа РеквизитФормы. Критически важно понимать, что вызов этого метода ведет к частичному пересозданию формы на сервере, поэтому его следует использовать обдуманно и, как правило, в обработчике события ПриСозданииНаСервере().
  2. ЭтаФорма.Элементы.Добавить(): Этот метод коллекции элементов формы позволяет программно добавлять новые элементы управления (поля, кнопки, группы, таблицы и т.д.) на форму. Он возвращает объект ЭлементФормы, свойства которого можно настроить.
  3. РеквизитФормы: Это служебный объект, который описывает реквизит, добавляемый на форму. При его создании мы указываем имя, тип, путь к данным (если это реквизит подчиненной таблицы) и заголовок.

Основным местом для выполнения операций по динамическому созданию реквизитов и элементов является процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка) формы. Именно здесь форма находится в подходящем состоянии для таких глубоких изменений своей структуры.

Программное формирование динамических колонок таблицы по выбранному периоду

Теперь перейдем к реализации основной задачи: созданию таблицы с динамическими колонками, соответствующими дням выбранного периода. Нам потребуется следующее:

  1. Динамическое создание реквизита формы типа «ТаблицаЗначений»: Этот реквизит будет служить источником данных для нашей динамической таблицы на форме.
  2. Динамическое добавление колонок к этому реквизиту: Для каждого дня в выбранном периоде мы будем добавлять отдельный реквизит, который будет представлять собой колонку в нашей таблице.
  3. Добавление элемента «ТаблицаФормы» на управляемую форму: Визуальное отображение нашей таблицы данных.
  4. Добавление элементов «ПолеФормы» для каждой динамической колонки: Чтобы каждая программно созданная колонка была видна и доступна для взаимодействия на форме.
  5. Заполнение динамической таблицы данными: После создания структуры мы заполним ее тестовыми данными.

Весь код, ответственный за динамическое формирование, мы разместим в процедуре ПриСозданииНаСервере(), которая будет вызываться при открытии формы.


// В модуле формы обработки
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

    // Проверяем, заданы ли даты. Если нет, то динамическую часть не формируем.
    // Предполагаем, что ДатаНачала и ДатаОкончания - это реквизиты формы,
    // которые могут быть заполнены из параметров при открытии или по умолчанию.
    Если Не ЗначениеЗаполнено(ДатаНачала) Или Не ЗначениеЗаполнено(ДатаОкончания) Тогда
        // Для демонстрации зададим значения по умолчанию, если они не заполнены
        Если Не ЗначениеЗаполнено(ДатаНачала) Тогда
            ДатаНачала = НачалоДня(ТекущаяДата());
        КонецЕсли;
        Если Не ЗначениеЗаполнено(ДатаОкончания) Тогда
            ДатаОкончания = КонецДня(ТекущаяДата() + 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;
        КонецЦикла;
    КонецЦикла;

КонецПроцедуры

&НаКлиенте
Процедура Сформировать(Команда)
    // При изменении периода на уже открытой форме,
    // самый простой способ обновить динамическую часть -
    // это пересоздать форму. В реальных задачах можно использовать
    // сложные механизмы удаления старых и добавления новых элементов,
    // но это требует аккуратной работы с ИзменитьРеквизиты()
    // и полным пониманием жизненного цикла формы.
    // В данном примере, для простоты, мы предполагаем, что динамика
    // формируется при первом открытии или при явном переоткрытии формы.
    // Для более интерактивного изменения, можно программно закрыть
    // и открыть форму с новыми параметрами.
    Сообщить("Для изменения динамических колонок требуется перезапустить форму с новыми датами.", СтатусСообщения.Информация);
КонецПроцедуры

Давайте подробнее рассмотрим каждый из этапов, представленных в коде:

  1. Получение периода: В начале процедуры ПриСозданииНаСервере() мы получаем значения ДатаНачала и ДатаОкончания. Эти реквизиты могут быть заполнены пользователем на форме до ее создания (например, через параметры открытия), или мы можем задать значения по умолчанию, как показано в примере.
  2. Создание массива реквизитов: Мы инициализируем пустой массив МассивИзменяемыхРеквизитов. В него будут добавляться все реквизиты, которые мы хотим динамически создать или изменить на форме.
  3. Добавление реквизита «ТаблицаЗначений»: Сначала мы добавляем основной реквизит ДанныеПоДням с типом ТаблицаЗначений. Это будет хранилище данных нашей будущей таблицы. Мы проверяем его наличие, чтобы не добавлять повторно при переоткрытии формы.
  4. Добавление статических колонок: Для примера мы добавили колонку Наименование, чтобы показать, как можно комбинировать статические и динамические колонки в одной таблице. Ее тип — Строка.
  5. Формирование динамических колонок:

    Мы используем цикл Пока ДатаТекущая <= ДатаОкончания Цикл для итерации по каждому дню в выбранном диапазоне. Внутри цикла:

    • Генерируется уникальное имя для каждой колонки (например, "День_01_01_1999") и заголовок (форматированная дата). Важно, чтобы имя реквизита было уникальным и соответствовало правилам именования идентификаторов в 1С.
    • Создается новый объект РеквизитФормы. Для колонок таблицы его ПутьКДанным указывается как "ИмяРеквизитаТаблицы.ИмяКолонки". В нашем случае это ИмяТаблицыДанных + "." + ИмяКолонки. Тип колонки выбираем Число, но вы можете использовать любой подходящий тип данных.
    • Каждый созданный объект РеквизитФормы добавляется в МассивИзменяемыхРеквизитов.
  6. Вызов ИзменитьРеквизиты(): После того как все необходимые реквизиты (и сама таблица, и ее колонки) были описаны и добавлены в массив, мы вызываем метод ИзменитьРеквизиты(МассивИзменяемыхРеквизитов). Этот вызов фактически перестраивает структуру реквизитов формы на сервере.
  7. Удаление старых элементов: Перед добавлением новых динамических элементов рекомендуется удалить старые. В нашем примере мы удаляем всю группу ГруппаДинамическаяТаблица, если она уже существует. Это обеспечивает чистоту формы при повторных вызовах.
  8. Создание группы для динамической таблицы: Мы создаем элемент ГруппаФормы (ГруппаДинамическаяТаблица), чтобы логически сгруппировать нашу динамическую таблицу и разместить ее на форме. Мы также указываем ее заголовок и положение относительно других элементов.
  9. Добавление элемента «ТаблицаФормы»: Мы добавляем на форму визуальный элемент типа ТаблицаФормы. Его ПутьКДанным связывается с нашим программно созданным реквизитом ИмяТаблицыДанных. Мы также можем настроить его свойства, такие как ИзменятьСоставКолонок, ИзменятьПорядокКолонок и Заголовок.
  10. Добавление элементов «ПолеФормы» для колонок:

    Аналогично динамическому формированию реквизитов, мы проходим по всем дням периода (и статической колонке) и для каждой из них добавляем элемент ПолеФормы. Каждый такой элемент:

    • Имеет имя, совпадающее с именем колонки.
    • Имеет ПутьКДанным, указывающий на соответствующую колонку в реквизите таблицы (например, "ДанныеПоДням.Наименование" или "ДанныеПоДням.День_01_01_1999").
    • Указывает Родитель, который является нашим элементом ТаблицаФормы (ЭлементТаблица), чтобы поля отображались как колонки таблицы.
    • Настраивается заголовок, вид и ширина.
  11. Заполнение таблицы данными: После того как структура реквизитов и элементов создана, мы можем получить доступ к нашему реквизиту формы ДанныеПоДням (через ЭтаФорма[ИмяТаблицыДанных]) и заполнить его данными. В примере мы добавляем несколько строк и заполняем статическую и динамические колонки тестовыми значениями.

Обработка изменения периода и повторное формирование

Как мы уже упоминали, метод ИзменитьРеквизиты() должен вызываться в фазе инициализации формы (ПриСозданииНаСервере()). Если пользователь изменяет ДатаНачала или ДатаОкончания на уже открытой форме и нажимает кнопку «Сформировать», прямое повторное использование ИзменитьРеквизиты() в обработчике команды может привести к непредсказуемым результатам или ошибкам, поскольку форма уже создана и отображается.

Для обработки изменения периода на уже открытой форме есть несколько подходов:

  1. Перезапуск формы: Самый простой и часто рекомендуемый способ – это программно закрыть текущую форму и открыть ее заново, передав новые значения ДатаНачала и ДатаОкончания в качестве параметров. Тогда при новом открытии формы вся логика в ПриСозданииНаСервере() будет выполнена заново с учетом новых параметров.
  2. Сложное удаление и добавление: Теоретически, можно программно удалить все динамически созданные элементы формы (ЭтаФорма.Элементы.Удалить()) и вызвать ИзменитьРеквизиты() с пустым массивом для удаления старых реквизитов, а затем с новыми. Однако это очень сложный и ресурсоемкий процесс, требующий глубокого понимания жизненного цикла формы и аккуратной работы, чтобы избежать ошибок и утечек памяти. Для большинства задач перезапуск формы является более надежным и простым решением.

В нашем примере обработчик команды Сформировать() на клиенте просто информирует пользователя о необходимости перезапуска формы, что соответствует более простому и безопасному подходу.

Важные нюансы и рекомендации

Итак, мы рассмотрели, как создать внешнюю обработку в 1С с управляемой формой, которая динамически формирует реквизиты и элементы (в частности, колонки таблицы) на основе выбранного периода. Этот подход обеспечивает гибкость и позволяет адаптировать интерфейс программы под конкретные нужды пользователей, делая ее более функциональной и удобной.

← На главную