В процессе разработки и кастомизации конфигураций 1С (например, «Управление торговлей», «ERP Управление предприятием» или «Комплексная автоматизация») программисты часто сталкиваются с необходимостью отобразить в табличной части документа или справочника дополнительную информацию. Это может быть текущий остаток товара на складе, артикул номенклатуры, информация о скидках или любые другие расчетные данные, которые не требуется постоянно хранить в базе данных. Для контроля корректности расчетных данных после доработки учёта можно выполнить автоматическую сверку проводок между базами с помощью COM-соединения.
Для реализации этой задачи разработчики добавляют новые реквизиты непосредственно на саму форму (или внутрь коллекции формы). Однако у новичков часто возникает вопрос: как связать эти новые колонки с кодом программы, как платформа понимает, куда именно выводить данные, и почему при записи документа выведенные значения внезапно исчезают? В этой статье мы подробно разберем механизмы работы с динамическими колонками табличных частей на управляемых формах 1С, изучим жизненный цикл данных формы и рассмотрим лучшие практики программирования. Для упрощения этой задачи можно использовать инструмент для программного создания колонок и сохранения их значений в документе.
В управляемом приложении 1С реализовано четкое разделение между данными формы (ее структурой в оперативной памяти) и элементами формы (визуальным интерфейсом, который видит пользователь). Когда вы создаете реквизит внутри табличной части формы (например, добавляете реквизит Остаток в коллекцию Объект.Товары) и перетаскиваете его мышью в визуальный редактор формы, платформа автоматически выполняет следующие действия:
DataPath). В нашем примере путь к данным будет выглядеть как Объект.Товары.Остаток.Благодаря этому свойству платформа автоматически сопоставляет каждую ячейку в визуальной таблице на экране с конкретной строкой и колонкой в структуре данных в памяти. Вам не нужно вручную позиционировать вывод на экране или указывать координаты — достаточно просто записать значение в соответствующий реквизит строки в коде, и платформа мгновенно отобразит его на экране.
Рассмотрим наиболее гибкий и распространенный способ — программное присвоение значений реквизитам строк. Для обращения к добавленной колонке используется простая конструкция: СтрокаТаблицы.ИмяРеквизита = Значение. Однако контекст выполнения кода и способ получения этой «строки» существенно различаются в зависимости от того, когда именно мы хотим заполнить данные.
Если нам нужно рассчитать и вывести значения для всех строк табличной части сразу, это действие необходимо выполнять на сервере. При этом крайне важно соблюдать правила оптимизации запросов и избегать выполнения запросов к базе данных в цикле.
Проанализируем плохой пример кода, который часто пишут начинающие разработчики:
// НЕПРАВИЛЬНО: Выполнение запроса в цикле по каждой строке табличной части
&НаСервере
Процедура ЗаполнитьДопКолонкуНеоптимально()
Для Каждого Строка Из Объект.Товары Цикл
// При каждом проходе цикла выполняется обращение к базе данных, что сильно замедляет работу формы
Строка.Остаток = ПолучитьОстатокТовараНаСкладе(Строка.Номенклатура);
КонецЦикла;
КонецПроцедуры
Если в табличной части будет 100 или 200 строк, система выполнит 100–200 отдельных запросов к СУБД, что вызовет серьезные задержки интерфейса. Разберем по шагам правильный, оптимизированный алгоритм заполнения колонок:
Соответствие), где ключом будет номенклатура, а значением — остаток.Давайте посмотрим на пример оптимизированной реализации этого алгоритма:
&НаСервере
Процедура ЗаполнитьДополнительныеКолонкиНаСервере()
// Шаг 1. Собираем уникальные ссылки на товары в массив
МассивТоваров = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
Если ЗначениеЗаполнено(Строка.Номенклатура) И МассивТоваров.Найти(Строка.Номенклатура) = Неопределено Тогда
МассивТоваров.Добавить(Строка.Номенклатура);
EndIf;
КонецЦикла;
Если МассивТоваров.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Шаг 2. Получаем данные одним запросом к базе данных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивТоваров)) КАК ТоварыНаСкладахОстатки";
Запрос.УстановитьПараметр("МассивТоваров", МассивТоваров);
// Шаг 3. Заполняем соответствие для быстрого поиска в памяти
СоответствиеОстатков = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СоответствиеОстатков.Вставить(Выборка.Номенклатура, Выборка.Остаток);
КонецЦикла;
// Шаг 4. Обходим строки и записываем значения в добавленный реквизит формы
Для Каждого Строка Из Объект.Товары Цикл
НайденныйОстаток = СоответствиеОстатков.Получить(Строка.Номенклатура);
Строка.Остаток = ?(НайденныйОстаток = Неопределено, 0, НайденныйОстаток);
КонецЦикла;
КонецПроцедуры
Бывают ситуации, когда пользователь интерактивно работает с формой: например, он добавляет новую строку в табличную часть или меняет номенклатуру в существующей строке. В этот момент значение в добавленной колонке должно мгновенно пересчитаться.
Для этого мы используем событие ПриИзменении для соответствующего поля ввода. Нам нужно получить текущую строку, на которой сейчас стоит курсор пользователя. Выясним, как это сделать правильно.
В управляемом интерфейсе у элементов формы типа *Таблица* есть свойство ТекущиеДанные. Оно возвращает объект типа ДанныеФормыКоллекцияЭлемент, который представляет собой ту самую строку, с которой взаимодействует пользователь. Обратите внимание: не путайте свойство ТекущиеДанные со свойством ТекущаяСтрока, так как последнее содержит лишь числовой или уникальный идентификатор строки, а не саму строку с данными.
Посмотрим на пример клиент-серверного взаимодействия при изменении номенклатуры в строке:
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
// Получаем строку табличной части, в которой изменили номенклатуру
СтрокаТаблицы = Элементы.Товары.ТекущиеДанные;
Если СтрокаТаблицы <> Неопределено Тогда
// Передаем номенклатуру на сервер для расчета остатка
ОстатокНаСкладе = ПолучитьОстатокТовараНаСервере(СтрокаТаблицы.Номенклатура);
// Записываем полученное значение в добавленную колонку
СтрокаТаблицы.Остаток = ОстатокНаСкладе;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьОстатокТовараНаСервере(Знач НоменклатураСсылка)
Если Не ЗначениеЗаполнено(НоменклатураСсылка) Тогда
Возврат 0;
КонецЕсли;
// Выполняем точечный запрос для получения остатка конкретного товара
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки";
Запрос.УстановитьПараметр("Номенклатура", НоменклатураСсылка);
УстановитьПривилегированныйРежим(Истина);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Возврат Выборка.Остаток;
КонецЕсли;
Возврат 0;
КонецФункции
Очень частая проблема, с которой сталкиваются программисты 1С: вы написали код заполнения колонок при создании формы, все прекрасно отображается, но стоит пользователю нажать на кнопку «Записать» или «Провести», как все значения в добавленной колонке мгновенно обнуляются. Выясним причину такого поведения платформы.
Если добавленный вами реквизит является реквизитом формы, а не реквизитом самого объекта метаданных (документа в конфигураторе), его значения хранятся исключительно в оперативной памяти веб-клиента или тонкого клиента. База данных СУБД ничего не знает про вашу колонку.
Когда пользователь нажимает «Записать», платформа выполняет транзакцию записи на сервере, после чего полностью перечитывает объект из базы данных, чтобы обновить форму актуальными данными из СУБД. В этот момент табличная часть формы перезаполняется «чистыми» данными из базы, а все не связанные с метаданными реквизиты формы сбрасываются в значения по умолчанию.
Чтобы решить эту проблему, вызов процедуры заполнения дополнительных колонок необходимо привязать к стандартным серверным обработчикам жизненного цикла формы:
ПриСозданииНаСервере — обеспечивает первичное заполнение колонок при открытии формы нового или существующего документа.ПриЧтенииНаСервере — вызывается при чтении данных объекта из базы на сервер (например, при перечитывании или обновлении формы).ПослеЗаписиНаСервере — отрабатывает сразу после записи объекта в базу, позволяя заново рассчитать и вывести значения в форму до того, как она отобразится пользователю.Проанализируем пример правильной привязки обработчиков в модуле управляемой формы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Заполняем колонки при открытии формы
ЗаполнилиДополнительныеКолонкиНаСервере();
КонецПроцедуры
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
// Заполняем колонки при перечитывании объекта из БД
ЗаполнилиДополнительныеКолонкиНаСервере();
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ПараметрыЗаписи)
// Заполняем колонки после сохранения документа, чтобы предотвратить их обнуление
ЗаполнилиДополнительныеКолонкиНаСервере();
КонецПроцедуры
Иногда программисту требуется вывести в добавленную колонку не сложные расчетные данные из регистров, а простые справочные реквизиты связанных объектов (например, вывести артикул или код производителя для выбранной номенклатуры в строке). Для таких сценариев отлично подходит механизм Условного оформления управляемой формы. Если же для сопоставления объектов в разных базах требуется поиск не по ссылке, а по артикулу или коду, можно применить инструмент переноса данных между похожими конфигурациями 1С с настройкой произвольных полей поиска.
Преимущество этого метода заключается в том, что он работает на уровне платформы без необходимости написания тяжелых серверных обходов коллекций, а отображаемые данные автоматически сохраняются на форме и не сбрасываются при перезаписи документа.
В условном оформлении формы есть специальное свойство оформления — Текст. Мы можем задать правило: если в строке заполнена номенклатура, то в добавленную колонку ОстатокТекст (которая может быть просто строковым реквизитом) принудительно выводить текст, соответствующий реквизиту номенклатуры.
Рассмотрим, как настроить такое оформление программно в обработчике ПриСозданииНаСервере:
&НаСервере
Процедура НастроитьУсловноеОформлениеДляАртикула()
// Создаем новый элемент условного оформления
ЭлементОформления = УсловноеОформление.Элементы.Добавить();
// 1. Определяем оформляемое поле (нашу добавленную колонку)
ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("ТоварыДопКолонкаАртикул");
// 2. Задаем условие: правило применяется, когда Номенклатура в строке заполнена
ЭлементОтбора = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Объект.Товары.Номенклатура");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Заполнено;
// 3. Устанавливаем значение свойства "Текст" через связь с реквизитом Номенклатуры
// В платформе 1С мы можем связать текст оформления напрямую с путем к данным смежного поля
ЭлементОформления.Установка.УстановитьЗначениеПараметра("Текст", Новый ПолеКомпоновкиДанных("Объект.Товары.Номенклатура.Артикул"));
КонецПроцедуры
Этот код заставляет платформу самостоятельно вытягивать артикул из связанных данных номенклатуры и отображать его в указанной колонке. При этом никаких запросов вручную писать не требуется, а производительность отрисовки формы остается на максимально высоком уровне.
Давайте сопоставим оба подхода, чтобы четко понимать, какой инструмент выбрать в конкретной ситуации:
Используя предложенные методики и соблюдая правила контекстной работы платформы 1С, вы сможете эффективно решать задачи по кастомизации интерфейса ваших конфигураций без ущерба для производительности системы. При масштабном внедрении подобных изменений на нескольких объектах пригодится инструмент, позволяющий обновлять и тестировать информационные базы 1С в автоматическом режиме.