В практике разработки на платформе 1С:Предприятие 8.3 часто возникает ситуация: на управляемой форме расположена табличная часть документа, данные которой требуется пересчитать в общем модуле. Для этого табличная часть выгружается в объект ТаблицаЗначений, передается на сервер или в общий модуль для сложной обработки, а затем загружается обратно с помощью метода Загрузить(). В этот момент фокус активной строки неожиданно сбрасывается, и курсор перемещается на самую первую строку таблицы. Разберем, почему это происходит и как правильно сохранить позиционирование пользователя.
Проанализируем внутреннее поведение платформы при работе с реквизитами формы типа ДанныеФормыКоллекция. Каждая строка на клиенте идентифицируется специальным числовым идентификатором, который возвращает метод ПолучитьИдентификатор(). Свойство таблицы формы ТекущаяСтрока хранит именно этот числовой идентификатор.
Когда вызывается метод Загрузить() или метод Очистить(), коллекция строк пересоздается заново. Старые числовые идентификаторы безвозвратно уничтожаются, а вновь созданным строкам присваиваются новые идентификаторы. В результате элемент управления теряет связь с прежним идентификатором и автоматически переводит выделение на первую строку. Пользователю приходится вручную искать строку, с которой он только что работал.
Многие разработчики ошибочно полагают, что управлять фокусом строки можно только на клиенте. Это заблуждение связано с тем, что свойство ТекущиеДанные действительно доступно только на клиенте. Однако само свойство элемента формы ТекущаяСтрока доступно для чтения и записи как на клиенте, так и на сервере.
Это дает возможность полностью инкапсулировать логику сохранения и восстановления фокуса внутри самой серверной процедуры пересчета. В таком случае отпадает необходимость встраивать дополнительный код во все клиентские обработчики (подбор номенклатуры, окончание редактирования строки и прочие события).
Рассмотрим наиболее элегантный и универсальный способ. Если в процессе обработки строки не удаляются и не группируются, а только пересчитываются или сортируются, позицию строки можно сохранить через саму строку таблицы значений. Дело в том, что ссылка на конкретный объект СтрокаТаблицыЗначений сохраняется даже при смене сортировки таблицы.
Разберем по шагам серверный алгоритм:
ТаблицаЗначений и сохраняем ссылку на строку ТЗ по найденному индексу.Посмотрим на пример серверной процедуры:
&НаСервере
Процедура ПересчитатьДанныеНаСервере()
// 1. Запоминаем текущую строку до выгрузки
ИдентификаторТекущейСтроки = Элементы.Материалы.ТекущаяСтрока;
СтрокаКоллекции = Объект.Материалы.НайтиПоИдентификатору(ИдентификаторТекущейСтроки);
ИндексСтроки = Неопределено;
Если СтрокаКоллекции <> Неопределено Тогда
ИндексСтроки = Объект.Материалы.Индекс(СтрокаКоллекции);
КонецЕсли;
// 2. Выгружаем данные в ТаблицуЗначений
ТЗМатериалы = Объект.Материалы.Выгрузить();
СтрокаТЗ = Неопределено;
Если ИндексСтроки <> Неопределено И ИндексСтроки < ТЗМатериалы.Количество() Тогда
СтрокаТЗ = ТЗМатериалы.Получить(ИндексСтроки);
КонецЕсли;
// 3. Вызываем расчетную процедуру общего модуля
РасчетСервер.РассчитатьСтоимость(ТЗМатериалы);
// 4. Загружаем данные обратно в коллекцию формы
Объект.Материалы.Загрузить(ТЗМатериалы);
// 5. Восстанавливаем позиционирование
Если СтрокаТЗ <> Неопределено Тогда
НовыйИндекс = ТЗМатериалы.Индекс(СтрокаТЗ);
Если НовыйИндекс <> -1 Тогда
ОбновленнаяСтрока = Объект.Материалы.Получить(НовыйИндекс);
Элементы.Материалы.ТекущаяСтрока = ОбновленнаяСтрока.ПолучитьИдентификатор();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Рассмотрим ситуацию, когда в процессе расчета состав строк может измениться: например, строки объединяются, удаляются нулевые позиции или таблица полностью переформировывается запросом. В этом случае ссылки на строки таблицы значений теряются, и восстанавливать позицию нужно по уникальному составу полей (например: Номенклатура, Характеристика, КлючСвязи или специальный уникальный идентификатор строки).
Такой поиск можно реализовать как на сервере, так и на клиенте. Посмотрим на клиентский вариант реализации, где перед серверным вызовом ключевые реквизиты помещаются в структуру, а после завершения пересчета строка находится методом НайтиСтроки():
&НаКлиенте
Перем СтруктураКлючей;
&НаКлиенте
Процедура ЗаполнитьКлючиТекущейСтроки()
СтруктураКлючей = Новый Структура;
Если Элементы.Материалы.ТекущиеДанные <> Неопределено Тогда
Ключ = Новый Структура;
Ключ.Вставить("Номенклатура", Элементы.Материалы.ТекущиеДанные.Номенклатура);
Ключ.Вставить("Характеристика", Элементы.Материалы.ТекущиеДанные.Характеристика);
Ключ.Вставить("КлючСвязи", Элементы.Материалы.ТекущиеДанные.КлючСвязи);
СтруктураКлючей.Вставить("Материалы", Ключ);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ВосстановитьПозициюСтроки()
Если СтруктураКлючей <> Неопределено И СтруктураКлючей.Свойство("Материалы") Тогда
НайденныеСтроки = Объект.Материалы.НайтиСтроки(СтруктураКлючей.Материалы);
Если НайденныеСтроки.Количество() > 0 Тогда
Элементы.Материалы.ТекущаяСтрока = НайденныеСтроки[0].ПолучитьИдентификатор();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура МатериалыПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
ЗаполнитьКлючиТекущейСтроки();
ПересчитатьДанныеНаСервере();
ВосстановитьПозициюСтроки();
КонецПроцедуры
Проанализируем альтернативный подход к проектированию пересчетов. Во многих задачах полная выгрузка в ТаблицаЗначений и обратная загрузка через Загрузить() являются избыточными. Если необходимо лишь рассчитать суммы, цены, ставки НДС или скидки, пересоздавать коллекцию строк формы вовсе не обязательно.
Вместо передачи всей таблицы на модификацию можно передавать копию данных для расчета, возвращать из общего модуля результаты (например, таблицу соответствия или массив вычисленных значений) и напрямую перезаписывать реквизиты в существующей коллекции Объект.Материалы. Когда строки обновляются точечно, идентификаторы не сбрасываются, коллекция не пересоздается, а выделенная строка гарантированно остается на своем месте без дополнительных алгоритмов восстановления.