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