Загрузка данных из внешних файлов (например, Excel) — это стандартная задача, которая часто осложняется необходимостью импорта изображений. Многие начинающие разработчики совершают одну и ту же архитектурную ошибку: пытаются использовать события визуального отображения данных на форме (такие как ПриВыводеСтроки) для выполнения тяжелых операций записи в базу данных. В этой статье мы разберем, почему такой подход ведет к сбоям, и рассмотрим правильный алгоритм программной загрузки изображений из табличного документа.
Рассмотрим ситуацию: разработчик читает файл Excel в ТабличныйДокумент, выводит его в табличное поле на форме и пытается в процедуре ПриВыводеСтроки создать элементы справочника Номенклатура и привязать к ним картинки. Основные проблемы этого метода:
ПриВыводеСтроки срабатывает каждый раз, когда строка отрисовывается на экране. Прокрутка списка, изменение размера окна или даже наведение курсора могут вызвать это событие повторно. Это приводит к попыткам создания дублей данных и бесконечным циклам обновления.Прочитать() для файла и Записать() для справочника. Это вызывает катастрофические задержки в работе интерфейса.Выясним причину: ОформлениеСтроки предназначено исключительно для визуализации — изменения цвета, шрифта или отображения уже существующих в памяти картинок. Для логики импорта данных нам нужен другой инструмент.
Рассмотрим правильную архитектуру процесса. Загрузка должна выполняться по нажатию кнопки в отдельном цикле, который никак не связан с обновлением строк на форме. Проанализируем шаги реализации:
ТабличныйДокумент.Для доступа к картинкам в ТабличномДокументе используется коллекция Рисунки. Посмотрим на пример кода, который позволяет перебрать все изображения и сопоставить их с конкретными ячейками:
// Создаем объект и читаем файл
ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ПутьКФайлу);
// Запускаем основной цикл по строкам (со 2-й строки, если первая - шапка)
Для НомерСтроки = 2 По ТабДок.ВысотаТаблицы Цикл
// Получаем текстовые данные
Артикул = СокрЛП(ТабДок.Область(НомерСтроки, КолонкаАртикула).Текст);
Наименование = СокрЛП(ТабДок.Область(НомерСтроки, КолонкаНаименования).Текст);
// Поиск картинки для текущей строки
КартинкаОбъект = Неопределено;
Для Каждого ТекущийРисунок Из ТабДок.Рисунки Цикл
// Проверяем, находится ли рисунок в текущей строке
Если ТипЗнч(ТекущийРисунок.Объект) = Тип("Картинка") И ТекущийРисунок.ВерхняяЯчейка.НомерСтроки = НомерСтроки Тогда
КартинкаОбъект = ТекущийРисунок.Картинка;
Прервать;
КонецЕсли;
КонецЦикла;
// Обрабатываем полученные данные
Если ЗначениеЗаполнено(Наименование) Тогда
ЗагрузитьНоменклатуруСКартинкой(Артикул, Наименование, КартинкаОбъект);
КонецЕсли;
КонецЦикла;
Важный нюанс: Картинка в Excel не всегда идеально "лежит" внутри одной ячейки. Она может занимать несколько ячеек или слегка выходить за границы. В коде выше мы используем свойство ВерхняяЯчейка, которое определяет, к какой строке "примагничено" начало изображения.
После того как мы получили объект Картинка из табличного документа, его необходимо правильно сохранить. Обычно в типовых конфигурациях (например, УТ 10.3) изображения хранятся в справочнике ХранилищеДополнительнойИнформации. Разберем по шагам процесс сохранения:
Номенклатура.ХранилищеДополнительнойИнформации.ХранилищеЗначения с использованием сжатия.Рассмотрим пример функции для сохранения:
Процедура ЗагрузитьНоменклатуруСКартинкой(Артикул, Наименование, КартинкаОбъект)
// 1. Поиск номенклатуры
СсылкаНоменклатура = Справочники.Номенклатура.НайтиПоАртикулу(Артикул);
Если СсылкаНоменклатура.Пустая() Тогда
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Артикул = Артикул;
НовыйЭлемент.Наименование = Наименование;
НовыйЭлемент.Записать();
СсылкаНоменклатура = НовыйЭлемент.Ссылка;
КонецЕсли;
// 2. Если есть картинка - сохраняем ее
Если КартинкаОбъект <> Неопределено Тогда
НовоеХранилище = Справочники.ХранилищеДополнительнойИнформации.СоздатьЭлемент();
НовоеХранилище.Объект = СсылкаНоменклатура;
НовоеХранилище.ВидДанных = Перечисления.ВидыДополнительнойИнформацииОбъектов.Изображение;
НовоеХранилище.Наименование = "Основное изображение";
// Помещаем картинку в хранилище значений
НовоеХранилище.Хранилище = Новый ХранилищеЗначения(КартинкаОбъект, Новый СжатиеДанных(9));
НовоеХранилище.Записать();
// Устанавливаем основное изображение в номенклатуре (если есть такой реквизит)
ОбъектНоменклатура = СсылкаНоменклатура.ПолучитьОбъект();
ОбъектНоменклатура.ОсновноеИзображение = НовоеХранилище.Ссылка;
ОбъектНоменклатура.Записать();
КонецЕсли;
КонецПроцедуры
Проанализируем ситуацию, когда файлов много или они содержат тысячи строк. В этом случае прямой перебор коллекции Рисунки внутри цикла по строкам может замедлить работу (сложность алгоритма составит O(N*M), где N — строки, M — рисунки).
Рассмотрим способ оптимизации через индексацию:
Перед началом основного цикла загрузки создадим Соответствие, где ключом будет номер строки, а значением — объект картинки. Пройдем по коллекции Рисунки один раз, заполним соответствие, а затем в основном цикле будем получать картинку мгновенно по номеру строки.
// Индексация рисунков
КартинкиПоСтрокам = Новый Соответствие;
Для Каждого Рисунок Из ТабДок.Рисунки Цикл
Если Рисунок.ТипРисунка = ТипРисункаТабличногоДокумента.Картинка Тогда
НомерСтр = Рисунок.ВерхняяЯчейка.НомерСтроки;
КартинкиПоСтрокам.Вставить(НомерСтр, Рисунок.Картинка);
КонецЕсли;
КонецЦикла;
// Теперь в основном цикле
Для НомерСтроки = 1 По Высота Цикл
ТекущаяКартинка = КартинкиПоСтрокам.Получить(НомерСтроки);
// ... дальнейшая логика
КонецЦикла;
Также стоит помнить о следующих моментах:
ТабличныйДокумент и Соответствие с картинками, чтобы освободить оперативную память.СокрЛП() при получении текста из ячеек Excel, так как невидимые пробелы часто мешают поиску номенклатуры в базе.Подводя итог: забудьте про ОформлениеСтрок при выполнении задач по импорту данных. Используйте объектный подход, работайте напрямую с ТабличнымДокументом в программных циклах, и ваша загрузка будет работать быстро и стабильно.