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

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

При разработке конфигураций на платформе 1С:Предприятие 8.3 (Управляемые формы) перед программистом часто возникает задача автоматического заполнения или пересчета колонок в табличной части документа или справочника. Типичный пример такой задачи — автоматическое определение номера учебного курса на основе выбранного семестра при нажатии кнопки на форме.

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

Анализ исходной проблемы и типичных ошибок

Начинающие разработчики часто сталкиваются с непониманием разницы между работой с одной текущей (активной) строкой формы и обработкой всей коллекции строк. Разберем основные архитектурные ошибки, которые часто встречаются в подобных алгоритмах:

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

Ниже мы рассмотрим правильные и оптимизированные варианты решения этой задачи.

Решение 1. Классический обход в цикле с условиями

Проанализируем базовый вариант решения задачи. Мы реализуем команду на клиенте, которая вызывает серверную процедуру. На сервере мы выполним полный обход табличной части Дисциплины, которая принадлежит объекту формы, и последовательно заполним реквизит Курс на основании реквизита Семестр.

Посмотрим на пример правильной программной реализации:


&НаСервере
Процедура РасчетНаСервере()
    
    // Обходим все строки табличной части "Дисциплины" нашего документа
    Для Каждого СтрокаТЧ Из Объект.Дисциплины Цикл
        
        // Проверяем, заполнено ли значение семестра в текущей строке
        Если ЗначениеЗаполнено(СтрокаТЧ.Семестр) Тогда
            
            // Заполняем курс в зависимости от выбранного семестра
            Если СтрокаТЧ.Семестр = Перечисления.Семестры.ПервыйСеместр 
                Или СтрокаТЧ.Семестр = Перечисления.Семестры.ВторойСеместр Тогда
                
                СтрокаТЧ.Курс = Перечисления.Курсы.ПервыйКурс;
                
            ИначеЕсли СтрокаТЧ.Семестр = Перечисления.Семестры.ТретийСеместр 
                Или СтрокаТЧ.Семестр = Перечисления.Семестры.ЧетвертыйСеместр Тогда
                
                СтрокаТЧ.Курс = Перечисления.Курсы.ВторойКурс;
                
            ИначеЕсли СтрокаТЧ.Семестр = Перечисления.Семестры.ПятыйСеместр 
                Или СтрокаТЧ.Семестр = Перечисления.Семестры.ШестойСеместр Тогда
                
                СтрокаТЧ.Курс = Перечисления.Курсы.ТретийКурс;
                
            ИначеЕсли СтрокаТЧ.Семестр = Перечисления.Семестры.СедьмойСеместр 
                Или СтрокаТЧ.Семестр = Перечисления.Семестры.ВосьмойСеместр Тогда
                
                СтрокаТЧ.Курс = Перечисления.Курсы.ЧетвертыйКурс;
                
            ИначеЕсли СтрокаТЧ.Семестр = Перечисления.Семестры.ДевятыйСеместр 
                Или СтрокаТЧ.Семестр = Перечисления.Семестры.ДесятыйСеместр Тогда
                
                СтрокаТЧ.Курс = Перечисления.Курсы.ПятыйКурс;
                
            ИначеЕсли СтрокаТЧ.Семестр = Перечисления.Семестры.ОдиннадцатыйСеместр 
                Или СтрокаТЧ.Семестр = Перечисления.Семестры.ДвенадцатыйСеместр Тогда
                
                СтрокаТЧ.Курс = Перечисления.Курсы.ШестойКурс;
                
            ИначеЕсли СтрокаТЧ.Семестр = Перечисления.Семестры.ТринадцатыйСеместр 
                Или СтрокаТЧ.Семестр = Перечисления.Семестры.ЧетырнадцатыйСеместр Тогда
                
                СтрокаТЧ.Курс = Перечисления.Курсы.СедьмойКурс;
                
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

&НаКлиенте
Процедура Расчет(Команда)
    
    // Вызываем серверный контекст формы для изменения данных объекта
    РасчетНаСервере();
    
КонецПроцедуры

В данном примере мы избавились от оператора Возврат внутри цикла, благодаря чему платформа успешно обрабатывает абсолютно все строки нашей таблицы. Также обратите внимание, что мы работаем с контекстом формы &НаСервере, что позволяет напрямую изменять коллекцию Объект.Дисциплины.

Решение 2. Использование объекта "Соответствие" (оптимальный читаемый код)

Конструкция Если...ИначеЕсли на несколько десятков строк выглядит громоздко и трудна в поддержке (ее часто называют "индусским кодом"). Если в будущем добавятся новые семестры или курсы, нам придется дописывать новые логические ветки. Выясним, как сделать этот код более профессиональным.

Для оптимизации сопоставлений мы можем использовать встроенный объект Соответствие. Это позволит нам один раз наполнить карту соответствий "Семестр - Курс" и затем осуществлять мгновенный поиск в цикле без использования тяжелых ветвлений.

Разберем этот метод подробнее на практическом примере:


&НаСервере
Процедура РасчетНаСервереОптимизированный()
    
    // Инициализируем коллекцию соответствий
    КартаСоответствий = Новый Соответствие;
    
    // Заполняем карту: Ключ - Семестр, Значение - Курс
    КартаСоответствий.Вставить(Перечисления.Семестры.ПервыйСеместр,       Перечисления.Курсы.ПервыйКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ВторойСеместр,       Перечисления.Курсы.ПервыйКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ТретийСеместр,       Перечисления.Курсы.ВторойКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ЧетвертыйСеместр,     Перечисления.Курсы.ВторойКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ПятыйСеместр,         Перечисления.Курсы.ТретийКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ШестойСеместр,        Перечисления.Курсы.ТретийКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.СедьмойСеместр,       Перечисления.Курсы.ЧетвертыйКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ВосьмойСеместр,       Перечисления.Курсы.ЧетвертыйКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ДевятыйСеместр,       Перечисления.Курсы.ПятыйКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ДесятыйСеместр,       Перечисления.Курсы.ПятыйКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ОдиннадцатыйСеместр,  Перечисления.Курсы.ШестойКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ДвенадцатыйСеместр,   Перечисления.Курсы.ШестойКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ТринадцатыйСеместр,   Перечисления.Курсы.СедьмойКурс);
    КартаСоответствий.Вставить(Перечисления.Семестры.ЧетырнадцатыйСеместр, Перечисления.Курсы.СедьмойКурс);
    
    // Обходим строки таблицы и устанавливаем значения по карте
    Для Каждого СтрокаТЧ Из Объект.Дисциплины Цикл
        
        Если ЗначениеЗаполнено(СтрокаТЧ.Семестр) Тогда
            
            НайденныйКурс = КартаСоответствий.Получить(СтрокаТЧ.Семестр);
            Если НайденныйКурс <> Неопределено Тогда
                СтрокаТЧ.Курс = НайденныйКурс;
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Такой подход делает код гораздо чище, лаконичнее и повышает скорость его работы, так как поиск в объекте Соответствие по хэш-таблице выполняется практически мгновенно.

Решение 3. Математический расчет по индексам перечислений

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

Проанализируем индексы наших перечислений:

Мы видим четкую математическую зависимость: если взять индекс семестра, разделить его нацело на 2, то мы получим индекс соответствующего ему курса! Например:

Посмотрим, как изящно можно написать этот алгоритм в кодовой базе всего в несколько строк:


&НаСервере
Процедура РасчетНаСервереПоИндексам()
    
    Для Каждого СтрокаТЧ Из Объект.Дисциплины Цикл
        
        Если ЗначениеЗаполнено(СтрокаТЧ.Семестр) Тогда
            
            // Получаем индекс семестра из метаданных (от 0 до 13)
            ИндексСеместра = Перечисления.Семестры.Индекс(СтрокаТЧ.Семестр);
            
            // Вычисляем индекс курса (деление нацело)
            ИндексКурса = Цел(ИндексСеместра / 2);
            
            // Получаем ссылку на курс по его индексу и записываем в строку
            СтрокаТЧ.Курс = Перечисления.Курсы.Получить(ИндексКурса);
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Важное предупреждение: Данный метод будет корректно работать только в том случае, если разработчик гарантирует, что порядок следования элементов в конфигураторе внутри перечислений Семестры и Курсы никогда не будет произвольно нарушен. Любое добавление элемента "вне очереди" приведет к смещению индексов и неверному расчету.

Интерактивное изменение при вводе строки

Помимо возможности группового заполнения по кнопке "Расчет", пользователям всегда удобно, чтобы при ручном выборе семестра в строке табличной части курс подставлялся автоматически, "на лету".

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

Рассмотрим пример создания такой процедуры-обработчика события на форме:


&НаКлиенте
Процедура ДисциплиныСеместрПриИзменении(Элемент)
    
    // Получаем текущую редактируемую строку
    СтрокаТаблицы = Элементы.Дисциплины.ТекущиеДанные;
    
    Если СтрокаТаблицы <> Неопределено Тогда
        // Вызываем серверный метод для расчета курса конкретно для этой строки
        СтрокаТаблицы.Курс = ПолучитьКурсПоСеместру(СтрокаТаблицы.Семестр);
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьКурсПоСеместру(Знач Семестр)
    
    Если Не ЗначениеЗаполнено(Семестр) Тогда
        Возврат ПредопределенноеЗначение("Перечисление.Курсы.ПустаяСсылка");
    КонецЕсли;
    
    ИндексСеместра = Перечисления.Семестры.Индекс(Семестр);
    ИндексКурса = Цел(ИндексСеместра / 2);
    
    Возврат Перечисления.Курсы.Получить(ИндексКурса);
    
КонецФункции

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

← На главную