При разработке или доработке форм подбора товаров в типовых конфигурациях, таких как 1С:Управление торговлей 11 (УТ 11), 1С:ERP или 1С:Комплексная автоматизация, часто возникает задача визуализации. Отображение изображения товара при позиционировании на строке списка существенно облегчает работу менеджера по продажам. Однако из-за особенностей хранения медиафайлов в современных конфигурациях стандартные платформенные методы могут вызывать трудности или приводить к существенному замедлению работы интерфейса.
В этой статье мы подробно разберем, как правильно реализовать показ картинок номенклатуры при активизации строки, почему стандартный метод получения навигационной ссылки может работать не так, как ожидается, и как оптимизировать производительность формы, чтобы избежать зависаний при быстром скроллинге списка товаров.
Часто разработчики пытаются получить адрес картинки напрямую через реквизит справочника Номенклатура, вызывая платформенную функцию ПолучитьНавигационнуюСсылку для реквизита ФайлКартинки. В результате они получают адрес вида:
e1cib/data/Справочник.Номенклатура.ФайлКартинки?ref=90d9eca86b...
Проанализируем, почему этот адрес не отображает картинку в элементе формы типа Поле картинки. Дело в том, что в современных конфигурациях на базе Библиотеки стандартных подсистем (БСП) файлы изображений не хранятся непосредственно внутри элемента справочника Номенклатура. Реквизит ФайлКартинки хранит лишь ссылку на другой справочник — НоменклатураПрисоединенныеФайлы.
Сам файл (двоичные данные изображения) располагается либо в реквизите ФайлХранилище (тип ХранилищеЗначения) этого подчиненного справочника, либо на внешнем томе хранения файлов (на жестком диске сервера). Когда мы пытаемся получить навигационную ссылку для ФайлКартинки, платформа генерирует ссылку на сам объект ссылки, а не на его двоичные данные. Поле картинки на форме такую ссылку отобразить не может.
Если бы мы решали задачу исключительно средствами платформы «1С:Предприятие», без использования БСП, нам потребовалось бы обратиться напрямую к реквизиту ФайлХранилище подчиненного справочника НоменклатураПрисоединенныеФайлы.
Синтаксис метода ПолучитьНавигационнуюСсылку позволяет передать ссылку на объект присоединенного файла и имя реквизита, содержащего двоичные данные:
АдресИзображения = ПолучитьНавигационнуюСсылку(СсылкаНаПрисоединенныйФайл, "ФайлХранилище");
Однако у этого способа есть критический недостаток. Если в настройках системы включено хранение присоединенных файлов в томах на диске, в реквизите ФайлХранилище базы данных будет пусто. Платформенный метод вернет пустую ссылку, и картинка не отобразится. Поэтому в типовых конфигурациях необходимо использовать механизмы БСП.
Для конфигураций, использующих БСП (включая УТ 11), разработчики предусмотрели универсальные общие модули, которые автоматически определяют, где именно хранится файл (в базе данных или на диске), считывают его и помещают во временное хранилище, возвращая готовый адрес для отображения на форме.
Разберем рабочий программный код. Нам понадобятся:
АдресКартинки (тип: Строка).АдресКартинки.ПриАктивизацииСтроки.Пропишем следующий код в модуле управляемой формы подбора:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено И ЗначениеЗаполнено(ТекущиеДанные.Ссылка) Тогда
// Запускаем серверный вызов без контекста для получения адреса изображения
АдресКартинки = ПолучитьАдресКартинкиНоменклатуры(ТекущиеДанные.Ссылка);
Иначе
АдресКартинки = "";
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьАдресКартинкиНоменклатуры(СсылкаНаНоменклатуру)
// Получаем ссылку на присоединенный файл картинки из номенклатуры
СсылкаНаФайл = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(СсылкаНаНоменклатуру, "ФайлКартинки");
Если ЗначениеЗаполнено(СсылкаНаФайл) Тогда
Попытка
// Используем типовой метод БСП для получения данных файла
ДанныеФайла = РаботаСФайлами.ДанныеФайла(СсылкаНаФайл);
Возврат ДанныеФайла.СсылкаНаДвоичныеДанныеФайла;
Исключение
Возврат "";
КонецПопытки;
КонецЕсли;
Возврат "";
КонецФункции
Рассмотрим подробнее, почему в функцию РаботаСФайлами.ДанныеФайла или её аналог ПрисоединенныеФайлы.ПолучитьДанныеФайла часто передают уникальный идентификатор формы. Выясним причину этого механизма.
При получении двоичных данных картинки БСП помещает файл во временное хранилище на сервере 1С. По умолчанию время жизни данных во временном хранилище ограничено одним серверным вызовом. Если поместить картинку в хранилище без привязки к форме, то сразу после завершения работы серверной процедуры это хранилище очистится, и клиентское поле картинки на форме останется пустым.
Чтобы этого избежать, адрес временного хранилища связывают с временем жизни формы. Для этого передается уникальный идентификатор формы. Пока форма открыта — картинка кэшируется в оперативной памяти сервера и успешно отображается на клиенте. В БСП этот параметр может передаваться неявно или явно в зависимости от используемой версии библиотеки:
// Пример явной передачи идентификатора формы для связывания времени жизни данных
ДанныеФайла = РаботаСФайлами.ДанныеФайла(СсылкаНаФайл, УникальныйИдентификаторФормы);
Событие ПриАктивизацииСтроки срабатывает каждый раз, когда пользователь перемещает курсор по списку товаров. Если пользователь зажмет клавишу "Вниз" на клавиатуре, событие будет генерироваться десятки раз в секунду. Каждый раз будет происходить серверный вызов, обращение к базе данных или диску, чтение файла и отправка его обратно на клиент. Это гарантированно приведет к "зависанию" и лагам интерфейса.
Разберем два эффективных метода оптимизации этой проблемы.
Чтобы не делать повторные запросы к серверу для товаров, картинки которых мы уже получали в рамках текущего сеанса подбора, организуем кэш на клиенте с помощью коллекции Соответствие.
В начале модуля формы объявим переменную:
Перем КэшКартинок;
Инициализируем её при создании формы на сервере или при открытии на клиенте:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
КэшКартинок = Новый Соответствие;
КонецПроцедуры
Теперь доработаем процедуру активизации строки так, чтобы она сначала проверяла кэш:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элемент.TeкущиеДанные;
Если ТекущиеДанные = Неопределено Или Не ЗначениеЗаполнено(ТекущиеДанные.Ссылка) Тогда
АдресКартинки = "";
Возврат;
КонецЕсли;
СсылкаНаТовар = ТекущиеДанные.Ссылка;
// Проверяем, есть ли адрес картинки в локальном кэше на клиенте
НайденныйАдрес = КэшКартинок.Получить(СсылкаНаТовар);
Если НайденныйАдрес <> Неопределено Тогда
АдресКартинки = НайденныйАдрес;
Иначе
// Если в кэше нет, запрашиваем сервер
АдресКартинки = ПолучитьАдресКартинкиНоменклатуры(СсылкаНаТовар);
// Записываем результат в кэш для последующих обращений
КэшКартинок.Вставить(СсылкаНаТовар, АдресКартинки);
КонецЕсли;
КонецПроцедуры
Для борьбы с зависаниями во время быстрой прокрутки списка применим паттерн "Debounce" (устранение дребезга). Суть метода в том, что мы не будем запрашивать картинку мгновенно. Мы подождем, пока пользователь остановится на конкретной строке хотя бы на 0.2–0.3 секунды, и только тогда выполним получение изображения.
Добавим в модуль формы клиентскую переменную для хранения текущей выбранной ссылки:
Перем ТекущийТоварДляОтображения;
Перепишем обработчик события активизации строки и добавим процедуру ожидания:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элемент.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Или Не ЗначениеЗаполнено(ТекущиеДанные.Ссылка) Тогда
ОтключитьОбработчикОжидания("Подключаемый_ПоказатьКартинкуСЗадержкой");
АдресКартинки = "";
Возврат;
КонецЕсли;
ТекущийТоварДляОтображения = ТекущиеДанные.Ссылка;
// Каждый раз при переходе на новую строку сбрасываем старый таймер и взводим новый
ОтключитьОбработчикОжидания("Подключаемый_ПоказатьКартинкуСЗадержкой");
ПодключитьОбработчикОжидания("Подключаемый_ПоказатьКартинкуСЗадержкой", 0.3, Истина);
КонецПроцедуры
&НаКлиенте
Процедура Подключаемый_ПоказатьКартинкуСЗадержкой()
// Этот код выполнится только если пользователь задержался на строке более 0.3 сек
Если ЗначениеЗаполнено(ТекущийТоварДляОтображения) Тогда
НайденныйАдрес = КэшКартинок.Получить(ТекущийТоварДляОтображения);
Если НайденныйАдрес <> Неопределено Тогда
АдресКартинки = НайденныйАдрес;
Иначе
АдресКартинки = ПолучитьАдресКартинкиНоменклатуры(ТекущийТоварДляОтображения);
КэшКартинок.Вставить(ТекущийТоварДляОтображения, АдресКартинки);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Комбинация клиентского кэширования и обработчика ожидания гарантирует плавную работу интерфейса подбора товаров. Пользователь сможет быстро пролистывать весь список номенклатуры без единой задержки, а детальное изображение товара будет подгружаться мгновенно и мягко при фиксации курсора на интересующей позиции.