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

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

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

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

Выясняем причины ошибок: почему не работает «простой» перенос кода

Давайте проанализируем, почему стандартные попытки модификации объекта «в лоб» вызывают ошибки. При работе с управляемыми формами разработчики часто пытаются получить форму программно, извлечь из нее свойство Объект (которое имеет тип ДанныеФормыСтруктура), передать его на сервер и там изменить. На этом пути их ждут две главные проблемы:

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

Рассмотрим пошагово рабочие способы решения этой задачи, начиная от простых клиентских сценариев и заканчивая сложными интеграционными механизмами через расширения.

Способ 1. Модификация данных исключительно на клиенте (простой вариант)

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

Рассмотрим пример, в котором нам нужно открыть форму существующего документа ОприходованиеТоваров и добавить в его табличную часть несколько строк с ценами:


&НаКлиенте
Процедура ИзменитьИОткрытьДокументНаКлиенте(СсылкаНаДокумент)

    // Шаг 1. Формируем структуру с ключом для поиска и открытия существующего объекта
    ПараметрыОткрытия = Новый Структура("Ключ", СсылкаНаДокумент);
    
    // Шаг 2. Открываем форму документа программно и получаем ее контекст
    ФормаДокумента = ОткрытьФорму("Документ.ОприходованиеТоваров.Форма.ФормаДокумента", ПараметрыОткрытия);
    
    // Шаг 3. Напрямую обращаемся к объекту формы и его табличной части
    НоваяСтрока = ФормаДокумента.Объект.Товары.Добавить();
    НоваяСтрока.Номенклатура = ПредопределенноеЗначение("Справочник.Номенклатура.ПустаяСсылка");
    НоваяСтрока.Количество = 1;
    НоваяСтрока.Цена = 100;
    
    // Еще одна строка для примера
    ЕщеОднаСтрока = ФормаДокумента.Объект.Товары.Добавить();
    ЕщеОднаСтрока.Цена = 200;
    
    // Шаг 4. Взводим флаг модифицированности формы, чтобы пользователь видел, 
    // что данные были изменены и требуют сохранения при закрытии
    ФормаДокумента.Модифицированность = Истина;

КонецПроцедуры

Преимущества метода: Максимальная простота реализации, отсутствие контекстных серверных вызовов в вызывающем коде, изменения сразу визуализируются на экране пользователя.

Ограничения: Подходит только для простых документов. Если требуется сложная бизнес-логика заполнения, расчет цен по типам цен или обращение к остаткам на складах, этот способ не применим, так как эти операции невозможно выполнить на клиенте.

Способ 2. Изменение данных формы на сервере с использованием "Знач" и "КопироватьДанныеФормы"

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

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


&НаКлиенте
Процедура ИзменитьИОткрытьДокументЧерезСервер(СсылкаНаДокумент)

    // Шаг 1. Получаем форму нужного документа в переменную без немедленного открытия
    ПараметрыОткрытия = Новый Структура("Ключ", СсылкаНаДокумент);
    ФормаДокумента = ПолучитьФорму("Документ.ТабельУчетаРабочегоВремени.Форма.ФормаДокумента", ПараметрыОткрытия);
    
    // Шаг 2. Копируем данные формы во временную переменную (тип ДанныеФормыСтруктура)
    ДанныеФормыОбъекта = ФормаДокумента.Объект;
    
    // Шаг 3. Вызываем серверную процедуру для выполнения сложных изменений
    ИзменитьДанныеНаСервере(ДанныеФормыОбъекта);
    
    // Шаг 4. Переносим измененные данные обратно в форму документа
    КопироватьДанныеФормы(ДанныеФормыОбъекта, ФормаДокумента.Объект);
    
    // Шаг 5. Сигнализируем платформе о том, что данные в форме отличаются от базы данных
    ФормаДокумента.Модифицированность = Истина;
    
    // Шаг 6. Показываем измененную форму пользователю
    ФормаДокумента.Открыть();

КонецПроцедуры

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

Важное техническое примечание: Использование конструкции Знач при передаче параметра на сервер гарантирует, что платформа передаст структуру по значению, избегая конфликта типов управляемых данных формы. Метод КопироватьДанныеФормы необходим для корректной интеграции изменений обратно в открытый программный контекст формы перед ее отрисовкой на клиенте.

Способ 3. Передача параметров открытия и обработка в расширении (самый надежный способ)

Для сложных документов (таких как расчетные документы в ЗУП/ЗГК, сложные заказы в ERP) предыдущие методы могут приводить к тому, что интерфейс формы не будет корректно перерисован, так как внутренние обработчики формы (события при чтении, при создании) перезапишут ваши ручные изменения своими расчетными алгоритмами.

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

Рассмотрим этот алгоритм по шагам.

Шаг 1. Код вызова формы на клиенте (например, во внешней обработке):


&НаКлиенте
Процедура ОткрытьДокументСПередачейПараметров(СсылкаНаДокумент)

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

КонецПроцедуры

Шаг 2. Код перехвата события в расширении (в модуле формы документа):

Создаем расширение конфигурации, находим форму документа ФормаДокумента и добавляем обработчик события ПриЧтенииНаСервере в режиме &После:


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

Почему этот способ является лучшим для сложных конфигураций?

Резюме и рекомендации

При выборе метода работы руководствуйтесь простым правилом: если документ имеет простую структуру (например, обычный складской документ или простой справочник) — смело используйте Способ 2 с передачей параметра через Знач и использованием процедуры КопироватьДанныеФормы. Это избавит вас от необходимости создавать новые расширения и позволит решить задачу в рамках одной внешней обработки.

Если же вы работаете со сложными документами зарплатного или производственного контура (ERP, ЗУП, КА) — используйте Способ 3. Передача параметров через событие чтения объекта гарантирует стабильность работы программы, правильность расчетов и корректность отображения данных на мониторе конечного пользователя.

← На главную