При разработке интерфейсов на платформе 1С:Предприятие 8.3 в управляемых формах часто возникает необходимость динамического формирования таблиц, например, на основе произвольного запроса к базе данных или внешних источников. Такая программно созданная таблица (чаще всего на базе объекта ТаблицаЗначений) на форме не обладает всей функциональностью табличных частей объектов метаданных, в частности, автоматическим расчетом и отображением итогов.
В этой статье мы подробно рассмотрим, как добавить итоги по строкам и столбцам в программно созданную таблицу, а также как обеспечить их динамический пересчет при изменении данных. Мы будем опираться на опыт и решения, представленные в обсуждении, а также на общие принципы программирования на платформе 1С.
Прежде чем перейти к итогам, давайте вспомним, как программно создается таблица на форме. Мы добавляем элемент формы типа ТаблицаФормы и привязываем его к реквизиту формы типа ТаблицаЗначений. Затем для каждой колонки таблицы создаем элемент формы типа ПолеФормы и привязываем его к соответствующей колонке ТаблицыЗначений.
// Пример создания реквизита формы для таблицы
// В модуле формы в обработчике ПриСозданииНаСервере или в отдельной процедуре
// Создадим реквизит таблицы
РеквизитыФормы = Новый Массив();
РеквизитыФормы.Добавить(Новый РеквизитФормы("ТаблицаДанных", Тип("ТаблицаЗначений")));
// Определим колонки для ТаблицыЗначений (пример)
НоваяТаблицаЗначений = Новый ТаблицаЗначений();
НоваяТаблицаЗначений.Колонки.Добавить("Наименование", Тип("Строка"));
НоваяТаблицаЗначений.Колонки.Добавить("Количество", Тип("Число"));
НоваяТаблицаЗначений.Колонки.Добавить("Цена", Тип("Число"));
НоваяТаблицаЗначений.Колонки.Добавить("Сумма", Тип("Число"));
// Добавим реквизит таблицы и его колонки
// После вызова ИзменитьРеквизиты() мы сможем обращаться к ТаблицаДанных как к реквизиту формы
РеквизитыФормы.Добавить(Новый РеквизитФормы("ТаблицаДанных", Тип("ТаблицаЗначений"), , , НоваяТаблицаЗначений.Колонки));
ЭтаФорма.ИзменитьРеквизиты(РеквизитыФормы);
// Присвоим пустую ТЗ реквизиту формы, чтобы он был создан
ТаблицаДанных = НоваяТаблицаЗначений;
// Создадим элемент таблицы на форме
Элементы.Добавить("ЭлементТаблицаДанных", Тип("ТаблицаФормы"));
Элементы.ЭлементТаблицаДанных.ПутьКДанным = "ТаблицаДанных";
Элементы.ЭлементТаблицаДанных.ТолькоПросмотр = Ложь; // Если хотим редактировать
// Создадим колонки для таблицы на форме
Элементы.Добавить("ЭлементТаблицаДанныхНаименование", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхНаименование.ПутьКДанным = "ТаблицаДанных.Наименование";
Элементы.ЭлементТаблицаДанныхНаименование.Заголовок = "Наименование";
Элементы.Добавить("ЭлементТаблицаДанныхКоличество", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхКоличество.ПутьКДанным = "ТаблицаДанных.Количество";
Элементы.ЭлементТаблицаДанныхКоличество.Заголовок = "Количество";
Элементы.Добавить("ЭлементТаблицаДанныхЦена", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхЦена.ПутьКДанным = "ТаблицаДанных.Цена";
Элементы.ЭлементТаблицаДанныхЦена.Заголовок = "Цена";
Элементы.Добавить("ЭлементТаблицаДанныхСумма", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхСумма.ПутьКДанным = "ТаблицаДанных.Сумма";
Элементы.ЭлементТаблицаДанныхСумма.Заголовок = "Сумма";
Теперь, когда у нас есть базовая программно созданная таблица (кстати, создать код новой таблицы значений можно из существующего реквизита), давайте приступим к добавлению итогов.
Итоги по строкам, как правило, представляют собой агрегированные значения в пределах каждой строки (например, сумма по всем числовым колонкам строки). Наиболее простым и распространенным способом добавления таких итогов является создание дополнительной колонки в самой таблице. Эту колонку мы будем программно заполнять вычисленными значениями для каждой строки.
ТаблицуЗначений: Например, "ИтогПоСтроке".ПолеФормы: Для этой новой колонки, чтобы она отображалась на форме (иногда может также потребоваться программное перемещение колонки для изменения порядка).ТаблицыЗначений данными, для каждой строки мы будем вычислять необходимое итоговое значение и присваивать его в созданную колонку.Рассмотрим пример добавления колонки "Итог по строке", которая будет суммировать "Количество" и "Цену" для каждой строки (хотя в реальной жизни это скорее будет "Сумма" * "Количество").
// ... (продолжение предыдущего кода создания таблицы)
// Добавим колонку для итогов по строкам в ТаблицуЗначений
НоваяТаблицаЗначений.Колонки.Добавить("ИтогПоСтроке", Тип("Число"));
// После изменения реквизитов формы и создания элементов таблицы
// Создадим элемент колонки для ИтогаПоСтроке
Элементы.Добавить("ЭлементТаблицаДанныхИтогПоСтроке", Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
Элементы.ЭлементТаблицаДанныхИтогПоСтроке.ПутьКДанным = "ТаблицаДанных.ИтогПоСтроке";
Элементы.ЭлементТаблицаДанныхИтогПоСтроке.Заголовок = "Итог по строке";
// Пример заполнения данных и расчета итогов по строкам
Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
// Предположим, что данные уже заполнены, или мы их генерируем
СтрокаТЗ.Количество = СлучайноеЧисло(1, 10);
СтрокаТЗ.Цена = СлучайноеЧисло(100, 1000);
СтрокаТЗ.Сумма = СтрокаТЗ.Количество * СтрокаТЗ.Цена;
// Рассчитываем итог по строке (например, сумма Количество и Цена)
// В реальной задаче это будет логика исходя из ваших потребностей
СтрокаТЗ.ИтогПоСтроке = СтрокаТЗ.Количество + СтрокаТЗ.Цена;
КонецЦикла;
Таким образом, итоги по строкам становятся частью самих данных таблицы, и их обновление происходит вместе с обновлением данных строки.
Итоги по колонкам обычно отображаются в подвале таблицы (нижняя часть). Для программно созданных таблиц на управляемых формах существует два основных метода реализации таких итогов.
Этот метод является наиболее простым для реализации, но требует ручного расчета и присвоения значений каждому полю подвала. Мы будем использовать свойство ТекстПодвала элемента формы типа ПолеФормы, соответствующего нашей колонке.
ТаблицаФормы необходимо установить свойство Подвал = Истина.ТаблицыЗначений. Для ТаблицыЗначений удобно использовать метод Итог().ТекстПодвала: Полученное итоговое значение присваиваем свойству ТекстПодвала соответствующего элемента ПолеФормы.Проанализируем шаги подробнее на примере:
// ... (после создания элементов таблицы и заполнения ТаблицаДанных)
// 1. Включим отображение подвала для таблицы
Элементы.ЭлементТаблицаДанных.Подвал = Истина;
// 2. Рассчитаем итоги по колонкам (например, для Количество, Цена, Сумма)
// Для ТаблицыЗначений можно использовать метод Итог()
// Для примера, возьмем итоги для колонок "Количество" и "Сумма"
// Итог по колонке "Количество"
ИтогКоличество = ТаблицаДанных.Итог("Количество");
// Итог по колонке "Сумма"
ИтогСумма = ТаблицаДанных.Итог("Сумма");
// 3. Присвоим значения свойству ТекстПодвала
// Для колонки "Количество"
Элементы.ЭлементТаблицаДанныхКоличество.ТекстПодвала = Строка(ИтогКоличество);
// Для колонки "Сумма"
Элементы.ЭлементТаблицаДанныхСумма.ТекстПодвала = Строка(ИтогСумма);
// Очистим подвал для других колонок, если они не числовые или не требуют итога
// Например, для колонки "Наименование" подвал может быть пустым или содержать текст "Итого:"
Элементы.ЭлементТаблицаДанныхНаименование.ТекстПодвала = "Итого:";
// Для колонки "Цена" в этом примере мы не считали итог
Элементы.ЭлементТаблицаДанныхЦена.ТекстПодвала = "";
// Для колонки "ИтогПоСтроке" также можно вывести общий итог
ИтогОбщийПоСтрокам = ТаблицаДанных.Итог("ИтогПоСтроке");
Элементы.ЭлементТаблицаДанныхИтогПоСтроке.ТекстПодвала = Строка(ИтогОбщийПоСтрокам);
Преимущества: Простота реализации. Не требуется создавать дополнительные реквизиты формы.
Недостатки: Отсутствие автоматического пересчета. Всегда нужно вручную пересчитывать и присваивать значения при любом изменении данных в таблице.
Этот метод более гибок и позволяет имитировать поведение табличных частей объектов метаданных, где итоги "привязываются" к данным. Для каждой числовой колонки, по которой требуется итог, мы должны программно создать отдельный реквизит формы, который будет хранить это итоговое значение. Затем свойству ПутьКДаннымПодвала элемента ПолеФормы присваивается путь к этому новому реквизиту.
Подвал = Истина.Число). Имена реквизитов должны быть уникальными.ПутьКДаннымПодвала: Свойству ПутьКДаннымПодвала элемента ПолеФормы, соответствующего колонке, присваиваем путь к созданному нами реквизиту формы.Рассмотрим этот метод по шагам. Этот подход был подробно рассмотрен в сообщении 12 форума:
// ... (продолжение кода, например, в ПриСозданииНаСервере)
// Массив для хранения новых реквизитов формы, которые будут использоваться для подвала
НовыеРеквизитыПодвала = Новый Массив();
// 1. Включим отображение подвала для таблицы
Элементы.ЭлементТаблицаДанных.Подвал = Истина;
// Пройдем по колонкам нашей ТаблицыЗначений, чтобы определить, по каким нужны итоги
Для Каждого КолонкаТЗ Из ТаблицаДанных.Колонки Цикл
// Создаем элементы ПолеФормы, если они еще не созданы
// Или используем уже созданные элементы (как в нашем примере выше)
ИмяЭлементаКолонки = "ЭлементТаблицаДанных" + КолонкаТЗ.Имя;
Если Элементы.Найти(ИмяЭлементаКолонки) = Неопределено Тогда
// Если элементы еще не созданы, создадим их здесь
НовыйЭлементПоля = Элементы.Добавить(ИмяЭлементаКолонки, Тип("ПолеФормы"), Элементы.ЭлементТаблицаДанных);
НовыйЭлементПоля.ПутьКДанным = "ТаблицаДанных." + КолонкаТЗ.Имя;
НовыйЭлементПоля.Заголовок = КолонкаТЗ.Заголовок;
КонецЕсли;
// Получим ссылку на элемент ПолеФормы
ЭлементПоля = Элементы[ИмяЭлементаКолонки];
// 2. Создадим программные реквизиты формы для каждого итога
// Подвал только для колонок с числовым типом
Если КолонкаТЗ.ТипЗначения.СодержитТип(Тип("Число")) Тогда
ИмяРеквизитаИтога = "Итог_" + КолонкаТЗ.Имя;
Если ЭтаФорма.Реквизиты.Найти(ИмяРеквизитаИтога) = Неопределено Тогда
НовыйРеквизитИтога = Новый РеквизитФормы(ИмяРеквизитаИтога, КолонкаТЗ.ТипЗначения);
НовыеРеквизитыПодвала.Добавить(НовыйРеквизитИтога);
КонецЕсли;
// 3. Установим ПутьКДаннымПодвала
ЭлементПоля.ПутьКДаннымПодвала = ИмяРеквизитаИтога;
Иначе
// Для нечисловых колонок можно установить статический текст в подвал
Если КолонкаТЗ.Имя = "Наименование" Тогда
ЭлементПоля.ТекстПодвала = "Итого:";
Иначе
ЭлементПоля.ТекстПодвала = ""; // Очищаем подвал для других нечисловых
КонецЕсли;
КонецЕсли;
КонецЦикла;
// После того, как все нужные реквизиты для подвала добавлены в массив,
// необходимо изменить реквизиты формы
Если НовыеРеквизитыПодвала.Количество() > 0 Тогда
ЭтаФорма.ИзменитьРеквизиты(НовыеРеквизитыПодвала);
КонецЕсли;
// 4. Рассчитаем и заполним значения реквизитов подвала
// (это можно сделать после заполнения ТаблицаДанных, например, в отдельной процедуре)
// Пример расчета
Процедура РассчитатьИтогиПодвала()
Для Каждого КолонкаТЗ Из ТаблицаДанных.Колонки Цикл
Если КолонкаТЗ.ТипЗначения.СодержитТип(Тип("Число")) Тогда
ИмяРеквизитаИтога = "Итог_" + КолонкаТЗ.Имя;
Если ЭтаФорма.Реквизиты.Найти(ИмяРеквизитаИтога) <> Неопределено Тогда
// Рассчитываем итог по колонке ТаблицыЗначений
РассчитанныйИтог = ТаблицаДанных.Итог(КолонкаТЗ.Имя);
// Присваиваем значение программному реквизиту формы
ЭтаФорма[ИмяРеквизитаИтога] = РассчитанныйИтог;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Вызываем после заполнения ТаблицаДанных
// РассчитатьИтогиПодвала();
Преимущества: Большая гибкость, имитация поведения стандартных табличных частей. После связывания ПутьКДаннымПодвала с реквизитом, отображение значения в подвале будет автоматически обновляться при изменении значения этого реквизита.
Недостатки: Более сложная реализация, необходимость динамического создания и управления реквизитами формы.
Как уже упоминалось, для программно созданных таблиц автоматический пересчет итогов "из коробки" не происходит. Если мы хотим, чтобы итоги обновлялись в реальном времени при изменении данных пользователем, нам необходимо реализовать этот механизм вручную.
Мы реализуем пересчет через обработчики событий элементов формы (для быстрой отладки алгоритмов пригодится встраиваемая консоль кода — для этого есть встраиваемая консоль кода для разработчика 1С):
ПриИзменении: Для каждого элемента ПолеФормы, соответствующего числовой колонке, мы должны назначить обработчик события ПриИзменении.Рассмотрим, как это сделать:
// ... (после создания элементов таблицы)
// Пример назначения обработчика для колонок "Количество" и "Цена", которые влияют на итоги
// Это делается для каждого элемента ПолеФормы, который может влиять на итоги
Элементы.ЭлементТаблицаДанныхКоличество.УстановитьДействие("ПриИзменении", "ЭлементТаблицаДанных_ПриИзменении");
Элементы.ЭлементТаблицаДанныхЦена.УстановитьДействие("ПриИзменении", "ЭлементТаблицаДанных_ПриИзменении");
Элементы.ЭлементТаблицаДанныхСумма.УстановитьДействие("ПриИзменении", "ЭлементТаблицаДанных_ПриИзменении"); // Если Сумма тоже может быть изменена вручную
// Создадим процедуру-обработчик на клиенте
// В модуле формы
Процедура ЭлементТаблицаДанных_ПриИзменении(Элемент)
// Получаем текущую строку таблицы
ТекущиеДанныеСтроки = Элементы.ЭлементТаблицаДанных.ТекущиеДанные;
Если ТекущиеДанныеСтроки = Неопределено Тогда
Возврат;
КонецЕсли;
// Определяем имя колонки, которая была изменена
ИмяКолонки = СтрЗаменить(Элемент.ПутьКДанным, "ТаблицаДанных.", "");
// Пересчитываем итог по строке для текущей строки
// Если изменено Количество или Цена, пересчитываем Сумму и ИтогПоСтроке
Если ИмяКолонки = "Количество" Или ИмяКолонки = "Цена" Тогда
ТекущиеДанныеСтроки.Сумма = ТекущиеДанныеСтроки.Количество * ТекущиеДанныеСтроки.Цена;
ТекущиеДанныеСтроки.ИтогПоСтроке = ТекущиеДанныеСтроки.Количество + ТекущиеДанныеСтроки.Цена; // Пример
ИначеЕсли ИмяКолонки = "Сумма" Тогда
// Если изменили Сумму, то, возможно, нужно пересчитать ИтогПоСтроке
ТекущиеДанныеСтроки.ИтогПоСтроке = ТекущиеДанныеСтроки.Количество + ТекущиеДанныеСтроки.Цена; // Пример
КонецЕсли;
// После пересчета итогов по строке, нужно пересчитать итоги по колонкам
// Это можно сделать в отдельной процедуре, вызываемой здесь
РассчитатьИтогиПодвалаКлиент();
КонецПроцедуры
// Процедура для расчета итогов подвала, вызываемая на клиенте
// Она должна быть Экспортной, если вызывается из обработчика события
Процедура РассчитатьИтогиПодвалаКлиент() Экспорт
// Если используем Метод 1 (ТекстПодвала)
// ЭтаФорма.Элементы.ЭлементТаблицаДанныхКоличество.ТекстПодвала = Строка(ТаблицаДанных.Итог("Количество"));
// ЭтаФорма.Элементы.ЭлементТаблицаДанныхСумма.ТекстПодвала = Строка(ТаблицаДанных.Итог("Сумма"));
// Если используем Метод 2 (ПутьКДаннымПодвала)
Для Каждого КолонкаТЗ Из ТаблицаДанных.Колонки Цикл
Если КолонкаТЗ.ТипЗначения.СодержитТип(Тип("Число")) Тогда
ИмяРеквизитаИтога = "Итог_" + КолонкаТЗ.Имя;
Если ЭтаФорма.Реквизиты.Найти(ИмяРеквизитаИтога) <> Неопределено Тогда
// Рассчитываем итог по колонке ТаблицыЗначений
РассчитанныйИтог = ТаблицаДанных.Итог(КолонкаТЗ.Имя);
// Присваиваем значение программному реквизиту формы
ЭтаФорма[ИмяРеквизитаИтога] = РассчитанныйИтог;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важные моменты:
РассчитатьИтогиПодвалаКлиент) можно сделать серверной с вызовом через НаСервере. Однако, для простых итогов ТаблицыЗначений, ее расчет может быть выполнен и на клиенте.Колонка.ТипЗначения.СодержитТип(Тип("Число"))).ТекстПодвала или ПутьКДаннымПодвала) для всех итогов по колонкам в одной таблице, чтобы избежать путаницы и упростить поддержку кода. Метод с ПутьКДаннымПодвала предпочтительнее для динамического обновления.Выбрав подходящий метод и тщательно реализовав логику пересчета, вы сможете эффективно добавлять и управлять итогами в программно созданных таблицах на управляемых формах 1С:Предприятия, предоставляя пользователям полноценный и интерактивный интерфейс.