При разработке интерфейсов на управляемых формах в «1С:Предприятие 8» программисты часто сталкиваются с задачей отображения итоговых данных (например, общей суммы, общего веса, объема или количества) внизу таблицы. Если мы работаем со стандартной Табличной частью (ТЧ) документа или справочника, платформа берет всю рутину на себя: достаточно сделать несколько кликов в конфигураторе, настроить свойство ПутьКДаннымПодвала, и система сама начнет автоматически суммировать показатели.
Однако, как только мы начинаем использовать динамическую Таблицу значений (ТЗ), созданную как произвольный реквизит формы, стандартный механизм автоматического подсчета итогов перестает работать. Попытка напрямую привязать колонку таблицы значений к подвалу приводит к тому, что в подвале отображается значение только текущей выделенной или самой первой строки.
В этой статье мы подробно разберем, почему возникает эта сложность, проанализируем правильные методы расчета и вывода итогов, а также выясним, почему популярный среди новичков метод «подсчета в цикле» является грубейшей ошибкой проектирования.
Давайте разберемся, почему платформа ведет себя по-разному с Табличной частью и Таблицей значений:
Объект.Товары.ИтогСумма.ТаблицаЗначений). Платформа не отслеживает динамическое изменение каждой ячейки этой таблицы с целью автоматического пересчета агрегатных функций для подвала. Поэтому свойство ПутьКДаннымПодвала для колонки ТЗ не может самостоятельно вычислить сумму. Если указать туда путь к самой колонке, система просто вернет значение активной строки таблицы.Для решения этой задачи нам придется взять расчет и вывод итогов под свой контроль. Рассмотрим два наиболее эффективных и правильных способа реализации.
Самый простой, быстрый и элегантный способ вывести итог в подвал таблицы значений — использовать свойство элемента формы ТекстПодвала. Этот способ хорош тем, что нам не требуется создавать новые реквизиты на форме. Мы будем напрямую записывать рассчитанную сумму в виде текста в подвал нужной колонки.
Разберем реализацию этого способа по шагам:
Шаг 1. Настройка свойств элементов формы в конфигураторе
Для того чтобы подвал вообще отобразился на экране пользователя, его необходимо включить:
Подвал) и установите его в значение Истина.Количество или Вес).ПутьКДаннымПодвала при этом оставляйте абсолютно пустым.Шаг 2. Написание программного кода расчета
Теперь нам нужно написать процедуру, которая будет вычислять сумму по колонке таблицы значений и выводить ее в подвал. Создадим универсальную процедуру на клиенте:
&НаКлиенте
Процедура ОбновитьИтогиТаблицы()
// Рассчитываем сумму по колонке "Количество"
// Обратите внимание: расчет происходит на сервере или клиенте в зависимости от доступности данных
СуммаКоличества = ТаблицаТоваров.Итог("Количество");
// Форматируем число для красивого отображения в интерфейсе
СтрокаФормата = "ЧДЦ=3; ЧРД=,; ЧГ=3"; // 3 знака после запятой, разделитель групп - пробел
ТекстИтога = Формат(СуммаКоличества, СтрокаФормата);
// Записываем результат напрямую в свойство элемента формы
Элементы.ТаблицаТоваровКоличество.ТекстПодвала = ТекстИтога;
КонецПроцедуры
Метод Итог() возвращает чистое число, которое мы преобразуем в строку с помощью функции Формат(), чтобы пользователю было удобно читать большие числа (например, вместо "1500450.5" он увидит "1 500 450,500").
Этот способ является классическим и наиболее часто приводится в методических материалах. Мы создаем специальный контекстный реквизит формы, связываем его с подвалом колонки, а затем программно меняем значение этого реквизита.
Проанализируем этот подход по шагам:
ИтогПоВесу. Задайте ему тип данных Число с нужной точностью (например, длина 15, точность 3).ТаблицаТоваровВес).ИтогПоВесу. Не забудьте также включить флажок Подвал у самой таблицы и у колонки.
&НаСервереБезКонтекста или &НаКлиенте
// Пример присвоения значения реквизиту формы
ИтогПоВесу = ТаблицаТоваров.Итог("Вес");
Этот метод удобен тем, что если вам потребуется использовать значение итога где-то еще в коде (например, для проверок при проведении или отправки на сервер), вам не нужно лезть в свойства элементов формы — значение всегда доступно в переменной ИтогПоВесу.
В сообщениях на форумах (в частности, в сообщении №12 обсуждаемой темы) начинающие разработчики часто пишут: «я добавил реквизит и посчитал его в цикле...». Опытные программисты реагируют на такие заявления крайне болезненно, и вот почему:
Обход таблицы значений в цикле Для Каждого Строка Из ТЗ Цикл ... КонецЦикла для получения суммы — это грубая оптимизационная ошибка. Если ваша таблица содержит сотни или тысячи строк (например, крупный заказ покупателя или накладная поступления), перебор строк на встроенном языке 1С будет выполняться неоправданно долго, нагружая процессор и заставляя интерфейс «подвисать».
Платформа 1С:Предприятие имеет встроенный высокооптимизированный метод ТаблицаЗначений.Итог("ИмяКолонки"). Этот метод реализован на уровне платформенного C++ кода. Он работает в десятки и сотни раз быстрее, чем аналогичный цикл, написанный разработчиком вручную. Считайте в циклах только тогда, когда вам требуется сложная условная фильтрация строк при подсчете, во всех остальных случаях — только .Итог()!
Чтобы итоги в подвале всегда оставались актуальными и не вводили пользователя в заблуждение, процедуру пересчета необходимо вызывать в строго определенных местах жизненного цикла формы.
Рассмотрим эти события:
1. При заполнении или загрузке таблицы значений
Когда вы программно заполняете таблицу значений на сервере (например, при выборе заказа покупателя или нажатии кнопки «Заполнить»), сразу после заполнения вызовите расчет итогов:
&НаСервере
Процедура ЗаполнитьТаблицуНаСервере(СсылкаНаЗаказ)
// Запрос к базе данных и заполнение ТЗ
ТаблицаТоваров.Загрузить(ПолучитьТабличнуюЧастьЗаказа(СсылкаНаЗаказ));
// Рассчитываем итоги после заполнения
ОбновитьИтогиТаблицы();
КонецПроцедуры
2. При ручном редактировании ячеек пользователем
Если колонка, по которой считается итог (например, Количество), доступна пользователю для ручного изменения непосредственно в таблице на форме, обязательно нужно реагировать на эти изменения.
Для этого выберите поле ввода внутри колонки таблицы, перейдите в его свойства, найдите событие ПриИзменении и создайте обработчик на клиенте:
&НаКлиенте
Процедура ТаблицаТоваровКоличествоПриИзменении(Элемент)
// Пользователь изменил число в строке - мгновенно пересчитываем общий итог в подвале
ОбновитьИтогиТаблицы();
КонецПроцедуры
3. При добавлении или удалении строк таблицы
Итог должен изменяться, если пользователь добавляет новую строку или удаляет существующую. Для этого необходимо назначить обработчики событий для самой Таблицы (как элемента формы):
ПослеУдаления — вызывается после того, как строка была стерта из таблицы.ПриОкончанииРедактирования — вызывается, когда пользователь закончил добавлять новую строку или завершил редактирование существующей.
&НаКлиенте
Процедура ТаблицаТоваровПослеУдаления(Элемент, ДанныеВыбора)
ОбновитьИтогиТаблицы();
КонецПроцедуры
&НаКлиенте
Процедура ТаблицаТоваровПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
Если Не ОтменаРедактирования Тогда
ОбновитьИтогиТаблицы();
КонецЕсли;
КонецПроцедуры
Рассмотрим продвинутую ситуацию. Допустим, на вашей форме есть таблица значений на 100 строк. Пользователь воспользовался стандартной строкой поиска 1С или установил отбор по какому-то значению (например, отобрал только определенную номенклатурную группу), и на экране осталось всего 5 строк.
Если в этот момент вы вызовете стандартный метод ТаблицаТоваров.Итог("Количество"), он вернет сумму по всем 100 строкам, включая скрытые отбором строки. Однако пользователю визуально важно видеть сумму только тех строк, которые он сейчас наблюдает на экране!
В этом сценарии нам действительно придется отказаться от метода Итог() и обойти строки таблицы, но делать это нужно правильно — с использованием платформенного метода элемента формы ПроверитьСтроку(). Данный метод проверяет, удовлетворяет ли строка текущим установленным пользователем отборам.
Проанализируем код такой процедуры:
&НаКлиенте
Процедура ОбновитьИтогиСУчетомФильтров()
ВсегоКоличество = 0;
// Обходим коллекцию строк таблицы значений
Для Каждого СтрокаТЗ Из ТаблицаТоваров Цикл
// Получаем уникальный идентификатор строки для связи с элементом формы
ИдентификаторСтроки = СтрокаТЗ.ПолучитьИдентификатор();
// Проверяем, отображается ли строка на экране (проходит ли по критериям поиска/отбора)
Если Элементы.ТаблицаТоваров.ПроверитьСтроку(ИдентификаторСтроки) Тогда
ВсегоКоличество = ВсегоКоличество + СтрокаТЗ.Количество;
КонецЕсли;
КонецЦикла;
// Выводим результат в подвал
Элементы.ТаблицаТоваровКоличество.ТекстПодвала = Формат(ВсегоКоличество, "ЧДЦ=3");
КонецПроцедуры
Этот подход гарантирует, что при наложении любых интерактивных фильтров подвал таблицы всегда будет показывать абсолютно корректную и актуальную информацию, соответствующую тому, что видит пользователь на своем мониторе.
Для вывода итогов по произвольной Таблице Значений на управляемой форме 1С запомните три главных правила:
Подвал как у самой таблицы, так и у необходимых колонок.ТекстПодвала конкретной колонки элемента формы — это избавит вас от захламления формы лишними реквизитами..Итог(), избегая ручных циклов, за исключением случаев, когда требуется рассчитать итоги строго по видимым (отфильтрованным) строкам.