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