Нередко при разработке вспомогательного функционала возникает задача: открыть из формы документа внешнюю обработку, рассчитать или заполнить в ней данные, а затем перенести строки в табличную часть исходного документа. Начинающие разработчики часто сталкиваются с ситуацией, когда код обработки отрабатывает без ошибок, однако в открытом окне документа добавленные строки не появляются до тех пор, пока пользователь не закроет и не откроет форму заново.
Проанализируем причины такого поведения платформы 1С:Предприятие и разберем правильные способы организации взаимодействия между обработкой и документом.
Рассмотрим типовой ошибочный фрагмент кода, с которого обычно начинается подобная доработка:
ДокументЗаполнения = ТекДокумент.ПолучитьОбъект();
Для каждого СтрокаТЧ Из ТабЧасть Цикл
СтрокаДок = ДокументЗаполнения.Товары.Добавить();
СтрокаДок.Номенклатура = СтрокаТЧ.Номенклатура;
СтрокаДок.Количество = СтрокаТЧ.Вес;
СтрокаДок.Количество1 = СтрокаТЧ.Размер;
СтрокаДок.коэффициент1 = СтрокаТЧ.УдельныйВес;
КонецЦикла;
ДокументЗаполнения.Записать();
Разберем подробно, что происходит в памяти платформы при выполнении этих инструкций:
ПолучитьОбъект() по ссылке, представляют собой два абсолютно независимых экземпляра в оперативной памяти. Изменяя и записывая экземпляр ДокументЗаполнения, мы обновляем только таблицы базы данных на сервере, но открытая форма документа об этом ничего не знает.Записать(), то при последующем нажатии кнопки сохранения в форме документа старые данные из экранной формы просто затрут все изменения, внесенные обработкой.ПолучитьОбъект() вызывается у ссылки. Если пользователь создал новый документ и еще ни разу его не сохранил, ссылки в базе данных не существует, и попытка вызвать метод завершится аварийной ошибкой.Поэтому главное правило интерактивной разработки: никогда не записывайте документ напрямую из вызванной обработки, если его форма открыта перед пользователем. Данные необходимо возвращать непосредственно в открытый контекст формы.
Посмотрим на классический паттерн типовых конфигураций, по которому устроены формы подбора номенклатуры. Форма обработки открывается с указанием владельца, а по завершении работы возвращает результат через стандартное оповещение о выборе.
Разберем по шагам реализацию этого механизма.
В модуле формы документа при вызове обработки открываем ее и назначаем текущую форму владельцем:
ФормаОбработки = ПолучитьФорму("ВнешняяОбработка.ЗаполнениеТабличнойЧасти.Форма", , ЭтаФорма);
ФормаОбработки.Открыть();
В модуле формы обработки по кнопке переноса данных выгружаем строки во временную таблицу значений и передаем ее форме-владельцу через метод ОповеститьОВыборе():
ТаблицаРезультат = ТабЧасть.Выгрузить();
ОповеститьОВыборе(ТаблицаРезультат);
Закрыть();
В модуле формы документа перехватываем переданные данные в стандартном событии ОбработкаВыбора:
Процедура ОбработкаВыбора(ЗначениеВыбора, Источник)
Если ТипЗнч(ЗначениеВыбора) = Тип("ТаблицаЗначений") Тогда
Для Каждого Стр Из ЗначениеВыбора Цикл
НоваяСтрока = ЭтотОбъект.Товары.Добавить();
НоваяСтрока.Номенклатура = Стр.Номенклатура;
НоваяСтрока.Количество = Стр.Вес;
НоваяСтрока.Количество1 = Стр.Размер;
НоваяСтрока.коэффициент1 = Стр.УдельныйВес;
КонецЦикла;
Модифицированность = Истина;
КонецЕсли;
КонецПроцедуры
Этот способ универсален, гарантирует сохранность данных и корректно работает как с уже записанными, так и с абсолютно новыми документами.
Рассмотрим альтернативный вариант для обычных форм, когда не требуется создавать промежуточные обработчики выбора в самом документе. Мы можем передать в обработку контекст формы и напрямую модифицировать табличную часть открытого объекта.
При открытии обработки из документа передаем форму в качестве владельца:
ФормаЗаполнения = ОбработкаОбъект.ПолучитьФорму("Форма", ЭтаФорма);
ФормаЗаполнения.Открыть();
В коде кнопки переноса в обработке обращаемся к объекту документа через свойство ВладелецФормы:
Если ВладелецФормы = Неопределено Тогда
Возврат;
КонецЕсли;
ДокументВладелец = ВладелецФормы.ЭтотОбъект; // или ВладелецФормы.ДокументОбъект
Для Каждого СтрокаТЧ Из ТабЧасть Цикл
СтрокаДок = ДокументВладелец.Товары.Добавить();
СтрокаДок.Номенклатура = СтрокаТЧ.Номенклатура;
СтрокаДок.Количество = СтрокаТЧ.Вес;
СтрокаДок.Количество1 = СтрокаТЧ.Размер;
СтрокаДок.коэффициент1 = СтрокаТЧ.УдельныйВес;
КонецЦикла;
ВладелецФормы.Модифицированность = Истина;
Закрыть();
При таком подходе строки сразу же отображаются в табличном поле документа на экране без необходимости перезаписи документа или перечитывания данных из базы.
Проанализируем ситуацию, когда строк для переноса очень много (сотни или тысячи). Построчный обход в цикле с вызовом метода Добавить() может приводить к заметным задержкам интерфейса.
Если требуется полностью заменить строки табличной части документа результатом обработки, гораздо эффективнее использовать метод Загрузить(). Для этого имена колонок в выгружаемой таблице значений должны совпадать с именами реквизитов табличной части документа:
ТаблицаДляЗагрузки = Новый ТаблицаЗначений;
ТаблицаДляЗагрузки.Колонки.Добавить("Номенклатура");
ТаблицаДляЗагрузки.Колонки.Добавить("Количество");
ТаблицаДляЗагрузки.Колонки.Добавить("Количество1");
ТаблицаДляЗагрузки.Колонки.Добавить("коэффициент1");
Для Каждого СтрокаТЧ Из ТабЧасть Цикл
НоваяСтр = ТаблицаДляЗагрузки.Добавить();
НоваяСтр.Номенклатура = СтрокаТЧ.Номенклатура;
НоваяСтр.Количество = СтрокаТЧ.Вес;
НоваяСтр.Количество1 = СтрокаТЧ.Размер;
НоваяСтр.коэффициент1 = СтрокаТЧ.УдельныйВес;
КонецЦикла;
// Быстрая пакетная загрузка в объект документа
ДокументВладелец.Товары.Загрузить(ТаблицаДляЗагрузки);
ВладелецФормы.Модифицированность = Истина;
Метод Загрузить() работает значительно быстрее, так как не вызывает многократную перерисовку элементов управления и выполняет загрузку данных единым блоком в памяти.