При разработке обработок загрузки данных из внешних файлов (например, электронных таблиц Microsoft Excel) перед программистом 1С часто встает задача создания удобного интерфейса для сопоставления колонок файла с реквизитами информационной базы. Пользователь должен иметь возможность выбрать, какие колонки загружать, как их переименовать, какие типы данных присвоить и нужно ли суммировать числовые показатели.
В этой статье мы подробно разберем, как устроен подобный интерфейс на управляемых формах 1С, проанализируем технические варианты его реализации, выявим скрытые проблемы популярного «наивного» подхода и рассмотрим наиболее правильные, оптимизированные методы решения этой задачи.
Представим задачу: нам необходимо дать пользователю возможность загрузить файл Excel, прочитать его структуру и вывести на форму список колонок. Для каждой колонки пользователь должен настроить следующие параметры:
При этом количество строк в этой настроечной таблице заранее неизвестно — оно целиком и полностью зависит от структуры импортируемого файла Excel.
---
Первый подход, который часто выбирают начинающие разработчики (и который мы регулярно встречаем в самописных конфигурациях), заключается в динамическом конструировании структуры элементов формы непосредственно в коде при выборе файла. Давайте пошагово разберем, как устроен этот механизм изнутри.
Поскольку количество колонок в файле Excel заранее неизвестно, мы не можем «нарисовать» элементы управления в конфигураторе. Нам приходится прибегать к программному изменению состава реквизитов формы и последующему добавлению связанных с ними элементов интерфейса.
Шаг 1. Программное создание реквизитов на сервере.
Для каждого элемента управления на управляемой форме должен существовать соответствующий реквизит, в котором хранятся данные. Если нам нужно вывести, например, 10 колонок со всеми настройками, нам потребуется создать динамический массив реквизитов и применить метод ИзменитьРеквизиты().
Рассмотрим пример кода, который выполняется на сервере после анализа файла Excel:
// Массив для добавления новых реквизитов формы
МассивДобавляемыхРеквизитов = Новый Массив;
// Предположим, мы получили список колонок файла Excel в массив МассивИменКолонок
Для Каждого ИмяКолонки Из МассивИменКолонок Цикл
// Генерируем уникальный префикс для реквизитов этой строки
ИмяРеквизитаПрефикс = "КолонкаExcel_" + СтрЗаменить(ИмяКолонки, " ", "_");
// Реквизит для флага "Выводить" (Булево)
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(ИмяРеквизитаПрефикс + "_Выводить", Новый ОписаниеТипов("Булево"), , "Выводить"));
// Реквизит для флага "Суммировать" (Булево)
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(ИмяРеквизитаПрефикс + "_Суммировать", Новый ОписаниеТипов("Булево"), , "Суммировать"));
// Реквизит для нового названия (Строка)
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(ИмяРеквизитаПрефикс + "_НовоеИмя", Новый ОписаниеТипов("Строка"), , "Новое название"));
// Реквизит для типа значения (ОписаниеТипов)
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(ИмяРеквизитаПрефикс + "_ТипДанных", Новый ОписаниеТипов("ОписаниеТипов"), , "Тип значения"));
КонецЦикла;
// Применяем изменения структуры реквизитов формы
ЭтаФорма.ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);
Шаг 2. Программное построение структуры интерфейса.
После того как реквизиты созданы, нам необходимо вывести их на форму. Чтобы визуально сымитировать таблицу без использования элемента ТаблицаФормы, разработчики используют вложенные группы элементов.
Создается внешняя горизонтальная группа (которая выступает контейнером для всей конструкции). Внутри нее создаются четыре вертикальные группы, которые имитируют колонки таблицы. Внутри каждой вертикальной группы программно создаются поля ввода и флажки:
// Создаем общую горизонтальную группу-контейнер
ГруппаКонтейнер = Элементы.Добавить("ГруппаКонтейнерКолонок", Тип("ГруппаФормы"), ЭтаФорма);
ГруппаКонтейнер.Вид = ВидГруппыФормы.ОбычнаяГруппа;
ГруппаКонтейнер.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Горизонтальная;
ГруппаКонтейнер.ОтображатьЗаголовок = Ложь;
// Создаем вертикальную группу для первой колонки (Флажок + Имя)
ГруппаКолонка1 = Элементы.Добавить("ГруппаКолонка_Выводить", Тип("ГруппаФормы"), ГруппаКонтейнер);
ГруппаКолонка1.Вид = ВидГруппыФормы.ОбычнаяГруппа;
ГруппаКолонка1.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Вертикальная;
ГруппаКолонка1.Заголовок = "Выводимые колонки";
// Создаем вертикальную группу для второй колонки (Суммировать)
ГруппаКолонка2 = Элементы.Добавить("ГруппаКолонка_Суммировать", Тип("ГруппаФормы"), ГруппаКонтейнер);
ГруппаКолонка2.Вид = ВидГруппыФормы.ОбычнаяГруппа;
ГруппаКолонка2.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Вертикальная;
ГруппаКолонка2.Заголовок = "Суммировать";
// И так далее для групп "Новые названия" и "Тип значения"...
Шаг 3. Добавление элементов управления в группы.
Для заполнения первой группы (выводимые колонки) добавляются элементы с видом ПолеФлажка. При этом, чтобы вывести название колонки рядом с чекбоксом, свойство ПоложениеЗаголовка устанавливается в значение Лево (или Право), а в заголовок передается само имя колонки из Excel:
Для Каждого ИмяКолонки Из МассивИменКолонок Цикл
ИмяРеквизитаПрефикс = "КолонкаExcel_" + СтрЗаменить(ИмяКолонки, " ", "_");
// Добавляем флажок выбора колонки
ИмяЭлементаФлаг = "Поле_" + ИмяРеквизитаПрефикс + "_Выводить";
НовоеПолеФлаг = Элементы.Добавить(ИмяЭлементаФлаг, Тип("ПолеФормы"), ГруппаКолонка1);
НовоеПолеФлаг.Вид = ВидПоляФормы.ПолеФлажка;
НовоеПолеФлаг.ПутьКДанным = ИмяРеквизитаПрефикс + "_Выводить";
НовоеПолеФлаг.ПоложениеЗаголовка = ПоложениеЗаголовкаЭлементаФормы.Право;
НовоеПолеФлаг.Заголовок = ИмяКолонки; // Имя колонки из Excel отображается как подпись к флажку
// Добавляем флажок суммирования во вторую вертикальную группу
ИмяЭлементаСумма = "Поле_" + ИмяРеквизитаПрефикс + "_Суммировать";
НовоеПолеСумма = Элементы.Добавить(ИмяЭлементаСумма, Тип("ПолеФормы"), ГруппаКолонка2);
НовоеПолеСумма.Вид = ВидПоляФормы.ПолеФлажка;
НовоеПолеСумма.ПутьКДанным = ИмяРеквизитаПрефикс + "_Суммировать";
НовоеПолеСумма.ПоложениеЗаголовка = ПоложениеЗаголовкаЭлементаФормы.Нет; // Без текста
КонецЦикла;
---
Хотя визуально такой интерфейс выглядит привычно и аккуратно, данный метод реализации имеет целый ряд критических недостатков, которые делают его неприемлемым в реальной промышленной разработке:
ИзменитьРеквизиты() является крайне «тяжелой» для сервера 1С. Если пользователь выберет файл Excel, содержащий более 30–50 колонок, время генерации формы и перерисовки интерфейса на стороне клиента может составить от нескольких секунд до десятков секунд. Особенно сильно это проявляется при работе через тонкий клиент на медленных каналах связи или в веб-клиенте.ЭтаФорма[ИмяДинамическогоРеквизита].---
Чтобы избежать всех вышеперечисленных проблем, для сопоставления колонок всегда следует использовать стандартный элемент управления платформы — Таблицу значений на форме.
Рассмотрим по шагам, как настроить и запрограммировать этот механизм.
Шаг 1. Проектирование реквизита формы в конфигураторе.
В режиме конфигуратора добавим новый реквизит формы с типом ТаблицаЗначений. Назовем его, например, ТаблицаНастройкиКолонок. Внутри этой таблицы создадим фиксированный набор колонок:
Использовать (тип Булево) — отвечает за флаг загрузки.ИмяКолонкиExcel (тип Строка) — содержит исходное название колонки.Суммировать (тип Булево) — признак суммирования.НовоеНаименование (тип Строка) — новое название колонки.ТипДанных (тип ОписаниеТипов) — выбранный тип данных.Перетащим этот реквизит в дерево элементов формы, чтобы платформа автоматически создала элемент управления Таблица.
Шаг 2. Как настроить визуальное объединение флажка и названия?
Для того чтобы получить интерфейс, где флажок и имя колонки выглядят как единый элемент (флажок с текстом слева или справа), выполним настройку элементов в дереве формы:
ГруппаВыводимыеКолонки.Группировка в значение В одной строке (горизонтальная группировка). Свойство Отображать заголовок установим в значение Да и укажем текст: «Выводимые колонки».Использовать и ИмяКолонкиExcel.Использовать (это наше поле флажка) снимем флаг ПоложениеЗаголовка (установим в значение Нет) и зададим минимальную ширину (например, 2 или 3 символа), чтобы убрать лишние отступы.ИмяКолонкиExcel установим свойство Вид в значение ПолеНадписи (или оставим ПолеВвода, но сделаем его доступным только для чтения), а также отключим заголовок.Благодаря такой группировке, для конечного пользователя чекбокс и текст названия колонки визуально сольются в единый элемент управления, при этом строки таблицы никогда не «поплывут», а интерфейс будет работать молниеносно.
Шаг 3. Программный код заполнения таблицы при выборе файла.
Теперь нам достаточно написать простой серверный обработчик, который будет считывать заголовки файла Excel и наполнять строки нашей таблицы значений:
&НаСервере
Процедура ЗаполнитьНастройкиКолонокИзФайлаНаСервере(ПутьКФайлу)
// Очищаем предыдущие настройки
Объект.ТаблицаНастройкиКолонок.Очистить();
// Для чтения структуры файла Excel воспользуемся универсальным объектом ТабличныйДокумент.
// Это избавляет нас от необходимости установки MS Excel на сервере 1С.
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
ВызватьИсключение "Не удалось прочитать файл! Ошибка: " + ОписаниеОшибки();
КонецПопытки;
// Считаем, что первая строка файла содержит заголовки колонок
КоличествоКолонок = ТабДок.ШиринаТаблицы;
Если КоличествоКолонок = 0 Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Файл Excel пуст или имеет некорректный формат!");
Возврат;
КонецЕсли;
Для НомерКолонки = 1 По КоличествоКолонок Цикл
ИмяКолонки = СокрЛП(ТабДок.Область(1, НомерКолонки).Текст);
// Игнорируем совсем пустые колонки без заголовков
Если ПустаяСтрока(ИмяКолонки) Тогда
ИмяКолонки = "Колонка_" + НомерКолонки;
КонецЕсли;
// Наполняем строки таблицы настроек на форме
НоваяСтрока = Объект.ТаблицаНастройкиКолонок.Добавить();
НоваяСтрока.Использовать = Истина; // По умолчанию выбираем все колонки
НоваяСтрока.ИмяКолонкиExcel = ИмяКолонки;
НоваяСтрока.Суммировать = Ложь;
НоваяСтрока.НовоеНаименование = ИмяКолонки; // Предлагаем то же имя
НоваяСтрока.ТипДанных = Новый ОписаниеТипов("Строка"); // Дефолтный тип данных
КонецЦикла;
КонецПроцедуры
---
Если конфигурация, в которой вы ведете разработку, построена на базе БСП (а это практически все современные типовые и отраслевые конфигурации 1С), то писать собственный механизм сопоставления колонок с нуля не имеет практического смысла.
В составе Библиотеки Стандартных Подсистем присутствует замечательная подсистема «Загрузка данных из файла». Давайте проанализируем преимущества ее использования:
.xlsx и .csv без использования тяжеловесных COM-объектов Windows. Это гарантирует бесперебойную работу даже на серверах под управлением ОС Linux.Для интеграции этого механизма разработчику достаточно лишь описать правила сопоставления в модуле менеджера загружаемого объекта (например, справочника или документа) и вызвать стандартную обработку БСП с помощью программного интерфейса (API).
---
Подводя итоги, сопоставим рассмотренные методы разработки интерфейса: