При разработке обработок по импорту данных из внешних файлов (в частности, Microsoft Excel) программисты часто сталкиваются с необходимостью предоставить пользователю выбор конкретного листа для загрузки. Поскольку само чтение файла и работа с COM-объектами происходят в безопасном и производительном контексте сервера, а выбор листа пользователем — исключительно на клиенте, возникает классическая задача 1С: передача данных через клиент-серверную границу.
В этой статье мы подробно разберем, как правильно организовать получение списка листов книги Excel на сервере, как передать этот список на клиент и корректно заполнить элемент управления на управляемой форме, избежав типичных ошибок (таких как зацикливание, бесконечное дублирование строк или зависание серверных процессов).
Попытка напрямую обратиться к серверным переменным или передать тяжелые COM-объекты (например, Excel.Application) на клиентскую сторону вызовет ошибку платформы. На клиенте недоступны типы данных, специфичные для сервера (например, ТаблицаЗначений или ссылки на COM-серверы). Поэтому передавать нужно простые, сериализуемые типы данных — массивы, структуры или заполнять элементы формы непосредственно на сервере, используя контекст формы.
---
Самый простой и логичный способ — заполнить свойство СписокВыбора элемента формы непосредственно в серверной процедуре. Поскольку управляемая форма автоматически синхронизирует состояние своих элементов между сервером и клиентом, заполненный на сервере список выбора моментально станет доступен пользователю в интерфейсе.
Давайте пошагово разберем, как должна выглядеть правильная архитектура этого процесса:
Посмотрим на практическую реализацию. Нам понадобится разделить логику интерактивного выбора файла и логику заполнения листов, чтобы код оставался чистым и поддерживаемым.
Шаг 1. Клиентская процедура выбора файла
В обработчике начала выбора для поля ввода пути к файлу мы открываем стандартный диалог. После успешного выбора файла мы сразу же запускаем процедуру получения списка листов.
&НаКлиенте
Процедура ПолеВводаНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Диалог.Заголовок = "Выберите файл Excel для загрузки";
Диалог.Фильтр = "Файлы Excel (*.xls; *.xlsx)|*.xls; *.xlsx";
Диалог.МножественныйВыбор = Ложь;
Если Диалог.Выбрать() Тогда
// Записываем путь к файлу в реквизит формы
Объект.ПутьКФайлу = Диалог.ПолноеИмяФайла;
// Сразу вызываем серверный метод для заполнения списка листов
ЗаполнитьСписокЛистовНаСервере();
КонецЕсли;
КонецПроцедуры
Шаг 2. Серверная процедура получения листов
Обратите внимание на важный нюанс: перед добавлением новых элементов в список выбора, его необходимо обязательно очистить. В противном случае при каждом повторном выборе файла список листов будет бесконечно дублироваться.
&НаСервере
Процедура ЗаполнитьСписокЛистовНаСервере()
// Проверяем, указан ли файл
Если Не ЗначениеЗаполнено(Объект.ПутьКФайлу) Тогда
Возврат;
КонецЕсли;
Попытка
Эксель = Новый COMОбъект("Excel.Application");
Исключение
ВызватьИсключение "Не удалось запустить MS Excel. Убедитесь, что приложение установлено на сервере.";
КонецПопытки;
Попытка
// Открываем книгу в невидимом режиме для ускорения работы
Эксель.Visible = Ложь;
Эксель.DisplayAlerts = Ложь;
Книга = Эксель.Workbooks.Open(Объект.ПутьКФайлу);
// Получаем доступ к списку выбора элемента формы
СписокЛистовВыбора = Элементы.ВыберитеНомерЛиста.СписокВыбора;
СписокЛистовВыбора.Очистить(); // Очищаем старые значения!
// Обходим все листы книги и добавляем их имена
Для Каждого ТекущийЛист Из Книга.WorkSheets Цикл
СписокЛистовВыбора.Добавить(ТекущийЛист.Name);
КонецЦикла;
// Корректно закрываем файл и завершаем процесс Excel
Книга.Close(Ложь);
Эксель.Quit();
Исключение
// В случае ошибки обязательно закрываем Excel, чтобы процесс не "завис" в памяти
Если ПреобразоватьКЧислу(Эксель) > 0 Тогда
Эксель.Quit();
КонецЕсли;
ЗаписатьЛогСобытия("Ошибка при чтении листов Excel: " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка);
ВызватьИсключение "Ошибка при чтении структуры файла Excel. Подробности в журнале регистрации.";
КонецПопытки;
КонецПроцедуры
---
Что делать, если список листов или прочитанные данные таблицы Excel нужно не просто вывести в поле выбора, а сохранить на клиенте для дальнейшей сложной логики (например, для интерактивного сопоставления колонок)? В таком случае напрямую работать с элементами формы неудобно. Лучше использовать универсальный механизм 1С — Временное хранилище.
Временное хранилище позволяет передавать между сервером и клиентом любые сериализуемые типы данных (включая таблицы значений, списки значений и структуры) по уникальному адресу-ссылке.
Рассмотрим алгоритм реализации:
&НаСервере
Функция ПолучитьСписокЛистовВоВременноеХранилище()
СписокЛистов = Новый СписокЗначений;
// ... здесь код открытия Excel и заполнения СпискаЛистов ...
// Для примера просто добавим данные:
СписокЛистов.Добавить("Лист1");
СписокЛистов.Добавить("Лист2");
// Помещаем объект во временное хранилище.
// Адрес привязываем к УникальномуИдентификатору формы, чтобы данные жили до закрытия формы.
АдресХранилища = ПоместитьВоВременноеХранилище(СписокЛистов, УникальныйИдентификатор);
Возврат АдресХранилища;
КонецФункции
&НаКлиенте
Процедура ПолучитьДанныеНаКлиенте()
// Получаем адрес временного хранилища с сервера
АдресДанных = ПолучитьСписокЛистовВоВременноеХранилище();
// На клиенте мы можем обратиться к данным (например, получить их копию для анализа)
СписокЛистов = ПолучитьИзВременногоХранилища(АдресДанных);
Для Каждого ЭлементСписка Из СписокЛистов Цикл
Сообщить("Доступен лист: " + ЭлементСписка.Значение);
КонецЦикла;
КонецПроцедуры
---
В практической разработке часто требуется передать полноценную ТаблицуЗначений (ТЗ) с сервера на клиент. Напрямую передать объект типа ТаблицаЗначений на клиент нельзя — платформа выдаст ошибку несоответствия типов. Отличным решением является конвертация таблицы в массив структур. Структуры и массивы прекрасно чувствуют себя на клиенте и легко поддаются обходу в циклах.
Проанализируем вспомогательную функцию, которая быстро решает эту задачу:
// Преобразует таблицу значений в массив структур для передачи на клиент.
// Параметры:
// ТаблицаЗначений - ТаблицаЗначений - исходные данные.
// Возвращаемое значение:
// Массив - массив структур, повторяющих строки исходной таблицы.
Функция ТаблицаЗначенийВМассив(ТаблицаЗначений) Экспорт
МассивРезультата = Новый Массив;
СтрокаКолонок = "";
НужнаЗапятая = Ложь;
// Формируем строку колонок для инициализации структур
Для Каждого Колонка Из ТаблицаЗначений.Колонки Цикл
Если НужнаЗапятая Тогда
СтрокаКолонок = СтрокаКолонок + ",";
КонецЕсли;
СтрокаКолонок = СтрокаКолонок + Колонка.Имя;
НужнаЗапятая = Истина;
КонецЦикла;
// Заполняем массив структур данными
Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл
НоваяСтруктура = Новый Структура(СтрокаКолонок);
ЗаполнитьЗначенияСвойств(НоваяСтруктура, СтрокаТЗ);
МассивРезультата.Добавить(НоваяСтруктура);
КонецЦикла;
Возврат МассивРезультата;
КонецФункции
Этот способ незаменим, когда объем данных небольшой. Однако для огромных таблиц (десятки тысяч строк) данный метод может вызвать просадку производительности из-за накладных расходов на создание объектов типа Структура. Для больших объемов используйте временное хранилище или реквизиты формы.
---
Использование COMОбъект("Excel.Application") имеет ряд критических недостатков:
Начиная с современных версий платформы 1С:Предприятие 8.3, разработчикам доступен быстрый нативный механизм чтения файлов Excel без использования COM. Мы можем прочитать файл напрямую в ТабличныйДокумент.
Проанализируем пример кода чтения структуры листов с помощью нативных механизмов 1С:
&НаСервере
Процедура ПрочитатьЛистыНативно()
ТабДок = Новый ТабличныйДокумент;
Попытка
// Чтение файла напрямую средствами платформы 1С
ТабДок.Прочитать(Объект.ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
СписокЛистовВыбора = Элементы.ВыберитеНомерЛиста.СписокВыбора;
СписокЛистовВыбора.Очистить();
// Платформа автоматически создает области с именами листов
Для Каждого Область Из ТабДок.Области Цикл
// В зависимости от структуры файла, имена листов можно извлечь из метаданных областей
СписокЛистовВыбора.Добавить(Область.Имя);
КонецЦикла;
Исключение
ВызватьИсключение "Не удалось прочитать файл методами 1С: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Этот подход полностью кроссплатформенный, работает в разы быстрее и не требует лицензий на сторонний офисный софт на стороне сервера.
---
Для успешного обмена данными между сервером и клиентом при работе с внешними файлами придерживайтесь простых правил:
Очистить(), чтобы избежать размножения дубликатов при повторных действиях пользователя.Попытка...Исключение и гарантированно закрывайте процессы Excel (метод Quit()) в блоке исключения, чтобы не перегружать память сервера.