Как программно добавить итоги по строкам и столбцам в динамически созданной таблице на форме 1С?

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

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

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

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

Прежде чем перейти к итогам, давайте вспомним, как программно создается таблица на форме. Мы добавляем элемент формы типа ТаблицаФормы и привязываем его к реквизиту формы типа ТаблицаЗначений. Затем для каждой колонки таблицы создаем элемент формы типа ПолеФормы и привязываем его к соответствующей колонке ТаблицыЗначений.


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

// Определим колонки для ТаблицыЗначений (пример)
НоваяТаблицаЗначений = Новый ТаблицаЗначений();
НоваяТаблицаЗначений.Колонки.Добавить("Наименование", Тип("Строка"));
НоваяТаблицаЗначений.Колонки.Добавить("Количество", Тип("Число"));
НоваяТаблицаЗначений.Колонки.Добавить("Цена", Тип("Число"));
НоваяТаблицаЗначений.Колонки.Добавить("Сумма", Тип("Число"));

// Добавим реквизит таблицы и его колонки
// После вызова ИзменитьРеквизиты() мы сможем обращаться к ТаблицаДанных как к реквизиту формы
РеквизитыФормы.Добавить(Новый РеквизитФормы("ТаблицаДанных", Тип("ТаблицаЗначений"), , , НоваяТаблицаЗначений.Колонки));
ЭтаФорма.ИзменитьРеквизиты(РеквизитыФормы);

// Присвоим пустую ТЗ реквизиту формы, чтобы он был создан
ТаблицаДанных = НоваяТаблицаЗначений;

// Создадим элемент таблицы на форме
Элементы.Добавить("ЭлементТаблицаДанных", Тип("ТаблицаФормы"));
Элементы.ЭлементТаблицаДанных.ПутьКДанным = "ТаблицаДанных";
Элементы.ЭлементТаблицаДанных.ТолькоПросмотр = Ложь; // Если хотим редактировать

// Создадим колонки для таблицы на форме
Элементы.Добавить("ЭлементТаблицаДанныхНаименование", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхНаименование.ПутьКДанным = "ТаблицаДанных.Наименование";
Элементы.ЭлементТаблицаДанныхНаименование.Заголовок = "Наименование";

Элементы.Добавить("ЭлементТаблицаДанныхКоличество", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхКоличество.ПутьКДанным = "ТаблицаДанных.Количество";
Элементы.ЭлементТаблицаДанныхКоличество.Заголовок = "Количество";

Элементы.Добавить("ЭлементТаблицаДанныхЦена", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхЦена.ПутьКДанным = "ТаблицаДанных.Цена";
Элементы.ЭлементТаблицаДанныхЦена.Заголовок = "Цена";

Элементы.Добавить("ЭлементТаблицаДанныхСумма", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхСумма.ПутьКДанным = "ТаблицаДанных.Сумма";
Элементы.ЭлементТаблицаДанныхСумма.Заголовок = "Сумма";

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

Добавление итогов по строкам

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

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

Рассмотрим пример добавления колонки "Итог по строке", которая будет суммировать "Количество" и "Цену" для каждой строки (хотя в реальной жизни это скорее будет "Сумма" * "Количество").


// ... (продолжение предыдущего кода создания таблицы)

// Добавим колонку для итогов по строкам в ТаблицуЗначений
НоваяТаблицаЗначений.Колонки.Добавить("ИтогПоСтроке", Тип("Число"));

// После изменения реквизитов формы и создания элементов таблицы
// Создадим элемент колонки для ИтогаПоСтроке
Элементы.Добавить("ЭлементТаблицаДанныхИтогПоСтроке", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхИтогПоСтроке.ПутьКДанным = "ТаблицаДанных.ИтогПоСтроке";
Элементы.ЭлементТаблицаДанныхИтогПоСтроке.Заголовок = "Итог по строке";

// Пример заполнения данных и расчета итогов по строкам
Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
    // Предположим, что данные уже заполнены, или мы их генерируем
    СтрокаТЗ.Количество = СлучайноеЧисло(1, 10);
    СтрокаТЗ.Цена = СлучайноеЧисло(100, 1000);
    СтрокаТЗ.Сумма = СтрокаТЗ.Количество * СтрокаТЗ.Цена;

    // Рассчитываем итог по строке (например, сумма Количество и Цена)
    // В реальной задаче это будет логика исходя из ваших потребностей
    СтрокаТЗ.ИтогПоСтроке = СтрокаТЗ.Количество + СтрокаТЗ.Цена;
КонецЦикла;

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

Добавление итогов по колонкам (подвал таблицы)

Итоги по колонкам обычно отображаются в подвале таблицы (нижняя часть). Для программно созданных таблиц на управляемых формах существует два основных метода реализации таких итогов.

Метод 1: Использование свойства "ТекстПодвала"

Этот метод является наиболее простым для реализации, но требует ручного расчета и присвоения значений каждому полю подвала. Мы будем использовать свойство ТекстПодвала элемента формы типа ПолеФормы, соответствующего нашей колонке.

  1. Включим отображение подвала для таблицы: Для элемента ТаблицаФормы необходимо установить свойство Подвал = Истина.
  2. Рассчитаем итоги: Для каждой числовой колонки, по которой нужен итог, мы программно рассчитываем суммарное (или другое агрегированное) значение по всем строкам ТаблицыЗначений. Для ТаблицыЗначений удобно использовать метод Итог().
  3. Присвоим значение свойству ТекстПодвала: Полученное итоговое значение присваиваем свойству ТекстПодвала соответствующего элемента ПолеФормы.

Проанализируем шаги подробнее на примере:


// ... (после создания элементов таблицы и заполнения ТаблицаДанных)

// 1. Включим отображение подвала для таблицы
Элементы.ЭлементТаблицаДанных.Подвал = Истина;

// 2. Рассчитаем итоги по колонкам (например, для Количество, Цена, Сумма)
// Для ТаблицыЗначений можно использовать метод Итог()
// Для примера, возьмем итоги для колонок "Количество" и "Сумма"

// Итог по колонке "Количество"
ИтогКоличество = ТаблицаДанных.Итог("Количество");
// Итог по колонке "Сумма"
ИтогСумма = ТаблицаДанных.Итог("Сумма");

// 3. Присвоим значения свойству ТекстПодвала
// Для колонки "Количество"
Элементы.ЭлементТаблицаДанныхКоличество.ТекстПодвала = Строка(ИтогКоличество);
// Для колонки "Сумма"
Элементы.ЭлементТаблицаДанныхСумма.ТекстПодвала = Строка(ИтогСумма);

// Очистим подвал для других колонок, если они не числовые или не требуют итога
// Например, для колонки "Наименование" подвал может быть пустым или содержать текст "Итого:"
Элементы.ЭлементТаблицаДанныхНаименование.ТекстПодвала = "Итого:";
// Для колонки "Цена" в этом примере мы не считали итог
Элементы.ЭлементТаблицаДанныхЦена.ТекстПодвала = "";
// Для колонки "ИтогПоСтроке" также можно вывести общий итог
ИтогОбщийПоСтрокам = ТаблицаДанных.Итог("ИтогПоСтроке");
Элементы.ЭлементТаблицаДанныхИтогПоСтроке.ТекстПодвала = Строка(ИтогОбщийПоСтрокам);

Преимущества: Простота реализации. Не требуется создавать дополнительные реквизиты формы.

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

Метод 2: Использование свойства "ПутьКДаннымПодвала" с программно созданными реквизитами

Этот метод более гибок и позволяет имитировать поведение табличных частей объектов метаданных, где итоги "привязываются" к данным. Для каждой числовой колонки, по которой требуется итог, мы должны программно создать отдельный реквизит формы, который будет хранить это итоговое значение. Затем свойству ПутьКДаннымПодвала элемента ПолеФормы присваивается путь к этому новому реквизиту.

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

Рассмотрим этот метод по шагам. Этот подход был подробно рассмотрен в сообщении 12 форума:


// ... (продолжение кода, например, в ПриСозданииНаСервере)

// Массив для хранения новых реквизитов формы, которые будут использоваться для подвала
НовыеРеквизитыПодвала = Новый Массив();

// 1. Включим отображение подвала для таблицы
Элементы.ЭлементТаблицаДанных.Подвал = Истина;

// Пройдем по колонкам нашей ТаблицыЗначений, чтобы определить, по каким нужны итоги
Для Каждого КолонкаТЗ Из ТаблицаДанных.Колонки Цикл
    // Создаем элементы ПолеФормы, если они еще не созданы
    // Или используем уже созданные элементы (как в нашем примере выше)
    ИмяЭлементаКолонки = "ЭлементТаблицаДанных" + КолонкаТЗ.Имя;
    Если Элементы.Найти(ИмяЭлементаКолонки) = Неопределено Тогда
        // Если элементы еще не созданы, создадим их здесь
        НовыйЭлементПоля = Элементы.Добавить(ИмяЭлементаКолонки, Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
        НовыйЭлементПоля.ПутьКДанным = "ТаблицаДанных." + КолонкаТЗ.Имя;
        НовыйЭлементПоля.Заголовок = КолонкаТЗ.Заголовок;
    КонецЕсли;
    
    // Получим ссылку на элемент ПолеФормы
    ЭлементПоля = Элементы[ИмяЭлементаКолонки];

    // 2. Создадим программные реквизиты формы для каждого итога
    // Подвал только для колонок с числовым типом
    Если КолонкаТЗ.ТипЗначения.СодержитТип(Тип("Число")) Тогда
        ИмяРеквизитаИтога = "Итог_" + КолонкаТЗ.Имя;
        Если ЭтаФорма.Реквизиты.Найти(ИмяРеквизитаИтога) = Неопределено Тогда
            НовыйРеквизитИтога = Новый РеквизитФормы(ИмяРеквизитаИтога, КолонкаТЗ.ТипЗначения);
            НовыеРеквизитыПодвала.Добавить(НовыйРеквизитИтога);
        КонецЕсли;

        // 3. Установим ПутьКДаннымПодвала
        ЭлементПоля.ПутьКДаннымПодвала = ИмяРеквизитаИтога;
    Иначе
        // Для нечисловых колонок можно установить статический текст в подвал
        Если КолонкаТЗ.Имя = "Наименование" Тогда
            ЭлементПоля.ТекстПодвала = "Итого:";
        Иначе
            ЭлементПоля.ТекстПодвала = ""; // Очищаем подвал для других нечисловых
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

// После того, как все нужные реквизиты для подвала добавлены в массив,
// необходимо изменить реквизиты формы
Если НовыеРеквизитыПодвала.Количество() > 0 Тогда
    ЭтаФорма.ИзменитьРеквизиты(НовыеРеквизитыПодвала);
КонецЕсли;

// 4. Рассчитаем и заполним значения реквизитов подвала
// (это можно сделать после заполнения ТаблицаДанных, например, в отдельной процедуре)
// Пример расчета
Процедура РассчитатьИтогиПодвала()
    Для Каждого КолонкаТЗ Из ТаблицаДанных.Колонки Цикл
        Если КолонкаТЗ.ТипЗначения.СодержитТип(Тип("Число")) Тогда
            ИмяРеквизитаИтога = "Итог_" + КолонкаТЗ.Имя;
            Если ЭтаФорма.Реквизиты.Найти(ИмяРеквизитаИтога) <> Неопределено Тогда
                // Рассчитываем итог по колонке ТаблицыЗначений
                РассчитанныйИтог = ТаблицаДанных.Итог(КолонкаТЗ.Имя);
                // Присваиваем значение программному реквизиту формы
                ЭтаФорма[ИмяРеквизитаИтога] = РассчитанныйИтог;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

// Вызываем после заполнения ТаблицаДанных
// РассчитатьИтогиПодвала();

Преимущества: Большая гибкость, имитация поведения стандартных табличных частей. После связывания ПутьКДаннымПодвала с реквизитом, отображение значения в подвале будет автоматически обновляться при изменении значения этого реквизита.

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

Автоматический пересчет итогов при изменении данных

Как уже упоминалось, для программно созданных таблиц автоматический пересчет итогов "из коробки" не происходит. Если мы хотим, чтобы итоги обновлялись в реальном времени при изменении данных пользователем, нам необходимо реализовать этот механизм вручную.

Мы реализуем пересчет через обработчики событий элементов формы (для быстрой отладки алгоритмов пригодится встраиваемая консоль кода — для этого есть встраиваемая консоль кода для разработчика 1С):

  1. Назначим обработчик события ПриИзменении: Для каждого элемента ПолеФормы, соответствующего числовой колонке, мы должны назначить обработчик события ПриИзменении.
  2. Реализуем логику пересчета: В обработчике мы будем перехватывать изменение значения, определять измененную строку и колонку, а затем пересчитывать необходимые итоги.

Рассмотрим, как это сделать:


// ... (после создания элементов таблицы)

// Пример назначения обработчика для колонок "Количество" и "Цена", которые влияют на итоги
// Это делается для каждого элемента ПолеФормы, который может влиять на итоги
Элементы.ЭлементТаблицаДанныхКоличество.УстановитьДействие("ПриИзменении", "ЭлементТаблицаДанных_ПриИзменении");
Элементы.ЭлементТаблицаДанныхЦена.УстановитьДействие("ПриИзменении", "ЭлементТаблицаДанных_ПриИзменении");
Элементы.ЭлементТаблицаДанныхСумма.УстановитьДействие("ПриИзменении", "ЭлементТаблицаДанных_ПриИзменении"); // Если Сумма тоже может быть изменена вручную

// Создадим процедуру-обработчик на клиенте
// В модуле формы
Процедура ЭлементТаблицаДанных_ПриИзменении(Элемент)
    // Получаем текущую строку таблицы
    ТекущиеДанныеСтроки = Элементы.ЭлементТаблицаДанных.ТекущиеДанные;
    Если ТекущиеДанныеСтроки = Неопределено Тогда
        Возврат;
    КонецЕсли;

    // Определяем имя колонки, которая была изменена
    ИмяКолонки = СтрЗаменить(Элемент.ПутьКДанным, "ТаблицаДанных.", "");

    // Пересчитываем итог по строке для текущей строки
    // Если изменено Количество или Цена, пересчитываем Сумму и ИтогПоСтроке
    Если ИмяКолонки = "Количество" Или ИмяКолонки = "Цена" Тогда
        ТекущиеДанныеСтроки.Сумма = ТекущиеДанныеСтроки.Количество * ТекущиеДанныеСтроки.Цена;
        ТекущиеДанныеСтроки.ИтогПоСтроке = ТекущиеДанныеСтроки.Количество + ТекущиеДанныеСтроки.Цена; // Пример
    ИначеЕсли ИмяКолонки = "Сумма" Тогда
        // Если изменили Сумму, то, возможно, нужно пересчитать ИтогПоСтроке
        ТекущиеДанныеСтроки.ИтогПоСтроке = ТекущиеДанныеСтроки.Количество + ТекущиеДанныеСтроки.Цена; // Пример
    КонецЕсли;

    // После пересчета итогов по строке, нужно пересчитать итоги по колонкам
    // Это можно сделать в отдельной процедуре, вызываемой здесь
    РассчитатьИтогиПодвалаКлиент();

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

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

    // Если используем Метод 2 (ПутьКДаннымПодвала)
    Для Каждого КолонкаТЗ Из ТаблицаДанных.Колонки Цикл
        Если КолонкаТЗ.ТипЗначения.СодержитТип(Тип("Число")) Тогда
            ИмяРеквизитаИтога = "Итог_" + КолонкаТЗ.Имя;
            Если ЭтаФорма.Реквизиты.Найти(ИмяРеквизитаИтога) <> Неопределено Тогда
                // Рассчитываем итог по колонке ТаблицыЗначений
                РассчитанныйИтог = ТаблицаДанных.Итог(КолонкаТЗ.Имя);
                // Присваиваем значение программному реквизиту формы
                ЭтаФорма[ИмяРеквизитаИтога] = РассчитанныйИтог;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Важные моменты:

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

Общие рекомендации и оптимизация

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

← На главную