Как правильно заполнить табличную часть документа через форму внешней обработки в 1С 8.3 УФ?

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия IT и автоматизация бизнеса
← На главную

Разберем классическую задачу, с которой часто сталкиваются разработчики при создании дополнительных обработок заполнения табличных частей (ЗТЧ) для управляемых форм «1С:Предприятие 8.3». Нам необходимо предоставить пользователю возможность интерактивно настроить параметры заполнения на форме обработки (например, выбрать счета учета, указать конкретный склад или установить фильтры), после чего заполнить табличную часть исходного документа и сразу отобразить изменения на экране.

В старых версиях платформы и конфигураций разработчики часто использовали обход открытых окон через метод ПолучитьОкна() с последующим поиском нужной формы по строке заголовка. Однако, начиная с платформы 8.3.7 (и особенно в интерфейсе «Такси»), этот метод стал работать крайне нестабильно: заголовки окон генерируются динамически, а сами формы могут не регистрироваться в коллекции окон до их полной активации. Выясним причину этой проблемы и рассмотрим наиболее надежные, современные способы заполнения данных с использованием контекста формы-владельца.

В чем причина сбоя при поиске окон через ПолучитьОкна()?

Проанализируем типичный фрагмент кода, который часто перестает работать после очередного обновления платформы:


Окна = ПолучитьОкна();
Для каждого Окно из Окна Цикл
    Если НЕ Окно.Основное И Найти(Окно.Заголовок, ЭлементОбъектовНазначения) Тогда
        Форма = ОткрытьФорму("Документ.ТребованиеНакладная.Форма.ФормаДокумента", КлючПоиска, , , Окно);
        // Заполнение...
    КонецЕсли;
КонецЦикла;

Этот код пытается найти вспомогательное окно, содержащее представление ссылки документа в своем заголовке. Этот подход имеет сразу три критических недостатка:

  1. Изменение структуры заголовков: В новых версиях платформы формат заголовков окон управляемого приложения регулярно оптимизируется. Поиск по подстроке Найти(Окно.Заголовок, ...) легко ломается, если платформа добавляет к заголовку имя приложения, статус модифицированности или режим работы базы данных (например, "[Копия]").
  2. Ограничения веб-клиента: В веб-клиенте работа с окнами через коллекцию ПолучитьОкна() имеет жесткие ограничения безопасности браузера, что делает обработку неработоспособной для пользователей, работающих через браузер или тонкий клиент через веб-сервер.
  3. Проблемы модальности: Блокирующие окна и формы, открытые в режиме разделения интерфейса, могут вести себя непредсказуемо при попытке получить их коллекцию на клиенте.

Правильное решение №1: Использование механизма ВладелецФормы

При вызове внешней обработки заполнения с типом команды «ОткрытиеФормы» Библиотека стандартных подсистем (БСП) автоматически передает форму изменяемого документа в качестве владельца открываемой формы обработки. Это означает, что нам вообще не нужно искать окно документа! Мы можем напрямую обратиться к форме документа через системное свойство ЭтаФорма.ВладелецФормы (или просто ВладелецФормы).

Рассмотрим, как будет выглядеть корректный вызов заполнения на клиенте в модуле формы нашей обработки:


&НаКлиенте
Процедура ЗаполнитьДокумент()
    
    // Проверяем, что форма открыта из контекста другого документа
    Если ВладелецФормы = Неопределено Тогда
        ВызватьИсключение "Обработка может быть вызвана только из формы документа!";
    КонецЕсли;
    
    // Получаем данные документа напрямую через владельца
    ОбъектДокумента = ВладелецФормы.Объект;
    
    // Вызываем серверный метод заполнения, передавая туда структуру данных
    ЗаполнитьДокументНаСервере(ОбъектДокумента);
    
    // Переносим измененные данные обратно в форму документа
    КопироватьДанныеФормы(ОбъектДокумента, ВладелецФормы.Объект);
    
    // Устанавливаем признак того, что документ был изменен пользователем
    ВладелецФормы.Модифицированность = Истина;
    
    // Закрываем форму настроек нашей обработки
    Закрыть();
    
КонецПроцедуры

Правильное решение №2: Борьба с отсутствием обновления интерфейса

Иногда разработчики сталкиваются с ситуацией, когда данные в ВладелецФормы.Объект успешно скопированы, но на экране пользователя табличная часть документа остается пустой. Это происходит из-за того, что платформа не всегда автоматически инициирует перерисовку связанных элементов управления при программном изменении данных формы.

Чтобы гарантировать обновление отображения данных на клиенте, используем следующие приемы:

  1. Обновление отображения всей формы: Вызов системного метода ОбновитьОтображениеДанных() заставляет платформу перечитать состояние всех связанных реквизитов на активном экране.
  2. Адресное обновление таблицы: Если на форме документа табличная часть выведена в виде конкретного элемента (например, таблица с именем "Материалы"), мы можем принудительно обновить именно этот элемент управления через коллекцию Элементы формы-владельца.

Посмотрим на пример завершения заполнения с принудительным обновлением таблицы:


&НаКлиенте
Процедура ЗаполнитьДокументЗавершение()
    
    // Копируем рассчитанные на сервере данные в объект владельца
    КопироватьДанныеФормы(ЗаказКлиентаОбъект, ВладелецФормы.Object);
    
    // Включаем признак модифицированности формы владельца
    ВладелецФормы.Модифицированность = Истина;
    
    // Принудительно обновляем конкретную таблицу на форме документа
    Если ВладелецФормы.Элементы.Найти("Товары") <> Неопределено Тогда
        ВладелецФормы.Элементы.Товары.Обновить();
    КонецЕсли;
    
    // Общий сброс кэша отображения для всей формы
    ОбновитьОтображениеДанных();
    
КонецПроцедуры

Полный практический пример реализации внешней обработки

Разберем по шагам создание готового шаблона внешней обработки заполнения табличной части документа "Требование-накладная" с возможностью выбора счетов на форме настроек.

Шаг 1. Модуль объекта внешней обработки

В модуле объекта описываем процедуру регистрации нашей обработки в системе по стандартам БСП:


Функция СведенияОВнешнейОбработке() Экспорт
    
    МассивНазначений = Новый Массив;
    МассивНазначений.Добавить("Документ.ТребованиеНакладная");
    
    ПараметрыРегистрации = Новый Структура;
    ПараметрыРегистрации.Вставить("Вид", "ЗаполнениеОбъекта");
    ПараметрыРегистрации.Вставить("Назначение", МассивНазначений);
    ПараметрыРегистрации.Вставить("Наименование", "Заполнить материалы по остаткам с выбором счетов");
    ПараметрыРегистрации.Вставить("Версия", "1.1");
    ПараметрыРегистрации.Вставить("БезопасныйРежим", Истина);
    ПараметрыРегистрации.Вставить("Информация", "Заполнение табличной части документа остатками ТМЦ с возможностью указания счетов.");
    
    // Создаем таблицу команд
    ТаблицаКоманд = ПолучитьТаблицуКоманд();
    
    // Добавляем команду с типом запуска "ОткрытиеФормы"
    НоваяСтрока = ТаблицаКоманд.Добавить();
    НоваяСтрока.Представление = "Заполнить по остаткам (с фильтром счетов)...";
    НоваяСтрока.Идентификатор = "ЗаполнитьТЧОстатками";
    НоваяСтрока.Использование = "ОткрытиеФормы"; // Важно! Позволяет открыть нашу форму параметров
    НоваяСтрока.ПоказыватьОповещение = Истина;
    
    ПараметрыРегистрации.Вставить("Коменты", ТаблицаКоманд); // В БСП используется ключ "Команды", будьте внимательны
    ПараметрыРегистрации.Вставить("Команды", ТаблицаКоманд);
    
    Возврат ПараметрыРегистрации;
    
КонецФункции

Функция ПолучитьТаблицуКоманд()
    Команды = Новый ТаблицаЗначений;
    Команды.Колонки.Добавить("Представление", Новый ОписаниеТипов("Строка"));
    Команды.Колонки.Добавить("Идентификатор", Новый ОписаниеТипов("Строка"));
    Команды.Колонки.Добавить("Использование", Новый ОписаниеТипов("Строка"));
    Команды.Колонки.Добавить("ПоказыватьОповещение", Новый ОписаниеТипов("Булево"));
    Команды.Колонки.Добавить("Модификатор", Новый ОписаниеТипов("Строка"));
    Возврат Команды;
EndФункции

Шаг 2. Модуль формы внешней обработки

В форме обработки размещаем список счетов (например, список значений с пометками) и кнопку "Заполнить". Код модуля формы будет выглядеть следующим образом:


&НаКлиенте
Процедура ПриОткрытии(Отказ)
    
    // Проверяем наличие владельца формы при инициализации
    Если ВладелецФормы = Неопределено Тогда
        ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Данную обработку нельзя запускать напрямую. Откройте её из документа!");
        Отказ = Истина;
    КонецЕсли;
    
КонецПроцедуры

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

&НаСервере
Функция ЗаполнитьОстаткиНаСервере(Знач Склад, Знач Организация, Знач ДатаОстатков)
    
    // Формируем список счетов, выбранных пользователем на форме нашей обработки
    СписокСчетов = Новый Массив;
    Для Каждого ЭлементСписка Из СчетаУчета Цикл
        Если ЭлементСписка.Пометка Тогда
            СписокСчетов.Добавить(ЭлементСписка.Значение);
        КонецЕсли;
    КонецЦикла;
    
    Если СписокСчетов.Количество() = 0 Тогда
        // Если ничего не выбрано, выберем счета по умолчанию (например, 10-й счет)
        СписокСчетов.Добавить(ПланыСчетов.Хозрасчетный.Материалы);
    КонецЕсли;
    
    // Строим запрос к регистру бухгалтерии для получения актуальных остатков ТМЦ
    Запрос = Новый Запрос;
    Запрос.УстановитьПараметр("Склад", Склад);
    Запрос.УстановитьПараметр("СписокСчетов", СписокСчетов);
    Запрос.УстановитьПараметр("ДатаОстатков", Новый Граница(ДатаОстатков, ВидГраницы.Включая));
    Запрос.УстановитьПараметр("Организация", Организация);
    
    Запрос.Текст = 
    "ВЫБРАТЬ
    |   ХозрасчетныйОстатки.Субконто1 КАК Номенклатура,
    |   ХозрасчетныйОстатки.Счет КАК Счет,
    |   ХозрасчетныйОстатки.КоличествоОстаток КАК Количество
    |ИЗ
    |   РегистрБухгалтерии.Хозрасчетный.Остатки(&ДатаОстатков, Счет В ИЕРАРХИИ (&СписокСчетов), , Организация = &Организация) КАК ХозрасчетныйОстатки
    |ГДЕ
    |   ХозрасчетныйОстатки.Субконто3 = &Склад";
    
    Возврат Запрос.Выделать().Выгрузить();
    
КонецФункции

Использование связки свойств ВладелецФормы.Объект и метода КопироватьДанныеФормы гарантирует абсолютную стабильность решения на любых версиях платформы 1С:Предприятие 8.3, защищая логику заполнения от любых изменений во внутреннем оконном менеджере платформы.

← На главную