Как выбрать файл картинки из формы объекта и сохранить его в реквизит из модуля команды в 1С, избегая ошибки "Не найден экспортируемый метод"?

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

В процессе разработки конфигураций на платформе 1С:Предприятие 8.3 часто возникает задача загрузки файлов (например, логотипов) непосредственно из формы объекта в реквизит, имеющий тип ХранилищеЗначения. Обычно для этого используется механизм команд формы. Однако, при попытке реализовать выбор файла с помощью асинхронной функции НачатьПомещениеФайловНаСервер из модуля команды, многие разработчики сталкиваются с ошибкой "Не найден экспортируемый метод". В этой статье мы подробно разберем причину возникновения этой ошибки и предложим эффективное альтернативное решение, которое позволит нам реализовать задуманное, не привязываясь к модулю формы объекта.

Анализируем исходную проблему: "Не найден экспортируемый метод: ЗавершениеВыбораФайлов"

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

Давайте посмотрим на синтаксис, который часто используется и вызывает ошибку:


&НаКлиенте
Процедура МояКоманда(Команда)
    СтрокаФильтра = "Файлы картинок|*.BMP;*.DIB;*.RLE;*.JPG;*.JPEG;*.TIF;*.GIF;*.PNG;*.ICO;*.WMF;*.EMF";
    ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов("Выберите файлы логотипа компании", Ложь, СтрокаФильтра);
    
    // Здесь кроется источник проблемы
    НачатьПомещениеФайловНаСервер(Новый ОписаниеОповещения("ЗавершениеВыбораФайлов", ПараметрыВыполненияКоманды.Источник, Новый Структура("СсылкаОрганизации", Команда.Параметры)),,,ПараметрыДиалога, ПараметрыВыполненияКоманды.Источник.УникальныйИдентификатор);
КонецПроцедуры

&НаКлиенте
Процедура ЗавершениеВыбораФайлов(ОписаниеФайла, ДополнительныеПараметры) Экспорт
    // Код обработки выбранного файла
КонецПроцедуры

Суть проблемы: Когда мы создаем ОписаниеОповещения, первым параметром мы указываем имя процедуры-обработчика ("ЗавершениеВыбораФайлов"), а вторым — объект, в модуле которого эта процедура должна быть найдена. В нашем случае мы передаем ПараметрыВыполненияКоманды.Источник. ПараметрыВыполненияКоманды.Источник в контексте команды формы указывает на форму объекта, из которой была вызвана эта команда. Таким образом, платформа ожидает найти экспортируемую процедуру ЗавершениеВыбораФайлов именно в модуле формы, а не в модуле команды или любом другом месте. Если мы размещаем эту процедуру в модуле команды, форма ее "не видит", что и приводит к ошибке "Не найден экспортируемый метод".

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

Стандартный подход с `НачатьПомещениеФайловНаСервер` (и его ограничения для нашей задачи)

Если бы у нас не было требования не изменять модуль формы, мы могли бы легко решить эту задачу, разместив процедуру-обработчик именно там. Давайте посмотрим, как это выглядело бы:

  1. В модуле команды формы (клиентский контекст):

    
    &НаКлиенте
    Процедура КомандаВыбратьЛоготип(Команда)
        СтрокаФильтра = "Файлы картинок|*.BMP;*.DIB;*.RLE;*.JPG;*.JPEG;*.TIF;*.GIF;*.PNG;*.ICO;*.WMF;*.EMF";
        ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов("Выберите файлы логотипа компании для учетной карточки", Ложь, СтрокаФильтра);
        
        // Передаем форму как источник оповещения
        НачатьПомещениеФайловНаСервер(Новый ОписаниеОповещения("ЗавершениеВыбораФайлов", ЭтотОбъект, Команда.Параметры),,,ПараметрыДиалога, ЭтотОбъект.УникальныйИдентификатор);
    КонецПроцедуры
    
  2. В модуле формы (клиентский контекст):

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

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

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

Альтернативное решение: Использование `ДиалогВыбораФайла` с асинхронным вызовом и временным хранилищем

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

Преимущества этого метода:

Этот подход требует платформу 1С:Предприятие версии 8.3.10 или выше для использования оператора Ждать и асинхронных вызовов.

  1. Шаг 1: Инициируем команду на клиенте и выбираем файл.

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

    
    &НаКлиенте
    Процедура КомандаВыбратьЛоготип(Команда)
        // В Команда.Параметры мы ожидаем получить ссылку на объект, 
        // для которого нужно сохранить логотип.
        // Например, при создании команды в форме, в параметрах команды можно указать:
        // Параметры.СсылкаОбъекта = ЭтотОбъект.Ссылка;
    
        Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
        Диалог.МножественныйВыбор = Ложь; // Выбираем только один файл
        Диалог.Фильтр = "Файлы картинок|*.BMP;*.DIB;*.RLE;*.JPG;*.JPEG;*.TIF;*.GIF;*.PNG;*.ICO;*.WMF;*.EMF";
        Диалог.Заголовок = "Выберите файл логотипа компании";
    
        // Асинхронный вызов диалога выбора файла.
        // Оператор Ждать (await) позволяет дождаться завершения операции без блокировки интерфейса.
        // Требуется платформа 8.3.10 и выше, а также включенное использование асинхронных вызовов в свойствах конфигурации/расширения.
        ИмяВыбранногоФайла = Ждать Диалог.ВыбратьАсинх();
    
        Если Не ЗначениеЗаполнено(ИмяВыбранногоФайла) Тогда
            // Пользователь отменил выбор файла или произошла ошибка
            Возврат; 
        КонецЕсли;
    
        // Шаг 2: Получение двоичных данных и помещение во временное хранилище (внутри этой же процедуры)
        Файл = Новый Файл(ИмяВыбранногоФайла);
        ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяВыбранногоФайла);
        
        // Помещаем двоичные данные файла во временное хранилище на клиенте.
        // УникальныйИдентификатор используется как ключ для хранилища,
        // чтобы данные не были удалены до момента их получения на сервере.
        АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, УникальныйИдентификатор);
    
        // Шаг 3: Вызов серверной процедуры для сохранения данных (также внутри этой же процедуры)
        // Передаем адрес временного хранилища, расширение файла и ссылку на объект, 
        // для которого предназначен логотип.
        ЗагрузитьЛоготипНаСервере(АдресВременногоХранилища, Файл.Расширение, Команда.Параметры.СсылкаОбъекта);
    
    КонецПроцедуры
    
  2. Шаг 2: Получение двоичных данных и помещение во временное хранилище.

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

  3. Шаг 3: Вызов серверной процедуры для сохранения данных.

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

  4. Шаг 4: Получение данных на сервере и запись в реквизит.

    На стороне сервера мы получаем переданные параметры. Используя адрес временного хранилища, мы извлекаем двоичные данные файла. Затем мы получаем объект базы данных по переданной ссылке, присваиваем двоичные данные его реквизиту типа ХранилищеЗначения и записываем изменения.

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

Дополнительные рекомендации и нюансы

Применяя этот подход, мы эффективно обходим ограничение НачатьПомещениеФайловНаСервер и реализуем функциональность выбора и сохранения файлов в реквизит объекта непосредственно из модуля команды, сохраняя чистоту модуля формы и обеспечивая удобство работы пользователя.

← На главную