Как сохранить табличную часть внешней обработки в 1С:Предприятии 8.3 для дальнейшего использования?

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

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

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

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

Рассмотрим подробнее каждый из подходов.

1. Автоматическое сохранение через реквизит формы строкового типа с использованием ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр

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

  1. Создание реквизита формы:
    Прежде всего, нам потребуется создать на форме обработки новый реквизит строкового типа. Мы можем назвать его, например, РеестрСохранение. Важно установить ему неограниченную длину или достаточно большую, чтобы вместить сериализованную табличную часть. Этот реквизит должен быть помечен для автоматического сохранения в настройках (в свойствах реквизита установить флаг "Сохраняемые данные"). Также убедитесь, что в свойствах самой формы установлено "АвтоматическоеСохранениеДанныхВНастройках" в значение "Использовать".
  2. Обработчики событий формы:
    Для того чтобы платформа знала, как сериализовать и десериализовать нашу табличную часть, нам необходимо реализовать обработчики событий формы ПриСохраненииДанныхВНастройкахНаСервере и ПриЗагрузкеДанныхИзНастроекНаСервере.

Посмотрим на пример кода:


&НаСервере
Процедура ПриСохраненииДанныхВНастройкахНаСервере(Настройки)
    // Объект.Реестр - это табличная часть внешней обработки.
    // Сначала выгружаем табличную часть в ТаблицуЗначений, затем сериализуем её в строку.
    Настройки["РеестрСохранение"] = ЗначениеВСтрокуВнутр(Объект.Реестр.Выгрузить());
КонецПроцедуры

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

Важный нюанс: принудительное сохранение изменений.

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

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


&НаКлиенте
Процедура ПриИзмененииТЧ(Элемент) // Или другое событие, когда ТЧ меняется
    СохраняемыеВНастройкахДанныеМодифицированы = Истина;
КонецПроцедуры

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

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

2. Использование Хранилища Общих Настроек

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

Принцип работы:

  1. Мы формируем структуру данных (например, Соответствие или Структуру), в которую помещаем все необходимые для сохранения реквизиты, включая выгруженную в ТаблицуЗначений табличную часть.
  2. Используем функцию ОбщегоНазначения.ХранилищеОбщихНастроекСохранить() для записи этой структуры в базу данных под определённым ключом.
  3. При необходимости используем функцию ОбщегоНазначения.ХранилищеОбщихНастроекЗагрузить() для восстановления данных.

Посмотрим на пример реализации с сохранением нескольких реквизитов и табличной части:


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

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

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

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

// Вызов процедур с клиента
&НаКлиенте
Процедура ПриЗакрытии()
    СохранитьНастройкиНаСервере();
КонецПроцедуры

&НаКлиенте
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    ВосстановитьНастройкиНаСервере();
КонецПроцедуры

Общие и пользовательские настройки:

Функции ХранилищеОбщихНастроекСохранить и ХранилищеОбщихНастроекЗагрузить имеют параметр Пользователь.

  1. Если мы хотим сохранить настройки для конкретного пользователя, мы можем передать в этот параметр текущего пользователя (например, Пользователи.ТекущийПользователь() или пустую строку "" в более старых версиях, что могло привести к сохранению под именем текущего пользователя, если это не было явно указано как пустое имя).
  2. Чтобы сделать настройки общими и доступными для всех пользователей, необходимо передать в параметр Пользователь пустую строку "". Важно не путать пустую строку с Неопределено. Если передать Неопределено, то система будет считать, что пользователь не указан и будет сохранять настройки от имени текущего пользователя, а не общие.

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

3. Сохранение во внешний файл с использованием ЗначениеВФайл и ЗначениеИзФайла

Этот метод предоставляет максимальную гибкость и удобен, когда необходимо переносить данные между разными информационными базами или вручную управлять файлами сохранения. Мы можем сохранить любую сериализуемую структуру данных (включая ТаблицуЗначений) непосредственно в файл на диске.

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

  1. Определение имени файла:
    Нам потребуется функция для определения пути к файлу, куда будут сохраняться данные. Мы можем разместить этот файл, например, в каталоге информационной базы.
  2. Сериализация и десериализация всех реквизитов:
    Создадим универсальные процедуры для сохранения и восстановления всех реквизитов формы, включая табличные части.

Пример кода:


// Вспомогательная функция для получения полного имени файла сохранения.
// Мы можем разместить её в модуле формы или в общем модуле.
// Чтобы данные разных обработок не перепутались, используйте уникальное имя файла.
&НаСервере
Функция ПолучитьИмяФайлаНастроек()
    // Этот метод получения каталога ИБ может отличаться в зависимости от типа соединения (файловое/клиент-сервер)
    // и настроек. Для файловой базы это будет путь к .ibd файлу, для клиент-серверной - обычно не имеет смысла.
    // Если нужно хранить в определённом месте, используйте ОбщиеФункцииКлиентСервер.ПолучитьКаталогВременныхФайлов()
    // или укажите фиксированный путь.
    // Для демонстрации примера возьмём простой вариант с именем обработки в каталоге пользователя или временных файлов.
    
    // Пример для временного файла, который будет уникален для каждого сеанса/пользователя
    Возврат КаталогВременныхФайлов() + "ПеренестиОстаткиДебеторскойКредиторскойЗадолженности_" + УникальныйИдентификатор + ".tmp";

    // Если нужно жестко привязать к каталогу ИБ (только для файлового варианта, и если есть доступ на запись):
    // СтрокаСоединения = СтрокаСоединенияИнформационнойБазы();
    // Позиция = Найти(СтрокаСоединения, "File="); // Внимание: "File" или "File=" в зависимости от версии и ОС
    // КаталогБД = Сред(СтрокаСоединения, Позиция + 5); // +5 для "File="
    // Позиция = Найти(КаталогБД, ";");
    // Если Позиция > 0 Тогда
    //     КаталогБД = Лев(КаталогБД, Позиция - 1);
    // КонецЕсли;
    // Возврат КаталогБД + "\МояВнешняяОбработкаДанные.tmp";
КонецФункции

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

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

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

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

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

4. Кратко о других вариантах: СохранитьЗначение и ВосстановитьЗначение

Функции СохранитьЗначение() и ВосстановитьЗначение() также предназначены для сохранения настроек пользователя, но они менее подходят для непосредственной работы с табличными частями. Они сохраняют значения в реестре или других системных хранилищах и обычно используются для простых типов данных (строки, числа, булево). Для сохранения табличной части с их помощью вам всё равно придётся предварительно сериализовать табличную часть в строку (например, с помощью ЗначениеВСтрокуВнутр()), что по сути сводится к первому рассмотренному нами методу, но без использования реквизитов формы.


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

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

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

Заключение

Мы рассмотрели несколько надёжных способов сохранения табличных частей и других данных внешних обработок в 1С:Предприятии 8.3. Каждый из методов имеет свои преимущества:

  1. Автоматическое сохранение через строковый реквизит формы с использованием ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр – это наиболее простой и интегрированный способ для сохранения настроек формы, привязанных к конкретному пользователю. Не забудьте про СохраняемыеВНастройкахДанныеМодифицированы = Истина; для гарантии сохранения.
  2. Использование ХранилищаОбщихНастроек – отличный вариант для централизованного хранения настроек, которые могут быть как персональными, так и общими для всех пользователей. Он обеспечивает высокую надёжность, так как данные хранятся в базе.
  3. Сохранение во внешний файл с помощью ЗначениеВФайл и ЗначениеИзФайла – предоставляет максимальную гибкость, особенно при необходимости переноса данных или при работе с большими объёмами информации, нежелательной для хранения в системных настройках.

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

← На главную