Как в 1С реализовать таблицу с динамическим количеством колонок

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

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

Анализ вариантов хранения данных

Прежде чем приступать к разработке формы, проанализируем варианты хранения информации в базе данных. Выбор архитектуры напрямую влияет на производительность, ссылочную целостность и возможность строить отчеты.

  1. Хранение в объекте ХранилищеЗначения. В этом случае динамическая таблица значений целиком сериализуется и помещается в реквизит документа. Главный минус такого подхода — невозможность обращаться к данным с помощью языка запросов, строить отчеты в СКД и контролировать ссылочную целостность (при удалении ссылочных элементов ссылки внутри бинарных данных становятся «битыми»).
  2. Нормализованная табличная часть (рекомендуемый подход). В конфигурации создается плоская табличная часть с тремя базовыми реквизитами: объект строки (например, Студент), объект колонки (например, Дисциплина) и значение (например, Оценка). При открытии формы строки плоской таблицы разворачиваются в динамические колонки на форме, а перед записью — сворачиваются обратно. Данные остаются полностью доступными для запросов, регистров и отчетов.
  3. Специализированная обработка-генератор. Если логика работы типовых документов не должна изменяться, создается внешняя обработка с динамической таблицей ввода, которая при сохранении генерирует стандартные документы информационной базы.

Создание динамических колонок на управляемой форме

Рассмотрим по шагам реализацию кросс-таблицы с помощью программного добавления реквизитов и элементов управляемой формы. Разберем алгоритм перестроения формы на сервере.

Для добавления колонок используется метод ИзменитьРеквизиты(), после чего создаются визуальные элементы через коллекцию Элементы.


&НаСервере
Процедура СформироватьТаблицуНаСервере(МассивДисциплин)

    // 1. Очищаем ранее созданные элементы и реквизиты
    МассивУдаляемыхРеквизитов = Новый Массив;
    Для Каждого Колонка Из Элементы.ТаблицаУспеваемости.ПодчиненныеЭлементы Цикл
        Если СтрНачинаетсяС(Колонка.Имя, "КолонкаДисциплина_") Тогда
            Элементы.Удалить(Колонка);
            МассивУдаляемыхРеквизитов.Добавить("ТаблицаУспеваемости." + Колонка.Имя);
        КонецЕсли;
    КонецЦикла;
    
    Если МассивУдаляемыхРеквизитов.Количество() > 0 Тогда
        ИзменитьРеквизиты(, МассивУдаляемыхРеквизитов);
    КонецЕсли;

    // 2. Формируем новые динамические реквизиты
    МассивДобавляемыхРеквизитов = Новый Массив;
    ОписаниеТипаЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(2, 0, ДопустимыйЗнак.Неотрицательный));
    
    Для Индекс = 0 По МассивДисциплин.Количество() - 1 Цикл
        ИмяРеквизита = "КолонкаДисциплина_" + Формат(Индекс, "ЧГ=0");
        Реквизит = Новый РеквизитФормы(ИмяРеквизита, ОписаниеТипаЧисло, "ТаблицаУспеваемости", Строка(МассивДисциплин[Индекс]));
        МассивДобавляемыхРеквизитов.Добавить(Реквизит);
    КонецЦикла;
    
    ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);

    // 3. Создаем визуальные элементы формы
    Для Индекс = 0 По МассивДисциплин.Количество() - 1 Цикл
        ИмяРеквизита = "КолонкаДисциплина_" + Формат(Индекс, "ЧГ=0");
        НовоеПоле = Элементы.Добавить(ИмяРеквизита, Тип("ПолеФормы"), Элементы.ТаблицаУспеваемости);
        НовоеПоле.Вид = ВидПоляФормы.ПолеВвода;
        НовоеПоле.ПутьКДанным = "ТаблицаУспеваемости." + ИмяРеквизита;
        НовоеПоле.Заголовок = Строка(МассивДисциплин[Индекс]);
    КонецЦикла;

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

Синхронизация кросс-таблицы с данными документа

Разберем, как связать визуальную динамическую матрицу на форме с плоской табличной частью объекта при чтении и записи документа.

  1. При чтении данных на сервере: считываем строки из плоской табличной части Объект.Оценки, заполняем строки таблицы формы уникальными студентами, а оценки распределяем по соответствующим динамическим колонкам.
  2. Перед записью на сервере: очищаем табличную часть Объект.Оценки, обходим строки и колонки коллекции ТаблицаУспеваемости на форме и для каждого заполненного значения создаем строку в табличной части.

&НаСервере
Процедура ПеренестиДанныеВТабличнуюЧасть()

    Объект.Оценки.Очистить();
    
    Для Каждого СтрФормы Из ТаблицаУспеваемости Цикл
        Для Индекс = 0 По МассивДисциплин.Количество() - 1 Цикл
            ИмяРеквизита = "КолонкаДисциплина_" + Формат(Индекс, "ЧГ=0");
            ЗначениеОценки = СтрФормы[ИмяРеквизита];
            
            Если ЗначениеЗаполнено(ЗначениеОценки) Тогда
                НоваяСтрока = Объект.Оценки.Добавить();
                НоваяСтрока.Студент = СтрФормы.Студент;
                НоваяСтрока.Дисциплина = МассивДисциплин[Индекс];
                НоваяСтрока.Оценка = ЗначениеОценки;
            КонецЕсли;
        КонецЦикла;
    КонецЦикла;

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

Работа с объектом ХранилищеЗначения

Если для конкретной задачи нормализация признана избыточной и выбрано хранение в реквизите с типом ХранилищеЗначения, важно учитывать особенности преобразования типов на форме. Данные таблицы формы представлены объектом ДанныеФормыКоллекция, который нельзя напрямую сериализовать.

Для корректной записи структуры используем метод РеквизитФормыВЗначение(), который трансформирует данные коллекции в полноценную ТаблицаЗначений:


&НаСервере
Процедура СохранитьВХранилище()

    // Преобразуем реквизит формы в Таблицу Значений
    ТаблицаДляСохранения = РеквизитФормыВЗначение("ТаблицаУспеваемости");
    
    // Помещаем таблицу в ХранилищеЗначения реквизита объекта
    Объект.ХранилищеТаблицы = Новый ХранилищеЗначения(ТаблицаДляСохранения, Новый СжатиеДанных(9));

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

Важный момент: при вызове метода РеквизитФормыВЗначение() в качестве параметра необходимо передавать точное имя реквизита формы (например, "ТаблицаУспеваемости"), а не путь через основной реквизит "Объект.ТаблицаУспеваемости".

← На главную