В процессе разработки интерфейсов на платформе «1С:Предприятие» перед программистом нередко встает задача повышения юзабилити системы. Одним из наиболее эффективных решений является организация быстрого предпросмотра прикрепленных файлов или изображений (например, сканов документов, фотографий номенклатуры) прямо при позиционировании на строке списка справочника. Это избавляет пользователей от необходимости открывать каждый элемент отдельно.
Однако при реализации этого механизма разработчики часто сталкиваются со специфическим поведением платформы. Например, при попытке извлечь данные из реквизита типа ХранилищеЗначения напрямую через строку табличного поля возвращается Неопределено или возникает ошибка «Несоответствие типов». В этой статье мы подробно разберем, почему возникают эти проблемы, как правильно организовать получение и вывод изображений на обычных и управляемых формах, а также рассмотрим вопросы оптимизации производительности.
Разберем по шагам, почему стандартное обращение вида Элемент.ТекущаяСтрока.Скан.Получить() в обычной форме часто не дает нужного результата и возвращает пустое значение. Для этого проанализируем три ключевые особенности работы платформы «1С:Предприятие» с тяжелыми типами данных:
ТекущаяСтрока табличного поля в обычных формах возвращает объект типа СправочникСсылка. Когда мы обращаемся к реквизитам через точку (например, Ссылка.Скан), система выполняет неявный запрос к базе данных для получения этого свойства. Однако для реквизитов с типом ХранилищеЗначения платформа делает исключение: данные из них не считываются автоматически при обращении через ссылку, так как файлы могут весить десятки мегабайт. Платформа умышленно возвращает Неопределено, чтобы защитить систему от критического падения производительности и перерасхода оперативной памяти при прокрутке списка.Скан у такой строки отсутствует или равен пустой ссылке. Попытка вызвать метод Получить() у несуществующего объекта закономерно приведет к ошибке выполнения кода.Картинка элемента формы само ХранилищеЗначения без предварительного вызова метода Получить(), либо если в самом хранилище находятся сырые ДвоичныеДанные файла, которые элемент формы «Поле картинки» не может отобразить без явного преобразования в объект Картинка.Теперь, когда причины понятны, рассмотрим подробные инструкции и примеры кода для реализации предпросмотра в различных режимах работы приложения.
Для того чтобы обойти ограничение платформы на чтение хранилищ значений через ссылку в обычных формах, нам необходимо гарантированно прочитать данные из базы данных. Сделать это можно двумя путями: быстрым (с помощью запроса) и медленным (через получение объекта). Мы настоятельно рекомендуем использовать именно первый вариант.
Рассмотрим подробнее реализацию быстрого и безопасного получения картинки в обработчике события ПриАктивизацииСтроки табличного поля формы списка справочника:
Процедура СправочникСписокПриАктивизацииСтроки(Элемент)
// 1. Предварительно очищаем элемент предпросмотра на форме
ЭлементыФормы.ПредПросмотрСканаДокумента.Картинка = Новый Картинка;
ТекущаяСтрока = Элемент.ТекущаяСтрока;
// 2. Выполняем обязательные проверки на корректность строки
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
Если ТекущаяСтрока.Пустая() Или ТекущаяСтрока.ЭтоГруппа Тогда
Возврат;
КонецЕсли;
// 3. Быстрое получение хранилища значений из базы данных с помощью запроса
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СканыПриходаТоваров.Скан КАК Скан
|ИЗ
| Справочник.СканыПриходаТоваров КАК СканыПриходаТоваров
|ГДЕ
| СканыПриходаТоваров.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ТекущаяСтрока);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Хранилище = Выборка.Скан;
// Проверяем, что в реквизите действительно хранится нужный тип
Если ТипЗнч(Хранилище) = Тип("ХранилищеЗначения") Тогда
// Извлекаем содержимое хранилища
Данные = Хранилище.Получить();
// Определяем, в каком формате записаны данные, и корректно выводим их
Если ТипЗнч(Данные) = Тип("Картинка") Тогда
ЭлементыФормы.ПредПросмотрСканаДокумента.Картинка = Данные;
ИначеЕсли ТипЗнч(Данные) = Тип("ДвоичныеДанные") Тогда
// Если в базе сохранен исходный файл, инициализируем объект Картинка из двоичных данных
ЭлементыФормы.ПредПросмотрСканаДокумента.Картинка = Новый Картинка(Данные);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Использование запроса в данном примере позволяет прочитать из базы данных только одно конкретное поле Скан для выбранной строки, не инициализируя весь объект справочника целиком в оперативной памяти компьютера пользователя.
В управляемом приложении архитектура работы с интерфейсом кардинально отличается от обычных форм. Мы не можем напрямую обращаться к элементам базы данных на клиенте, а передача тяжелых бинарных данных (картинок) напрямую с сервера на клиент в реквизитах формы вызовет критические задержки в работе интерфейса.
Для отображения картинок в управляемом приложении используется эффективный механизм навигационных ссылок и временного хранилища. Платформа 1С умеет самостоятельно асинхронно считывать данные по специальному адресу-ссылке и отображать их в элементе формы. Посмотрим на пример реализации.
Разберем алгоритм настройки управляемой формы по шагам:
АдресКартинкиПревью и типом данных Строка.ПриАктивизацииСтроки. В обработчике этого события пропишем следующий код:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элемент.ТекущиеДанные;
// Проверяем доступность строки
Если ТекущиеДанные = Неопределено Тогда
АдресКартинкиПревью = "";
Возврат;
КонецЕсли;
// Исключаем попытки получить картинку для групп справочника
Если ТекущиеДанные.Свойство("ЭтоГруппа") И ТекущиеДанные.ЭтоГруппа Тогда
АдресКартинкиПревью = "";
Возврат;
КонецЕсли;
// Получаем уникальную навигационную ссылку на реквизит "Скан" конкретного элемента
// Платформа сама считает двоичные данные из СУБД в фоновом режиме
АдресКартинкиПревью = ПолучитьНавигационнуюСсылку(ТекущиеДанные.Ссылка, "Скан");
КонецПроцедуры
Этот способ является максимально производительным, так как получение данных и отрисовка картинки происходят оптимизировано силами самой платформы без написания сложных серверных вызовов со стороны разработчика.
Проанализируем ситуацию, связанную со способом записи файлов в базу данных. Зачастую разработчики сохраняют в ХранилищеЗначения непосредственно объект типа Картинка. Это вполне рабочий вариант, но он имеет ряд скрытых недостатков: теряется исходное расширение файла, ухудшается переносимость данных при интеграциях через API, веб-сервисы и при выгрузке файлов обратно на диск.
Мы рекомендуем сохранять файлы в их первозданном виде — в формате ДвоичныеДанные. Рассмотрим пример правильного кода для сохранения картинки из диалога выбора файлов:
&НаКлиенте
Процедура ДобавитьИзображение(Команда)
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Диалог.Фильтр = "Изображения (*.jpg;*.png)|*.jpg;*.png";
Диалог.МножественныйВыбор = Ложь;
Если Диалог.Выбрать() Тогда
ВыбранныйПуть = Диалог.ПолноеИмяФайла;
// Передаем файл на сервер для записи в базу данных
СохранитьФайлВБазеНаСервере(ВыбранныйПуть);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура СохранитьФайлВБазеНаСервере(ПутьКФайлу)
// Получаем чистые двоичные данные файла с диска
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
// Помещаем их в ХранилищеЗначения с максимальным уровнем сжатия данных (9)
ХранилищеФайла = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
// Записываем данные в новый элемент справочника
НовыйЭлемент = Справочники.СканыПриходаТоваров.СоздатьЭлемент();
НовыйЭлемент.Наименование = "Скан от " + ТекущаяДатаСеанса();
НовыйЭлемент.Скан = ХранилищеФайла;
Попытка
НовыйЭлемент.Записать();
Исключение
ВызватьИсключение "Не удалось сохранить изображение: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Такой подход гарантирует целостность исходного файла и значительно упрощает его последующую выгрузку обратно на диск без каких-либо искажений метаданных.
Представим ситуацию: пользователь быстро пролистывает список справочника, зажимая клавишу «Стрелка вниз» на клавиатуре. При активизации каждой строки система будет мгновенно отправлять запросы к базе данных или серверу 1С для получения картинок. В файловых базах данных или при работе через медленный мобильный интернет это неизбежно приведет к сильным «зависаниям» интерфейса и дискомфорту в работе.
Для решения этой проблемы мы можем использовать метод отложенной загрузки с помощью обработчика ожидания. Суть метода заключается в том, что картинка начинает запрашиваться из базы данных только тогда, когда пользователь остановил скроллинг на конкретной строке более чем на 0.3–0.5 секунды.
Рассмотрим, как реализовать отложенную загрузку на управляемой форме:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
// Отключаем ранее запущенный обработчик ожидания, если пользователь продолжил движение
ОтключитьОбработчикОжидания("ПодключитьИнициализациюПревью");
// Запускаем новый таймер ожидания на 0.3 секунды
ПодключитьОбработчикОжидания("ПодключитьИнициализациюПревью", 0.3, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПодключитьИнициализациюПревью()
ТекущиеДанные = Элементы.Список.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
АдресКартинкиПревью = "";
Возврат;
КонецЕсли;
// Запрос картинки выполнится только после паузы в скроллинге
АдресКартинкиПревью = ПолучитьНавигационнуюСсылку(ТекущиеДанные.Ссылка, "Скан");
КонецПроцедуры
С помощью этого несложного приема мы минимизируем количество запросов к серверу базы данных, снижаем нагрузку на сеть и обеспечиваем плавную работу интерфейса программы даже при наличии сотен тысяч элементов в списке справочника.