Как правильно записать данные во вспомогательную форму ввода и перенести их в табличную часть документа 1С

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

При разработке интерфейсов в управляемом приложении 1С часто возникает задача: предоставить пользователю удобную отдельную форму для редактирования сложной строки табличной части или ввода дополнительных параметров. Однако начинающие разработчики часто сталкиваются с аппаратными и архитектурными трудностями при попытке сохранить введенные данные обратно в документ.

В этой статье мы подробно разберем, почему стандартные попытки записать данные напрямую в базу данных приводят к критическим ошибкам, проанализируем распространенные заблуждения и рассмотрим три надежных современных способа передачи данных из вспомогательной формы ввода в табличную часть документа-владельца «в памяти» без преждевременной перезаписи всего объекта.

В чем суть проблемы и почему возникает ошибка блокировки?

Давайте проанализируем ситуацию, с которой столкнулся автор темы. Он открывал вспомогательную форму редактирования данных за день, передавая туда ссылку на документ и номер строки табличной части. Попытка записать данные напрямую в базу данных через следующий код:


ДокументОбъект = Ссылка.ПолучитьОбъект();
// ... заполнение реквизитов табличной части ...
ДокументОбъект.Записать();

привела к закономерной ошибке при последующей попытке провести документ из его основной формы: «Данные были изменены или удалены другим пользователем».

Выясним причину этого поведения. В платформе 1С:Предприятие 8 реализован механизм оптимистической блокировки. Когда пользователь открывает документ, его данные считываются из базы данных и помещаются в оперативную память клиентского сеанса (в реквизит формы Объект). Если в этот момент другое окно, фоновое задание или наша вспомогательная форма получает объект по ссылке через метод ПолучитьОбъект(), изменяет его и физически записывает в базу данных методом Записать(), то версия объекта в базе данных увеличивается.

Когда пользователь в основном окне документа нажимает кнопку «Записать» или «Провести», платформа сравнивает версию данных в памяти формы с версией данных в таблице базы данных. Поскольку версии различаются (в БД уже записана новая версия из вспомогательной формы), транзакция откатывается, и система сообщает о конфликте версий. Документ блокируется для записи.

Золотое правило разработки в 1С: любые изменения в документе или справочнике из интерактивных вспомогательных окон должны происходить исключительно «в памяти» формы-владельца. Физическую запись измененного объекта в базу данных должен инициировать сам пользователь, нажимая стандартную кнопку сохранения в основной форме.

---

Способ №1. Использование описания оповещения при закрытии (Рекомендуемый стандарт)

Рассмотрим самый современный, асинхронный и безопасный метод передачи данных в управляемом приложении. Он основан на использовании объекта ОписаниеОповещения. Этот способ работает в веб-клиенте, тонком клиенте и полностью соответствует стандартам разработки фирмы «1С».

Разберем по шагам реализацию этого механизма.

Шаг 1. Открытие вспомогательной формы из основной формы документа

В коде основной формы документа мы должны описать процедуру открытия вспомогательной формы. Обратите внимание, что мы не просто открываем форму, а передаем в нее специальный параметр ОповещениеОЗакрытии.


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

Шаг 2. Возврат данных из вспомогательной формы

Во вспомогательной форме ввода при нажатии кнопки «ОК» мы проверяем корректность заполнения реквизитов, упаковываем новые значения в структуру и передаем ее в метод Закрыть(). Базу данных при этом мы никак не затрагиваем!


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

Шаг 3. Прием данных в основной форме документа

В модуле основной формы должна быть объявлена экспортируемая процедура, имя которой мы указали при создании объекта ОписаниеОповещения. Именно в ней происходит «магия» изменения данных в памяти.


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

При использовании этого метода основная форма сразу отобразит новые данные в табличной части, свойство формы Модифицированность станет равным Истина (появится звездочка в заголовке окна), а физическая запись произойдет только тогда, когда пользователь нажмет кнопку «Сохранить» или «Провести» в основном документе.

---

Способ №2. Передача через систему глобальных оповещений (Оповестить / ОбработкаОповещения)

Этот способ основан на механизме рассылки сообщений внутри клиентского сеанса 1С. Рассмотрим подробнее, как он устроен.

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

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

Посмотрим на пример реализации этого метода:

Код во вспомогательной форме (отправка данных):


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

Код в основной форме документа (прием данных):


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

Для инициализации этой схемы при открытии вспомогательной формы из основной обязательно передавайте в структуру параметров свойство ЭтаФорма.УникальныйИдентификатор.

---

Способ №3. Использование стандартного механизма выбора (ОповеститьОВыборе)

Данный подход хорошо знаком тем, кто часто разрабатывает формы подбора товаров или выбора значений. Он лаконичен и задействует штатное событие платформы ОбработкаВыбора().

Проанализируем этот алгоритм по шагам:

  1. При вызове метода ОткрытьФорму() в качестве третьего параметра (Владелец) мы передаем элемент управления табличной части или саму форму: ЭтотОбъект.
  2. Во вспомогательной форме при успешном завершении ввода мы вызываем метод ОповеститьОВыборе(СтруктураРезультата). Этот метод автоматически закроет вспомогательное окно.
  3. В основной форме перехватываем управление в предопределенном обработчике события ОбработкаВыбора(ВыбранноеЗначение, Источник).

Посмотрим на код реализации:

Вызов формы из основного документа:


&НаКлиенте
Процедура ОткрытьФормуВвода(Команда)
    
    ПараметрыФормы = Новый Структура("НомерСтроки", Элементы.ДанныеОВремени.ТекущаяСтрока);
    
    // Третьим параметром обязательно передаем ЭтаФорма
    ОткрытьФорму(
        "Документ._КЕ_ЖурналУчетаРабочегоВремени.Форма.ФормаРедактированиеДанныхУчетаВремениЗаДень", 
        ПараметрыФормы, 
        ЭтаФорма
    );
    
КонецПроцедуры

Возврат данных из формы ввода:


&НаКлиенте
Процедура КнопкаОК(Команда)
    
    СтруктураДанных = Новый Структура;
    СтруктураДанных.Вставить("НомерСтроки", НомерСтроки);
    СтруктураДанных.Вставить("Значение", ВведенноеВремя);
    
    // Оповещаем владельца о выборе и закрываем форму
    ОповеститьОВыборе(СтруктураДанных);
    
КонецПроцедуры

Обработка выбора в основной форме документа:


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

---

Как передавать целые строки табличной части "в памяти"?

Иногда требуется отредактировать не одно-два поля в строке, а передать во вспомогательную форму всю строку целиком, изменить ее там, а потом вернуть обратно. Напрямую передавать строку табличной части (тип ДанныеФормыЭлементКоллекции) между формами на клиенте нельзя из-за ограничений сериализации платформы.

Для решения этой задачи мы рекомендуем использовать метод КопироватьДанныеФормы(). Рассмотрим пример:

  1. Создаем во вспомогательной форме реквизит с именем СтрокаДокумента с типом данных, идентичным строке нашей ТЧ.
  2. При открытии вспомогательной формы на сервере копируем данные строки-оригинала в этот реквизит: КопироватьДанныеФормы(Параметры.ИсходнаяСтрока, СтрокаДокумента);
  3. Пользователь редактирует реквизиты формы, связанные со СтрокаДокумента.
  4. При закрытии вспомогательной формы возвращаем измененную структуру СтрокаДокумента обратно в оповещение основной формы и там с помощью аналогичного метода КопироватьДанныеФормы() обновляем исходную строку в ТЧ.

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

← На главную