Как открыть управляемую форму на тонком клиенте из фонового процесса или общего модуля 1С?

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

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

В данной статье мы подробно разберем, почему возникает популярная ошибка Переменная не определена (Элементы), выясним правила разделения контекста и рассмотрим пошаговые инструкции по реализации надежного механизма оповещения.

Анализ архитектурной ошибки: почему код автора не работал?

Разберем ситуацию, представленную в обсуждении. Автор пытается реализовать периодическую проверку актуальности заметок с помощью обработчика ожидания и сталкивается со следующими проблемами:

  1. Попытка открыть форму с сервера. Внутри функции общего модуля (выполняющейся на сервере) автор хочет вызывать визуальную форму списка. Серверный контекст платформы «1С:Предприятие» полностью лишен интерфейсной части. На сервере нет понятий «экран», «окно», «форма» или «диалог». Метод ОткрытьФорму является методом глобального контекста, доступным только на клиенте (в тонком, толстом или веб-клиенте).
  2. Обращение к локальному контексту формы из общего модуля. В коде динамического построения таблицы (Сообщение 11) автор использует конструкции ЭтаФорма.ИзменитьРеквизиты и Элементы.Добавить. Системные переменные ЭтаФорма и Элементы доступны только внутри модуля самой управляемой формы. В общем модуле (даже если он компилируется на клиенте) этих переменных не существует, так как они принадлежат конкретному экземпляру формы. Попытка обратиться к ним напрямую вызывает ошибку компиляции: Переменная не определена (Элементы).

Для исправления этой ситуации нам необходимо строго разделить бизнес-логику (получение данных из БД на сервере) и интерфейсную логику (обработку результата и открытие формы на клиенте).

Решение 1. Правильный шаблон реализации клиент-серверного взаимодействия

Рассмотрим правильный паттерн проектирования данной задачи. Мы разделим процесс на три этапа:

Выполним реализацию по шагам.

Шаг 1. Создание серверной функции в общем модуле

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


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

Шаг 2. Настройка обработчика ожидания на клиенте

Перейдем в модуль управляемого приложения (или модуль конкретной формы, если оповещения должны работать только в ней). Инициализируем обработчик ожидания, который будет срабатывать, например, каждые 60 секунд:


Процедура ПриНачалеРаботыСистемы()
    
    // Подключаем обработчик ожидания. 
    // "ПроверитьАктуальныеЗаметки" - имя процедуры, которая должна быть объявлена как Экспорт
    // 60 - интервал в секундах
    // Ложь - обработчик будет выполняться многократно
    ПодключитьОбработчикОжидания("ПроверитьАктуальныеЗаметки", 60, Ложь);
    
КонецПроцедуры

Шаг 3. Реализация клиентского обработчика ожидания

В этом же клиентском модуле опишем экспортную процедуру, которая будет получать данные с сервера и открывать форму в случае обнаружения записей:


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

Этот подход полностью безопасен, соответствует стандартам разработки фирмы «1С» и исключает любые ошибки контекста.

Решение 2. Как правильно изменять форму динамически (программное создание элементов)

Если ваша задача действительно требует динамического формирования интерфейса (как пытался сделать автор в Сообщении 11), запомните главное правило: любое изменение структуры формы должно выполняться в контексте самой этой формы, а не в общем модуле.

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

  1. Создайте пустую управляемую форму (например, вспомогательную форму в обработке или регистре).
  2. В модуле этой формы опишите обработчик события ПриСозданииНаСервере.
  3. Перенесите туда логику создания реквизитов и элементов интерфейса.

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


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

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


// В общем модуле (Сервер)
Процедура ИнициализироватьТаблицуФормы(ПолучательФорма, ТаблицаДанных) Экспорт
    
    // Обращаемся к коллекции элементов переданной формы
    ЭлементТаблица = ПолучательФорма.Элементы.Добавить("Таблица", Тип("ТаблицаФормы"));
    // Дальнейшая логика аналогична...
    
КонецПроцедуры

Альтернативный вариант: Использование подсистемы БСП «Напоминания пользователя»

Разработка собственной системы периодических проверок и всплывающих окон оправдана только в самописных конфигурациях «с нуля». Если вы работаете в любой типовой конфигурации фирмы «1С» (например, 1С:ERP, 1С:Комплексная автоматизация, 1С:Управление торговлей, 1С:УНФ), настоятельно рекомендуется использовать стандартную подсистему «Напоминания пользователя» из состава Библиотеки стандартных подсистем (БСП).

Преимущества использования БСП вместо собственных обработчиков ожидания:

Рассмотрим пример программного создания напоминания средствами БСП:


// Пример создания напоминания для пользователя по конкретному договору
ПараметрыНапоминания = Новый Структура;
ПараметрыНапоминания.Вставить("Пользователь", Пользователи.ТекущийПользователь());
ПараметрыНапоминания.Вставить("ВремяСобытия", ТекущаяДата() + 3600); // Напомнить через 1 час
ПараметрыНапоминания.Вставить("СпособУстановкиВремени", Перечисления.СпособыУстановкиВремениНапоминания.В ОдноВремя);
ПараметрыНапоминания.Вставить("Описание", "Проверить условия договора!");
ПараметрыНапоминания.Вставить("Источник", СсылкаНаДоговор); // Ссылка на объект базы данных

НапоминанияПользователяСлужебный.СоздатьНапоминание(ПараметрыНапоминания);

Используя данный метод, вы избавите себя от необходимости писать громоздкие обработчики ожидания и динамически перестраивать формы вручную.

← На главную