При разработке конфигураций на платформе 1С версии 8.1 или при работе с Обычными формами в 8.3 программисты часто сталкиваются с ограничением: реквизиту табличной части (ТЧ) нельзя напрямую назначить тип СписокЗначений или ТаблицаЗначений. Однако бизнес-задачи часто требуют возможности хранить в одной строке наборы данных, например, перечень дат визитов, список дополнительных характеристик или массив идентификаторов. Разберем подробно, как обойти это ограничение, используя ХранилищеЗначений, и как правильно организовать пользовательский интерфейс для работы с такими данными.
Это наиболее универсальный способ, который позволяет упаковать любой объект (включая СписокЗначений) в бинарные данные и сохранить его в базе данных. Проанализируем алгоритм реализации этого метода по шагам.
Во-первых, в конфигураторе для нужной табличной части документа или справочника добавим новый реквизит. Назовем его, например, ДополнительныеДатыХранилище. В поле «Тип» выберем ХранилищеЗначений. Важно понимать, что данные в этом поле нельзя будет просмотреть через стандартную консоль запросов в читаемом виде, так как они сериализованы.
Во-вторых, на форме документа добавим колонку, связанную с этим реквизитом. Однако, так как ХранилищеЗначений не имеет стандартного редактора, нам потребуется программное вмешательство. Установим у элемента формы (поля ввода) свойство КнопкаВыбора = Истина и свойство РежимВыбораИзСписка = Ложь.
Рассмотрим код обработчика события НачалоВыбора для этой колонки. Нам нужно извлечь список из хранилища, дать пользователю его отредактировать и сохранить обратно:
Процедура ТоварыДополнительныеДатыНачалоВыбора(Элемент, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь; // Отключаем стандартный поиск значения
ТекущаяСтрока = ЭлементыФормы.Товары.ТекущиеДанные;
// 1. Извлекаем список из хранилища
СписокДат = ТекущаяСтрока.ДополнительныеДатыХранилище.Получить();
// Если в ячейке еще ничего нет, инициализируем новый список
Если СписокДат = Неопределено Тогда
СписокДат = Новый СписокЗначений;
СписокДат.ТипЗначения = Новый ОписаниеТипов("Дата");
КонецЕсли;
// 2. Вызываем стандартное окно редактирования списка
Если СписокДат.ВыбратьЭлемент("Выберите даты") <> Неопределено Тогда
// 3. Если пользователь нажал ОК, упаковываем обратно
ТекущаяСтрока.ДополнительныеДатыХранилище = Новый ХранилищеЗначений(СписокДат);
// Помечаем документ как измененный
ЭтаФорма.Модифицированность = Истина;
КонецЕсли;
КонецПроцедуры
Проблема ХранилищаЗначений в том, что в ячейке таблицы пользователь будет видеть надпись "Хранилище значений" или пустоту. Это неудобно. Чтобы сделать интерфейс дружелюбным, применим технику «виртуальной колонки». Выясним, как это реализовать.
ПредставлениеДат с типом Строка. Эта колонка не должна быть связана с данными объекта (не ставим «Данные»).ПриВыводеСтроки. В этом событии мы будем динамически формировать текст для отображения.Посмотрим на пример реализации формирования текста:
Процедура ТоварыПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
// Пытаемся получить данные из бинарного реквизита
Список = ДанныеСтроки.ДополнительныеДатыХранилище.Получить();
Если ТипЗнч(Список) = Тип("СписокЗначений") И Список.Количество() > 0 Тогда
Текст = "";
Для Каждого ЭлементСписки Из Список Цикл
Текст = Текст + Формат(ЭлементСписки.Значение, "ДФ=dd.MM.yyyy") + "; ";
КонецЦикла;
ОформлениеСтроки.Ячейки.ПредставлениеДат.УстановитьТекст(Текст);
Иначе
ОформлениеСтроки.Ячейки.ПредставлениеДат.УстановитьТекст("... нажмите для ввода ...");
КонецЕсли;
КонецПроцедуры
Важный нюанс: Обработка ПриВыводеСтроки вызывается часто, поэтому внутри неё не рекомендуется выполнять тяжелые запросы. Но метод Получить() для небольших списков работает достаточно быстро.
Если вам не нравится работать с бинарными данными, можно использовать строковый реквизит неограниченной длины. В 1С есть встроенные механизмы сериализации объектов в системную строку. Разберем, как использовать функции ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр.
Этот способ удобен тем, что данные можно увидеть "глазами" в отладчике. Однако помните, что формат внутренней строки 1С специфичен и его не стоит пытаться парсить вручную. Работа с ним выглядит так:
// При сохранении:
ТекущаяСтрока.СтроковыйРеквизит = ЗначениеВСтрокуВнутр(СписокДат);
// При чтении:
СписокДат = ЗначениеИзСтрокиВнутр(ТекущаяСтрока.СтроковыйРеквизит);
Проанализируем ситуацию: если список значений в каждой строке будет содержать сотни или тысячи записей, использование ХранилищаЗначений приведет к деградации производительности. В таких случаях правильно использовать вторую табличную часть в том же документе.
Например, у нас есть ТЧ Товары и ТЧ ДатыПоставок. В ТЧ ДатыПоставок мы добавляем реквизит КлючСвязи (число), который соответствует индексу или уникальному идентификатору строки из ТЧ Товары.
Преимущества такого подхода очевидны:
Рассмотрим, как получить эти данные для вывода в макет. Поскольку данные хранятся в каждой строке, нам необходимо организовать цикл по основной табличной части и внутри каждой итерации извлекать список.
Процедура Печать(ТабДок)
// ... инициализация макета ...
Для Каждого СтрокаТовар Из СсылкаНаОбъект.Товары Цикл
Область = Макет.ПолучитьОбласть("Строка");
Область.Параметры.Наименование = СтрокаТовар.Номенклатура;
// Извлекаем список дат
СписокДат = СтрокаТовар.ДополнительныеДатыХранилище.Получить();
Если СписокДат <> Неопределено Тогда
// Превращаем список в строку для печати
СтрокаДат = СписокДат.ВыгрузитьЗначения().НайтиСтроки(...); // Или через цикл
Область.Параметры.Даты = СтрСоединить(СписокДат.ВыгрузитьЗначения(), ", ");
КонецЕсли;
ТабДок.Вывести(Область);
КонецЦикла;
КонецПроцедуры
Подведем итог: для реализации "списка в строке" на платформе 8.1 наиболее эффективным является использование ХранилищаЗначений в сочетании с программным вызовом метода ВыбратьЭлемент(). Это сохраняет чистоту структуры данных (не плодятся лишние таблицы) и обеспечивает интуитивно понятный интерфейс для пользователя.