Как сохранить значения в реквизите, созданном программно, при закрытии и открытии документа в 1С?

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

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

Предположим, у нас есть код в общем модуле, который добавляет реквизит формы и элемент на форму документа Документ.КадровыйПеревод:


Если Имя = "Документ.КадровыйПеревод.Форма.ФормаДокумента" Тогда
    #Область ДобавлениеРеквизитовФормы
    // Массив для новых реквизитов
    ДобавляемыеРеквизиты = Новый Массив;

    // Опишем реквизит формы
    Реквизит_ТекущийОстаток = Новый РеквизитФормы("ТекущийОстаток", Новый ОписаниеТипов("Число", , , Новый КвалификаторыЧисла(10, 3)), , "Остаток",Истина);
    
    // Для наглядности заполним массив после описания реквизитов формы
    ДобавляемыеРеквизиты.Добавить(Реквизит_ТекущийОстаток);
    
    // Добавим новые реквизиты в форму
    Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
    #КонецОбласти

    #Область ДобавлениеЭлементовФормы
    // Горизонтальная группа без отображения на форме
    ГруппаФормы = Форма.Элементы.Добавить("Группа_СправочныеДанные", Тип("ГруппаФормы"));
    ГруппаФормы.Вид = ВидГруппыФормы.ОбычнаяГруппа;
    ГруппаФормы.Отображение = ОтображениеОбычнойГруппы.Нет;
    ГруппаФормы.ОтображатьЗаголовок = Ложь;
    ГруппаФормы.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Горизонтальная;

    // Элементы
    НовыйЭлемент = Форма.Элементы.Добавить("ТекущийОстаток", Тип("ПолеФормы"), ГруппаФормы);
    НовыйЭлемент.ПутьКДанным = "ТекущийОстаток";
    НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
    #КонецОбласти
КонецЕсли;

После выполнения этого кода реквизит ТекущийОстаток появляется на форме, и мы можем вводить в него значения. Однако, после записи документа и его закрытия, а затем повторного открытия, значение этого реквизита оказывается равным 0 или пустым. Выясним причину такого поведения и предложим надежные решения.

Понимание различий: Реквизит формы vs. Реквизит объекта

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

  1. Реквизит формы:
    • Что это: Реквизит формы — это свойство, которое существует только в контексте открытой формы. Он предназначен для хранения временных данных, которые используются для отображения информации на форме, для выполнения расчетов или для интерактивных действий пользователя.
    • Где хранится: Данные, хранящиеся в реквизитах формы, находятся исключительно в оперативной памяти компьютера (RAM) пока форма открыта.
    • Жизненный цикл: Когда форма закрывается, все ее реквизиты и их значения безвозвратно удаляются из памяти. Именно поэтому при повторном открытии документа мы видим, что значение нашего программно добавленного реквизита сбрасывается.
    • Создание: Реквизиты формы можно создавать как в конфигураторе (статически), так и программно (динамически) с помощью метода ИзменитьРеквизиты(), как в нашем примере.
  2. Реквизит объекта (документа):
    • Что это: Реквизит объекта — это поле базы данных, которое является частью структуры самого объекта конфигурации (например, документа, справочника, регистра). Он предназначен для постоянного хранения данных, которые характеризуют этот объект.
    • Где хранится: Данные, хранящиеся в реквизитах объекта, сохраняются непосредственно в информационной базе (на диске) при записи объекта.
    • Жизненный цикл: Эти данные доступны всегда, независимо от того, открыта ли форма, и сохраняются между сеансами работы программы.
    • Создание: Невозможно программно создать реквизит объекта (документа) в режиме выполнения (рантайме). Реквизиты объектов создаются только в режиме конфигуратора или с помощью механизма расширений.

Из этого следует, что наш программно созданный Реквизит_ТекущийОстаток является реквизитом формы, и его данные не могут сохраняться в базе данных по определению. Для того чтобы данные сохранялись, их необходимо записывать в реквизит объекта (документа) или использовать другие механизмы постоянного хранения.

Решение 1: Создание реквизита документа и привязка к нему реквизита формы

Наиболее прямолинейное и часто используемое решение для постоянного хранения данных, связанных с конкретным документом, — это создание реквизита непосредственно в документе. Рассмотрим подробнее, как это сделать, и как связать с ним наш реквизит формы.

Шаг 1: Создание реквизита документа

Как мы уже выяснили, реквизит документа нельзя создать программно в режиме 1С:Предприятие. Для этого нам потребуется режим Конфигуратор или механизм Расширений конфигурации.

  1. Через Конфигуратор (прямое изменение конфигурации):

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

    1. Откройте конфигурацию в режиме Конфигуратора.
    2. Найдите нужный документ (например, Документ.КадровыйПеревод) в дереве объектов конфигурации.
    3. Раскройте ветку "Реквизиты" этого документа.
    4. Добавьте новый реквизит (например, ТекущийОстатокДокумента) с типом "Число" и соответствующими квалификаторами (например, длина 10, точность 3).
    5. Сохраните конфигурацию базы данных (F7).
  2. Через Расширение конфигурации (рекомендуемый способ для типовых конфигураций):

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

    1. Откройте конфигурацию в режиме Конфигуратора.
    2. Перейдите в меню "Конфигурация" -> "Расширения конфигурации".
    3. Создайте новое расширение или выберите существующее.
    4. В расширении найдите объект Документ.КадровыйПеревод. Если его нет, добавьте его в расширение, перетащив из основной конфигурации или через контекстное меню "Добавить в расширение".
    5. В расширении для объекта Документ.КадровыйПеревод добавьте новый реквизит (например, ТекущийОстатокДокументаРасширения) с типом "Число" и квалификаторами.
    6. Сохраните расширение и обновите конфигурацию базы данных.

    Важно: При создании реквизита в расширении, его имя должно быть уникальным в рамках расширения, но не обязательно уникальным относительно основной конфигурации, если вы не планируете прямой конфликт имен.

Шаг 2: Привязка программно созданного реквизита формы к реквизиту документа

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

Рассмотрим измененный код:


Если Имя = "Документ.КадровыйПеревод.Форма.ФормаДокумента" Тогда
    #Область ДобавлениеРеквизитовФормы
    // Массив для новых реквизитов
    ДобавляемыеРеквизиты = Новый Массив;

    // Опишем реквизит формы. Теперь он будет представлять собой "прослойку"
    // между элементом формы и реквизитом объекта.
    // Указываем тип и квалификаторы, соответствующие реквизиту объекта.
    Реквизит_ТекущийОстаток = Новый РеквизитФормы("ТекущийОстаток", Новый ОписаниеТипов("Число", , , Новый КвалификаторыЧисла(10, 3)), "Объект.ТекущийОстатокДокумента", "Остаток", Истина);
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  Здесь указываем ПутьКДанным к реквизиту документа

    ДобавляемыеРеквизиты.Добавить(Реквизит_ТекущийОстаток);
    
    Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
    #КонецОбласти

    #Область ДобавлениеЭлементовФормы
    // Горизонтальная группа без отображения на форме
    ГруппаФормы = Форма.Элементы.Добавить("Группа_СправочныеДанные", Тип("ГруппаФормы"));
    ГруппаФормы.Вид = ВидГруппыФормы.ОбычнаяГруппа;
    ГруппаФормы.Отображение = ОтображениеОбычнойГруппы.Нет;
    ГруппаФормы.ОтображатьЗаголовок = Ложь;
    ГруппаФормы.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Горизонтальная;

    // Элементы
    НовыйЭлемент = Форма.Элементы.Добавить("ТекущийОстаток", Тип("ПолеФормы"), ГруппаФормы);
    // ПутьКДанным элемента формы теперь будет ссылаться на наш реквизит формы,
    // который, в свою очередь, связан с реквизитом объекта.
    НовыйЭлемент.ПутьКДанным = "ТекущийОстаток"; 
    НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
    #КонецОбласти
КонецЕсли;

Пояснения к изменениям:

  1. В конструкторе Новый РеквизитФормы() мы указываем третий параметр — Путь к данным. Теперь это "Объект.ТекущийОстатокДокумента" (или "Объект.ТекущийОстатокДокументаРасширения", если реквизит создан в расширении). Это означает, что наш программно созданный реквизит формы ТекущийОстаток будет связан с реквизитом объекта ТекущийОстатокДокумента.
  2. НовыйЭлемент.ПутьКДанным = "ТекущийОстаток": Элемент формы по-прежнему связан с реквизитом формы, но теперь реквизит формы выступает в роли посредника, который "знает", куда сохранять свои данные — в соответствующий реквизит объекта.

Теперь, когда пользователь вводит значение в поле ТекущийОстаток на форме, это значение записывается в реквизит формы. При записи документа (например, по кнопке "Записать" или "Провести"), система автоматически переносит значение из связанного реквизита формы в соответствующий реквизит объекта Объект.ТекущийОстатокДокумента и сохраняет его в базе данных. При повторном открытии документа, значение будет загружено из базы данных в реквизит объекта, затем в реквизит формы, и отображено на форме.

Важно: Убедитесь, что имя реквизита объекта (например, ТекущийОстатокДокумента) совпадает с тем, что вы указали в ПутьКДанным для реквизита формы. Также важно, чтобы типы данных реквизита формы и реквизита объекта соответствовали друг другу.

Решение 2: Использование регистров сведений для хранения данных

Иногда данные, которые мы хотим хранить, не являются прямыми свойствами самого документа, а скорее некоторой дополнительной информацией, связанной с ним или с его контекстом. В таких случаях идеальным решением может стать Регистр сведений.

Когда использовать регистр сведений?

Как это работает?

  1. Создание регистра сведений:

    Как и реквизиты объекта, регистры сведений создаются в Конфигураторе или через Расширения. Мы создаем новый регистр сведений, определяем его измерения (поля, по которым мы будем отбирать записи), ресурсы (сами данные, которые храним) и реквизиты (дополнительные поля). Например, для нашего "Текущего остатка" регистр может выглядеть так:

    • Измерения: ДокументОснование (тип ДокументСсылка.КадровыйПеревод) — чтобы связать запись с конкретным документом.
    • Ресурсы: Остаток (тип Число) — для хранения значения остатка.
    • Периодичность: Может быть установлена, если остаток может меняться во времени и нужно хранить историю.

    Важно выбрать подходящую "Режим записи" регистра: "Независимый" (если записи не связаны напрямую с конкретным регистратором и могут изменяться вручную) или "Подчинение регистратору" (если записи всегда создаются и изменяются только определенным документом).

  2. Запись данных в регистр сведений из формы документа:

    После того как регистр сведений создан, мы можем программно записывать в него данные из формы документа. Это обычно делается в обработчике события, например, при записи или проведении документа.

    Предположим, у нас на форме есть программно созданный реквизит формы ТекущийОстаток (как в исходном примере, но без привязки к реквизиту объекта), и мы хотим сохранить его значение в регистре сведений СведенияОстаткиДокументов.

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

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

  3. Чтение данных из регистра сведений при открытии формы:

    Чтобы отобразить сохраненное значение на форме при ее открытии, нам нужно прочитать его из регистра сведений.

    
    &НаСервере
    Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
        // ... (здесь ваш код добавления реквизитов формы)
    
        // После создания реквизита формы, читаем значение из регистра
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ ПЕРВЫЕ 1
            |    СведенияОстаткиДокументов.Остаток
            |ИЗ
            |    РегистрСведений.СведенияОстаткиДокументов КАК СведенияОстаткиДокументов
            |ГДЕ
            |    СведенияОстаткиДокументов.ДокументОснование = &ДокументОснование";
    
        Запрос.УстановитьПараметр("ДокументОснование", Объект.Ссылка);
    
        РезультатЗапроса = Запрос.Выполнить();
        Выборка = РезультатЗапроса.Выбрать();
    
        Если Выборка.Следующий() Тогда
            Форма.ТекущийОстаток = Выборка.Остаток; // Присваиваем значение реквизиту формы
        Иначе
            Форма.ТекущийОстаток = 0; // Или любое другое значение по умолчанию
        КонецЕсли;
    КонецПроцедуры
    

    В событии ПриСозданииНаСервере мы выполняем запрос к регистру сведений, отбирая записи по ссылке на текущий документ. Если запись найдена, мы присваиваем значение из регистра нашему программно созданному реквизиту формы ТекущийОстаток.

Дополнительные механизмы хранения

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

  1. Пользовательские свойства и реквизиты (БСП):

    Многие типовые конфигурации, разработанные на базе Библиотеки стандартных подсистем (БСП), содержат механизм "Дополнительные реквизиты и сведения". Этот механизм позволяет пользователям (или администраторам) интерактивно добавлять реквизиты к различным объектам (включая документы) прямо в режиме 1С:Предприятие. Эти реквизиты сохраняются в специальных регистрах сведений и могут быть выведены на формы. Если задача заключается в том, чтобы дать пользователю возможность гибко настраивать дополнительные поля без прямого программирования или изменений в конфигураторе, этот механизм является отличным решением.

    Работать с такими реквизитами из кода также можно, используя стандартные методы работы с дополнительными свойствами объектов.

  2. Хранилище настроек (СохраняемыеДанные):

    У управляемой формы есть возможность автоматически сохранять значения своих реквизитов в настройках пользователя. Для этого у формы есть свойство "Автоматическое сохранение данных в настройках". Однако, этот механизм предназначен в первую очередь для сохранения пользовательских настроек формы (например, положения окон, видимости колонок или последних введенных значений для конкретного пользователя) и не является способом для постоянного хранения данных объекта в базе. Данные хранятся локально для пользователя и не переносятся между разными пользователями или компьютерами.

Заключение

Мы видим, что программное создание реквизитов формы, как в нашем первоначальном примере, полезно для временного хранения данных в рамках одного сеанса работы с формой. Однако, для обеспечения постоянства данных между открытиями документа и для их сохранения в базе данных необходимо использовать другие механизмы.

В зависимости от характера и назначения данных, мы рекомендуем следующие подходы:

  1. Если данные являются неотъемлемой частью документа и должны храниться вместе с ним, создайте реквизит документа (в Конфигураторе или через Расширение) и свяжите программно созданный реквизит формы с этим реквизитом объекта через свойство ПутьКДанным.
  2. Если данные имеют сложную структуру, периодичность, или являются дополнительной аналитикой, не привязанной строго к структуре документа, используйте Регистры сведений. В этом случае программно записывайте и считывайте данные из регистра при работе с формой документа.
  3. Если требуется гибкость для конечного пользователя в добавлении полей, рассмотрите механизм пользовательских свойств и реквизитов, если он реализован в вашей конфигурации (часто на базе БСП).

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

← На главную