gifts2017

Сохранение (чтение) настроек внешней обработки в файл (управляемые формы)

Опубликовал iodine (iodine) в раздел Программирование - Практика программирования

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

Иногда требуется создать внешнюю обработку с большим количеством реквизитов (в том числе и с табличной частью), первое, что мы делаем для сохранения настроек в этом случае - устанавливаем свойство формы АвтоматическоеСохранениеДанныхВНастройках в Использовать. Затем ставим напротив всех нужных реквизитов галочки "Сохранение" и наслаждаемся результатом.

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

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

Пробуем сначала сохранить значения всех реквизитов формы в файл. Поиски в интернете приводят нас к процедуре ЗначениеВФайл и функции ЗначениеВСтрокуВнутр, приём последней пользоваться как бы не рекомендуется, якобы она предназначена для обмена с 7.7. Обе конструкции языка доступны только на сервере и толстом клиенте, поэтому возникает вопрос, как бы обойти значения всех реквизитов формы &НаСервере.

Ведь цикл

Для Каждого Реквизит Из Объект Цикл
КонецЦикла;

выдаст вполне ожидаемую не для всех ошибку: Итератор для значения не определен.

Тоже самое ожидает нас и с переменными ЭтаФорма, ЭтотОбъект. На такую простую вещь можно потратить пару часов, однако отладчик и поисковики творят чудеса - получить значения всех реквизитов формы внешней обработки на сервере можно с помощью следующего кода:

Для Каждого Реквизит Из ЭтаФорма.ПолучитьРеквизиты("Объект") Цикл
   ЗначениеРеквизита = Объект[Реквизит.Имя];
КонецЦикла;

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

В результате этого знания получаем:

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

// Выбор файла для сохранения настроек и вызов серверной процедуры сохранения файла
&НаКлиенте
Процедура ЭкспортНастроекВФайл(Команда)
	
	ДиалогСохраненияФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
	Фильтр = НСтр("ru = 'Файлы TXT'") + "(*.txt)|*.txt|";
	ДиалогСохраненияФайла.Фильтр = Фильтр;
	ДиалогСохраненияФайла.МножественныйВыбор = Ложь;

	Если ДиалогСохраненияФайла.Выбрать() Тогда
		ЭкспортНастроекВФайлНаСервере(ДиалогСохраненияФайла.ПолноеИмяФайла);
	КонецЕсли; 
	
КонецПроцедуры

 

Это даже работает, но радоваться рано - этот код неправильный! Мы допустили одну из самых распространенных ошибок при написании кода на управляемых формах. Выбрав имя файла на клиенте, а потом передав его на сервер, мы забыли, что при клиент-серверном режиме работы код, выполняемый на стороне сервера, будет запущен под другим пользователем. И далеко не факт, что этот пользователь будет иметь доступ туда (например, локальный диск терминальной сессии по RDP), куда мы сохраняем файл.

Поэтому переписываем код, сохраняем файл теперь на клиенте, правда придётся воспользоваться не очень рекомендуемой функцией ЗначениеВСтрокуВнутр:

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

 

 

Теперь перейдём к загрузке из файла. Мы не будем использовать, как мы уже выяснили, следующий неправильный код с передачей только имени файла на сервер:

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


// Выбор файла для загрузки настроек и вызов серверной процедуры загрузки файла
&НаКлиенте
Процедура ИмпортНастроекИзФайла(Команда)
	
	ДиалогСохраненияФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
	Фильтр = НСтр("ru = 'Файлы TXT'") + "(*.txt)|*.txt|";
	ДиалогСохраненияФайла.Фильтр = Фильтр;
	ДиалогСохраненияФайла.МножественныйВыбор = Ложь;

	Если ДиалогСохраненияФайла.Выбрать() Тогда
		ИмпортНастроекИзФайлаНаСервере(ДиалогСохраненияФайла.ПолноеИмяФайла);
	КонецЕсли; 
	
КонецПроцедуры

 

А напишем другой вариант, например, используя временное хранилище:

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

// При выборе файла вызов серверной процедуры загрузки файла из временного хранилища
&НаКлиенте
Процедура ОбработатьВыборФайлаИмпорта(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
	
	Если НЕ Результат Тогда
		Возврат;
	КонецЕсли;
	
	ИмпортНастроекИзВременногоХранилищаНаСервере(Адрес);
	
КонецПроцедуры


// Выбор файла для загрузки настроек
&НаКлиенте
Процедура ИмпортНастроекИзФайла(Команда)
	
	ДиалогСохраненияФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
	Фильтр = НСтр("ru = 'Файлы TXT'") + "(*.txt)|*.txt|";
	ДиалогСохраненияФайла.Фильтр = Фильтр;
	ДиалогСохраненияФайла.МножественныйВыбор = Ложь;

	Если ДиалогСохраненияФайла.Выбрать() Тогда
		Оповещение = Новый ОписаниеОповещения("ОбработатьВыборФайлаИмпорта", ЭтаФорма);
		НачатьПомещениеФайла(Оповещение, , ДиалогСохраненияФайла.ПолноеИмяФайла, Ложь, УникальныйИдентификатор);
	КонецЕсли; 
	
КонецПроцедуры

 

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

Чтобы не насиловать файловую систему временными файлами, давайте перепишем код, без использования временного хранилища, но с использованием не очень рекомендуемой функции ЗначениеИзСтрокиВнутр:

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

// Выбор файла для загрузки настроек и вызов серверной процедуры загрузки
&НаКлиенте
Процедура ИмпортНастроекИзФайла(Команда)
	
	ДиалоЧтенияФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
	Фильтр = НСтр("ru = 'Файлы TXT'") + "(*.txt)|*.txt|";
	ДиалоЧтенияФайла.Фильтр = Фильтр;
	ДиалоЧтенияФайла.МножественныйВыбор = Ложь;

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


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

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

Скачать файлы

Наименование Файл Версия Размер Кол. Скачив.
СохранениеЧтениеНастроекФайл
.epf 10,30Kb
14.07.15
15
.epf 1.0 10,30Kb 15 Скачать

См. также

Вознаграждение за ответ
Сумма: 0 $m
Добавили:
Андрей Вдовин (AEVdovin) (2.20 $m)
Подписаться Добавить вознаграждение

Комментарии

1. Константин Юрин (kostyaomsk) 15.07.15 07:03
Тема актуальная по сохранению и восстановлению настроек во временный для переноса собственных обработок. За приведенные шаблоны кода автору большой плюс - буду тестировать.
Вот даже столкнулся с интересной ситуацией: дорабатываешь типовой отчет для УТ 10.3 в дополнительной форме "настройки", куда выносятся собственные реквизиты-галочки или путь к сохранению в другие форматы (Excel, txt, xml...) и для удобства пользователя ставишь в обычной форме Сохранять значения и эти реквизиты в сохранение - нифига. Даже под полными правами пользователя. Главное и роли админские и все везде включено, а нужно шаманство в режиме предприятия для включения в группу админов.
Еще аналогичное тоже при всех админских "ролях и правах" что на УФ, что на обычных при вызове обработок из справочника внешних отчетов и обработок часть функционала не настраивается, кнопки "серые", а вызывать из главного меню не всегда удобно. Самый вроде бы оптимальный вариант сохранять и читать текстовый файл из каталога пользователя причем автоматически. Или тоже автоматически подстраиваться под чтение-запись в ХранилищеЗначения справочника.
По идее нужен универсальный кусок кода чтоб вставлять его даже в типовые внешние обработки и отчеты чтоб читал-записывал настройки и писал если что про ошибку и нужно уже в ручном режиме исправлять ситуацию (нет прав на запись в каталог пользователя и прочее). Или когда такой вариант не сработал чтоб были системные события СохранитьЗначение и ВосстановитьЗначение. Самое главное что даже в роли "ПолныеПрава" в конфигураторе стоит сохранение настроек пользователя. Может, где-то программно срабатывает обработчик в коде конфигурации, причем у каждой свой. У кого будут какие идеи по модификации предложенного автором решения. Делать диалог выбора файла и отсылать файл текста, можно, но не хотелось бы для массовых решений.
2. iodine (iodine) 15.07.15 09:42
(1) kostyaomsk, я считаю, что правильнее разобраться с правами, чем использовать сохранение и чтение настроек из файла. У меня нет под рукой УТ 10.3, поэтому не могу сказать как там правильно всё настроить, но в других достаточно легко. Например, в БП 3 достаточно добавить роль с правом "Сохранение данных пользователя" и назначить эту роль через профиль нужному пользователю.
kostyaomsk; +1 Ответить
3. Александр П (pacas) 26.10.15 12:25
Спасибо! Помогла статья
4. Андрей Вдовин (AEVdovin) 23.02.16 15:29
Большое спасибо!!!! Это очень полезная обработка!!! А главное - толковое объяснение проблемных моментов....ведь действительно при решении схожей задачи я прибегнул изначально к знаниям из спеца по платформе: ПриСохраненииДанныхВНастройкахНаСервере, ПриВыгрузке...... и т.д.. Затем немного работы с файлами, с хранилищами. Вроде уже не раз когда-то схожие задачи делал - и всегда натыкался на те же пороги - что и описано в статье. Очень рад, что нашелся специалист - который подробно описал возможные преграды при решении такой задачи, а в самой обработке показал пример выгрузки/загрузки настроек и данных реквизитов внеш.обработки. Спасибо!
5. Михаил Миф (xxx90) 19.10.16 09:11
Спасибо за объяснение и обработку(очень пригодилась)!
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа