В процессе разработки сложных интерфейсов, например, при создании обработок сопоставления данных, часто возникает задача реализации связи типа «один ко многим». Рассмотрим типичную ситуацию: на форме есть основная таблица (мастер-таблица) и подчиненная таблица (детальная). При выборе строки в первой таблице вторая должна отображать данные, относящиеся именно к этой строке, причем пользователь может редактировать эти данные, и они должны сохраняться при переключении между строками основной таблицы.
Проанализируем ситуацию и разберем несколько подходов к решению этой задачи: от стандартных механизмов платформы до более гибких программных методов.
Это наиболее правильный и производительный метод с точки зрения архитектуры 1С. Вместо того чтобы физически очищать и заново заполнять вторую таблицу при каждом клике, мы будем хранить все данные в одной большой коллекции, но показывать пользователю только нужные фрагменты.
Разберем реализацию по шагам:
ТЧ1) и в подчиненную таблицу (ТЧ2) добавим колонку КлючСвязи. Тип этой колонки лучше всего выбрать УникальныйИдентификатор или Число.ТЧ1 программно генерируем новый ключ. Если используем число — берем максимальный индекс, если UUID — используем функцию Новый УникальныйИдентификатор().ОтборСтрок.Посмотрим на пример кода, который необходимо разместить в обработчике события ПриАктивизацииСтроки основной таблицы:
&НаКлиенте
Процедура ТЧ1ПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элементы.ТЧ1.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Устанавливаем отбор в подчиненной таблице по ключу текущей строки
Элементы.ТЧ2.ОтборСтрок = Новый ФиксированнаяСтруктура("КлючСвязи", ТекущиеДанные.КлючСвязи);
КонецПроцедуры
При добавлении новой строки в подчиненную таблицу (ТЧ2) нам также нужно гарантировать, что она «привяжется» к основной строке:
&НаКлиенте
Процедура ТЧ2ПередНачаломДобавления(Элемент, Отказ, Копирование, Родитель, Группа, Параметр)
ТекущиеДанныеТЧ1 = Элементы.ТЧ1.ТекущиеДанные;
Если ТекущиеДанныеТЧ1 <> Неопределено Тогда
// Автоматически подставляем ключ из основной таблицы
Элементы.ТЧ2.ТекущиеДанные.КлючСвязи = ТекущиеДанныеТЧ1.КлючСвязи;
КонецЕсли;
КонецПроцедуры
Преимущество этого метода: Высокая скорость работы. Платформа не перерисовывает таблицу с нуля, а лишь скрывает строки, не соответствующие фильтру. Все данные физически находятся в памяти формы (в реквизите ДанныеФормыКоллекция).
Если по каким-то причинам вы не хотите раздувать подчиненную таблицу всеми записями сразу (например, если данных слишком много и они загружаются динамически), можно использовать хранение данных в отдельной колонке основной таблицы в виде массива структур.
Проанализируем этот подход. В ТЧ1 создается реквизит с типом Произвольный. При деактивации строки (или перед сменой строки) данные из ТЧ2 выгружаются в этот реквизит, а при активации новой строки — загружаются обратно.
Рассмотрим пример кода для сохранения и восстановления данных:
&НаСервере
Процедура СохранитьТЧ2ВСтрокуТЧ1(ИндексСтрокиТЧ1)
СтрокаТЧ1 = Объект.ТЧ1[ИндексСтрокиТЧ1];
// Выгружаем ТЧ2 в таблицу значений, затем преобразуем в массив структур
ТЗ = Объект.ТЧ2.Выгрузить();
МассивДанных = Новый Массив;
Для Каждого СтрТЗ Из ТЗ Цикл
СтруктураСтроки = Новый Структура("Номенклатура, Количество, Цена");
ЗаполнитьЗначенияСвойств(СтруктураСтроки, СтрТЗ);
МассивДанных.Добавить(СтруктураСтроки);
КонецЦикла;
СтрокаТЧ1.ДанныеХранения = МассивДанных;
КонецПроцедуры
&НаСервере
Процедура ВосстановитьТЧ2ИзСтрокиТЧ1(ТекущаяСтрока)
Объект.ТЧ2.Очистить();
Если ТипЗнч(ТекущаяСтрока.ДанныеХранения) = Тип("Массив") Тогда
Для Каждого СтруктураИзХранилища Из ТекущаяСтрока.ДанныеХранения Цикл
НоваяСтр = Объект.ТЧ2.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтр, СтруктураИзХранилища);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Важный нюанс: На управляемых формах передача больших объемов данных в реквизите с типом Произвольный может существенно замедлить работу, так как этот контекст постоянно пересылается между клиентом и сервером. Используйте этот метод с осторожностью.
Выясним, как оптимизировать передачу данных, если подчиненные таблицы очень объемные. Вместо того чтобы хранить данные непосредственно в строке таблицы, мы можем поместить их во ВременноеХранилище, а в строке ТЧ1 хранить только адрес (строку).
Разберем алгоритм работы:
ТЧ2 мы вызываем серверный метод.ТЧ2 в ТаблицуЗначений.ПоместитьВоВременноеХранилище(ТЗ, УникальныйИдентификаторФормы).АдресДанных текущей строки ТЧ1.Почему это удобно? Данные в хранилище живут столько же, сколько открыта форма, и не нагружают основной контекст сообщения при обмене между клиентом и сервером.
Проанализируем техническую особенность: событие ПриАктивизацииСтроки срабатывает крайне часто — при каждом перемещении курсора, при обновлении списка и даже при потере фокуса формой. Если внутри этого события прописана тяжелая логика (например, запросы к БД или полная перезагрузка больших таблиц), интерфейс начнет «тормозить».
Рекомендуется использовать обработчик ожидания. Это позволит программе дождаться, пока пользователь закончит листать список, и выполнить загрузку данных только для финальной строки. Рассмотрим, как это реализовать:
&НаКлиенте
Процедура ТЧ1ПриАктивизацииСтроки(Элемент)
// Отключаем предыдущий вызов, если пользователь нажал стрелку быстро
ОтключитьОбработчикОжидания("ВыполнитьОбновлениеПодчиненнойТаблицы");
// Запускаем обработчик через 0.2 секунды
ПодключитьОбработчикОжидания("ВыполнитьОбновлениеПодчиненнойТаблицы", 0.2, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьОбновлениеПодчиненнойТаблицы()
// Здесь вызываем логику фильтрации или загрузки данных
ОбновитьОтображениеТЧ2();
КонецПроцедуры
Выбор метода зависит от ваших задач:
ЗначениеВСтрокуВнутр() для сохранения таблиц в ячейки, так как это устаревший метод, который создает нечитаемые и тяжелые строки, плохо подходящие для управляемого приложения.Применяя эти подходы, вы сможете создавать отзывчивые и надежные интерфейсы для работы со связанными данными в 1С.