Многие разработчики при переходе на управляемые формы платформы 1С:Предприятие 8.3 сталкиваются с неожиданным поведением интерфейса. Например, когда мы пишем простой и понятный код, который должен сначала вывести информационное сообщение, затем выполнить тяжелую процедуру на сервере, а в конце сообщить об успешном завершении, мы ожидаем увидеть последовательное обновление экрана. Однако на практике пользователь видит белый экран или «зависшее» окно, а все сообщения и изменения заголовков на форме отображаются одновременно только после того, как сервер завершит свою работу. Давайте подробно разберем, почему так происходит и как правильно организовать взаимодействие с пользователем по современным стандартам 1С.
Для того чтобы понять природу этого поведения, нам нужно проанализировать, как устроен тонкий клиент 1С. В отличие от обычных форм, где использовался прямой доступ к Win32 API и операционная система могла принудительно перерисовывать отдельные окна прямо во время выполнения тяжелых циклов, управляемый интерфейс 1С спроектирован по веб-технологиям.
Главный принцип работы управляемого приложения — однопоточность выполнения интерфейсных задач (Event Loop). Это означает следующее:
Сообщить() или свойства элементов формы (такие как Заголовок) не обновляют экран мгновенно. Платформа помещает эти изменения в специальную очередь (буфер) отрисовки.Именно поэтому, если мы пишем код синхронно, пользователь не увидит изменения заголовка или начальное сообщение до тех пор, пока серверная процедура не вернет управление на клиент и клиентский метод не завершит работу.
Разберем по шагам, как обойти эту проблему для относительно быстрых операций (которые длятся до 2–3 секунд), когда нам нужно просто успеть обновить форму или вывести сообщение на экран до ухода на сервер.
Идея метода заключается в том, чтобы разделить одну клиентскую процедуру на две части, дав платформе возможность вернуть управление в цикл событий на долю секунды. За это время интерфейс успеет обновиться.
Рассмотрим пример реализации:
&НаКлиенте
Процедура ЗагрузитьНаКлиенте()
// Шаг 1: Меняем элементы формы и выводим сообщение
Сообщить("Начало загрузки данных...");
Элементы.СтатусЗагрузки.Заголовок = "Подготовка к загрузке...";
// Шаг 2: Вместо прямого вызова сервера подключаем разовый обработчик ожидания.
// Интервал 0.1 секунды достаточен, чтобы поток освободился и перерисовал экран.
ПодключитьОбработчикОжидания("ВыполнитьЗагрузкуНаСервереБезБлокировки", 0.1, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьЗагрузкуНаСервереБезБлокировки()
// Шаг 3: Теперь, когда экран обновился и пользователь видит текст,
// мы можем спокойно уходить на сервер.
ЗагрузитьНаСервере();
// Шаг 4: Выводим финальную информацию
Сообщить("Загрузка успешно завершена!");
Элементы.СтатусЗагрузки.Заголовок = "Готово.";
КонецПроцедуры
Этот способ прост в реализации и отлично подходит для небольших задач, визуально устраняя эффект зависания программы перед началом работы.
Если серверная процедура выполняется дольше нескольких секунд, синхронный вызов использовать категорически не рекомендуется. В этом случае браузер или тонкий клиент могут выдать предупреждение о зависании процесса, а пользователь потеряет контроль над интерфейсом.
Правильный архитектурный подход заключается в запуске процедуры в фоновом задании на сервере. При этом на клиенте мы открываем специализированную форму ожидания и периодически опрашиваем состояние фонового задания, забирая из него новые сообщения. Для упрощения этой задачи в Библиотеке стандартных подсистем (БСП) есть готовый механизм длительных операций.
Проанализируем шаблон интеграции длительной операции через БСП:
&НаКлиенте
Процедура НачатьЗагрузкуБСП()
// Настраиваем параметры выполнения длительной операции
ПараметрыВыполнения = ДлительныеОперацииКлиент.ПараметрыВыполнения(ЭтотОбъект);
ПараметрыВыполнения.ВыводитьПрогрессВыполнения = Истина;
ПараметрыВыполнения.ТекстПредупреждения = "Пожалуйста, подождите, выполняется обработка данных...";
// Описываем процедуру, которая запустится на сервере в фоновом задании.
ИмяЭкспортнойПроцедуры = "МойСерверныйМодуль.ВыполнитьДлительнуюЗагрузку";
ПараметрыПроцедуры = Новый Структура;
ПараметрыПроцедуры.Вставить("ИмяФайла", "C:\import.xml");
// Запускаем фоновое задание
РезультатОперации = ДлительныеОперацииКлиент.ВыполнитьВФоне(
ИмяЭкспортнойПроцедуры,
ПараметрыПроцедуры,
ПараметрыВыполнения
);
// Регистрируем оповещение, которое сработает после завершения фонового задания
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыполненияДлительнойОперации", ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(РезультатОперации, ОписаниеОповещения);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыполненияДлительнойОперации(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат; // Операция была отменена пользователем
КонецЕсли;
Если Результат.Статус = "Выполнено" Тогда
ПоказатьОповещениеПользователя("Завершено", , "Все данные успешно загружены!");
ИначеЕсли Результат.Статус = "Ошибка" Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(Результат.КраткоеПредставлениеОшибки);
КонецЕсли;
КонецПроцедуры
Использование фоновых заданий позволяет не только разблокировать интерфейс, но и выводить пользователю динамический прогресс-бар, транслируя сообщения с сервера на клиент в режиме реального времени.
Состояние()Если вам нужно оперативно информировать пользователя прямо в процессе выполнения какого-то клиентского цикла (без ухода на сервер), стандартный метод Сообщить() использовать неудобно, так как он засоряет панель сообщений. Вместо этого в платформе существует встроенный метод Состояние().
Этот метод выводит текст во всплывающем окне или в строке состояния платформы. Давайте посмотрим на пример его использования внутри тяжелого клиентского цикла:
&НаКлиенте
Процедура ОбработатьМассивНаКлиенте(МассивДанных)
ВсегоЭлементов = МассивДанных.Количество();
Для Счетчик = 0 По ВсегоЭлементов - 1 Цикл
// Выполняем полезную работу на клиенте
ОбработатьЭлемент(МассивДанных[Счетчик]);
// Динамически обновляем статус для пользователя
Если Счетчик % 10 = 0 Тогда // Обновляем каждые 10 элементов, чтобы не перегружать интерфейс
Процент = Округ(Счетчик / ВсегоЭлементов * 100);
Состояние("Выполняется обработка...", Процент, "Обработано " + Счетчик + " из " + ВсегоЭлементов);
КонецЕсли;
EndЦикл;
Состояние(); // Очищаем строку состояния после завершения работы
КонецПроцедуры
Важно помнить, что метод Состояние() будет плавно обновлять экран только в том случае, если внутри цикла нет синхронных вызовов сервера. Любой синхронный вызов сервера мгновенно «заморозит» отрисовку состояния до своего окончания.
ПоказатьОповещениеПользователя()Для вывода некритичных информационных сообщений разработчики часто по привычке используют модальные окна (предупреждения) или метод Сообщить(). Однако по современным стандартам разработки юзабилити 1С рекомендует применять всплывающие оповещения, которые не требуют отвлекающих кликов от пользователя.
Давайте рассмотрим, как правильно использовать этот метод:
&НаКлиенте
Процедура ЗавершитьПроцесс()
// Всплывающее уведомление в углу экрана, которое исчезнет само через несколько секунд
ПоказатьОповещениеПользователя(
"Процесс завершен",
ПолучитьНавигационнуюСсылку(ДокументСсылка),
"Документ успешно проведен!",
БиблиотекаКартинок.Информация
);
КонецПроцедуры
Такой подход делает работу пользователя с программой комфортной, быстрой и соответствующей гайдлайнам интерфейса «Такси».
Разработчикам на платформе 1С 8.3 крайне важно отказаться от старых привычек программирования в стиле обычных форм. Понимание асинхронной модели работы управляемого интерфейса позволяет создавать быстрые, отзывчивые и удобные приложения. Помните три простых правила:
ПодключитьОбработчикОжидания().