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