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