Под капотом управляемых форм

Опубликовано Павел Путков 12.07.2018 0 Комментарии

Введение

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

Немного истории

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

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

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

И все бы ничего, но до версии 8.2 и появления управляемого приложения реализация серверного кода была совсем необязательной частью. Существовал ряд проблем, объективных и субъективных, которые мешали использовать сервер для обработки бизнес-логики. Код на сервере выполнялся, но в очень редких случаях, когда его специально, не без помощи костылей, заставляли там выполняться.

В основном, все оставалось по-старому, и клиентское приложение так и оставалось «толстым» клиентом, гоняющим сотни мегабайт по сети.

Версия 8.2 кардинально сломала устоявшийся подход к работе приложения и заставила по-новому взглянуть на архитектуру приложения. Об этом новом подходе и пойдет речь в данной статье.

Волшебный зверь Клиент-сервер

С выходом версии 8.0 все учебные курсы, книжки, статьи на ИТС и прочее, и прочее, все в один голос твердили нам – «обработка данных должна идти на сервере», «все нужно выполнять на сервере». Волшебное заклинание «на сервере» плотно забило мозг, так сильно, что на него уже никто внимания не обращал, как на информационный шум.

Опыт показывает, что рядовой программист практически никогда не знал, как нужно перенести код на сервер и что это вообще значит – «на сервер». Удивительно часто встречался код, который переносился «на сервер», но был написан с явным непониманием того, что это в итоге принесет.

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

Однако многие уже опытные разработчики запросто писали такой код:

  • - Выбирается локальный файл
  • - Его путь передается на сервер
  • - Там по этому пути файл пытается открыться и падает с ошибкой «Файл не найден» (еще бы, он же остался на клиенте).

Переход на сервер

До версии 8.2 перенести исполнение кода на сервер можно было только одним способом – а именно, вызовом общего модуля с флагом «Сервер». Причем, только с этим флагом. Если помимо флага «сервер» поставить «клиент», то перехода не произойдет. По этому поводу на Инфостарте не так давно была неплохая статья про флажки в свойствах общих модулей (к сожалению, не могу найти ссылку, подскажете?).

Суть всех флагов модуля сводится к простым правилам:

  1. Флаг показывает, где будет скомпилирован код модуля (на клиенте, на сервере, во внешнем соединении)
  2. Если модуль скомпилирован в нескольких местах, то он будет виден только в соответствии с флагами. Клиентский – на клиенте, серверный на сервере. Переноса исполнения с машины на машину не будет.
  3. Перенос исполнения кода возможен только если в текущем контексте исполнения нет вызываемого модуля, но он есть в другом месте (если – модуль есть только на сервере, а на клиенте его нет, то будет сделан вызов сервера)

Флаг «Вызов сервера»

В версии 8.2 добавился флаг «вызов сервера», который помогает разрулить условия перехода на другую машину. Если модулю назначить этот флаг, то модуль будет виден с клиента, если нет – то попытка вызова с клиента приведет к ошибке. Код модуля виден не будет, как будто его нет совсем.

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

  1. Установлен флажок «Сервер»
  2. Установлен флажок «Вызов сервера»
  3. Сняты все «клиентские» флажки

Особенности перехода на сервер

Платформа заботится о том, чтобы переход исполнения кода на другую машину был как можно более незаметным. Мы вызываем функции серверного кода, передаем туда параметры, получаем возвращаемые значения, получаем изменения параметров, передаваемых по ссылке. Все выглядит так, как будто никакого сервера нет. Однако, это не так.

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

Это объясняет, почему не все объекты можно передавать на сервер. Это все потому, что не все они поддерживают сериализацию (превращение в строку и обратно). На сервер нельзя передать, например, «ДокументОбъект», т.к. у него в модуле объекта могут быть глобальные переменные, а в них записаны какие-нибудь сложные несериализуемые значения, вроде COM-объектов… Короче, ограничения по обмену с сервером понятны и обоснованны. Нужно следить за тем, что мы передаем на сервер и что возвращаем назад.

Ну и, разумеется, на сервере недоступны некоторые интерфейсные вещи, вроде открытия форм. Открытые на сервере формы просто будет некому увидеть, некому тыкнуть в них мышкой, поскольку, пользователь остался сидеть у другого компьютера.

Мне кажется, что простота реализации серверного вызова сыграла с платформой 1С злую шутку. Серверный вызов настолько прозрачен, что его сложно заметить, сложно осознать, что вот здесь, с этого момента, мы находимся на совсем другом компьютере. В результате и получается такой код, о котором я написал выше.

Отсутствие контекста (состояния) на сервере

Существует такая модель серверных приложений, когда сервер только отвечает на запросы, но ничего не сохраняет у себя в промежутке между двумя запросами. Эта модель называется моделью «без состояния» (англ. state-less).

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

Сервер 1С работает по такому же принципу. В общем случае, в памяти сервера нельзя сохранить промежуточные данные между вызовами. Нельзя определить переменную, которая будет хранить данные между двумя вызовами.

Есть специальные объекты, в которых можно хранить очень незначительную часть данных – так называемые «параметры сеанса», но это именно специально выделенное место для хранения состояния. Каждый серверный вызов выполняет некоторую работу, возвращает результат и забывает все свои данные. В управляемом режиме про это всегда необходимо помнить.

Управляемое приложение

С выходом управляемого приложения фирма «1С» поступила с клиент-серверным взаимодействием, на мой взгляд, совершенно правильно. Она не стала его прятать, наоборот, она заставила всех видеть его, тыкнула всех носом: «смотрите, тут вы пишете код для физически разных машин, думайте, что делаете».

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

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

Тонкий клиент

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

Такое облегчение не далось бесплатно. Очень многие привычные операции стали выполняться сложнее. Упомянутая выше сериализация никуда, разумеется, не делась, а данные, которые нельзя было передавать на сервер, так и нельзя туда передавать.

Все это привело к особенностям, с которыми мы сталкиваемся при разработке управляемых форм. Ключевая особенность, в общем-то, всего одна – форма полностью отделена от данных объекта.

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

Тонкий клиент стал поистине «клиентом». Он больше не обрабатывает данные, он их показывает и позволяет редактировать. Всю обработку мы теперь явно переносим на сервер.

Устройство управляемой формы

Давайте рассмотрим, из каких частей состоит управляемая форма.

Самая важная часть в управляемой форме, это ее реквизиты. Те, синенькие, что расположены в панели справа вверху. Это, собственно, данные формы, ее основа. Только то, что указано в реквизитах можно разместить на форме.

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

Данные формы

Есть старая уже хохма проПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокументИмениНуралиеваБорисаГеоргиевича.

Управляемая форма добавляет очередного претендента на звание длинного и непонятного идентификатора: ДанныеФормыСтруктураСКоллекцией и еще ряд новых классов с именами вида «ДанныеФормы….что-то там». Давайте попробуем выяснить, что это за новые объекты.

Как уже говорилось выше, сервер предприятия не сохраняет свое состояние между двумя вызовами. Когда мы обратимся к серверу, он что-то выполнит для нас и тут же очистит все данные, созданные во время вызова. Объекты с именами «ДанныеФормы…» это как раз то место, в котором мы можем сохранять свои данные между двумя серверными вызовами. На сервере нельзя создать глобальную переменную уровня формы. Такой глобальной переменной должен быть реквизит в данных формы.

Обратите внимание, что классы данных формы любопытно называются «ДанныеФормыСтруктура» или «ДанныеФормыКоллекция». Это вообще, как мне кажется, неспроста. Эти объекты – это просто немного модифицированная стандартная Структура (или ТаблицаЗначений, соответственно).

Здесь стоит немного вспомнить обычные формы. Ведь как мы привыкли – В форме справочника достаточно присвоить переменной «Наименование» какой-то текст и этот текст попадет в СправочникОбъект, а оттуда – в информационную базу.

У управляемой форме все почти так, за исключением того, что все совсем не так :).Платформа предпринимает массу усилий, чтобы в повседневной работе программиста ему не приходилось с этим заморачиваться. Как правило, мы, как и раньше, присваиваем свойства реквизитам формы, а они попадают в СправочникОбъект, а оттуда – в ИБ.

Разница заключается в том, что теперь на клиенте не существует «СправочникОбъект». Как я уже говорил, это сложный объект, он не сериализуется, а значит, его нельзя передавать с клиента на сервер. Вместо «Объекта» на клиенте существует его упрощенный аналог – хранилище данных – объект ДанныеФормы.

Это, по сути, просто структура, у которой свойства совпадают со свойством объекта. При всей «похожести», это совсем разные объекты. Причем, не просто концептуально, это различие явное, его можно «пощупать» из кода и с этим различием приходится работать в явном виде.

Адресация данных формы

Данные формы доступны непосредственно в коде по тем именам, как они расположены в дереве реквизитов формы (в окне редактора)

Реквизиты верхнего уровня доступны сразу. Вложенные реквизиты доступны через точку. Раньше в обычной форме мы обращались к данным объекта напрямую. Мы писали «Наименование = «АААААА» и данные попадали в объект. Теперь надо писать «Объект.Наименование = «ААААА», потому, что реквизит «Наименование» вложен внутрь реквизита «Объект».

Обмен формы с сервером

Основным «двигателем» всех аспектов управляемой формы является то, что у нас state-less сервер. На нем не сохраняется ничего, никаких промежуточных данных. Всё состояние формы (текст в полях ввода, строки табличных частей и т.п.) хранится на клиенте. Вся совокупность данных формы называется контекстом формы.

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

Жизненный цикл формы объекта

Давайте рассмотрим, что происходит, когда мы открываем форму существующего элемента справочника.

  1. На клиенте вызывается метод «ОткрытьФорму» или мы просто открываем форму из какого-либо списка справочника. Начинается серверный вызов
  2. В памяти сервера создается новый СправочникОбъект, выполняется код модуля объекта (тот, что написан в самом низу модуля)
  3. Данные объекта считываются из базы данных, присваиваются значения реквизитов, наполняются табличные части объекта СправочникОбъект.
  4. В памяти сервера создается новая управляемая форма элемента
  5. Вызывается событие формы ПриЧтенииНаСервере, куда передается свежесозданный СправочникОбъект в параметре ТекущийОбъект
  6. Основной реквизит формы «Объект», тот, что в списке реквизитов выделен жирным шрифтом наполняется данными на основании данных СправочникОбъект. Здесь происходит обычное поэлементное присваивание свойствам объекта ДанныеФормы значений, записанных в одноименных свойствах объекта СправочникОбъект. По сути, происходит «ЗаполнитьЗначенияСвойств(ДанныеФормы, СправочникОбъект)
  7. СправочникОбъект уничтожается. Все его данные стираются из памяти сервера.
  8. Вызывается событие формы «ПриСозданииНаСервере», в котором мы получаем уже заполненные ДанныеФормы
  9. Данные формы сериализуются и отправляются на клиента, где форма отображается пользователю

Обратите внимание, что СправочникОбъект, к которому мы так привыкли в обычном приложении, уничтожился. Если в модуле объекта были какие-либо глобальные переменные, экспортные или не экспортные, все они стали потеряны.

Сохранить что-либо в данных объекта больше нельзя, все сохранение состояния должно выполняться в ДанныхФормы.

Теперь, давайте рассмотрим процесс записи данных справочника:

  1. Пользователь нажимает кнопку «Записать»
  2. Выполняется обработчик формы «ПередЗаписью» (на клиенте)
  3. Происходит серверный вызов
  4. В памяти сервера создается новый СправочникОбъект, он наполняется данными на основании данных текущей формы (что-то вроде ЗаполнитьЗначенияСвойств, но в обратном направлении – пишется из формы в объект)
  5. Вызывается обработчик события формы «ПередЗаписьюНаСервере», куда передается свежесозданный СправочникОбъект. С этого момента, если мы хотим что-то записать в базу данных, то менять нужно именно СправочникОбъект. Изменения данных формы не отразятся в записываемом объекте.
  6. Вызывается обработчик «ПередЗаписью» самого СправочникОбъект (в модуле объекта)
  7. Вызывается обработчик «ПриЗаписи» самого СправочникОбъект (в модуле объекта)
  8. Вызывается обработчик события формы «ПриЗаписиНаСервере», транзакция записи еще не завершена и можно отменить запись
  9. Вызывается обработчик события формы «ПослеЗаписиНаСервере», транзакция записи уже завершена
  10. СправочникОбъект уничтожается
  11. Выполняется возврат на клиента, где вызывается обработчик события формы «ПослеЗаписи»

Обратите внимание, серверное состояние восстанавливается с нуля, каждый раз создается новый СправочникОбъект. В промежутках между вызовами сервера он не сохраняется.

Конвертация данных формы

Для упрощения жизни существуют простые способы преобразования универсальных объектов «ДанныеФормы….» в привычные прикладные объекты и наоборот.

Так, например, можно получить наполненный данными СправочникОбъект на основании данных формы, если вызвать метод «РеквизитФормыВЗначение(«Объект»). При этом произойдет то, что описано выше – будет создан новый СправочникОбъект и наполнен данными из указанного реквизита данных формы.

Обратное преобразование выполняется методом «ЗначениеВРеквизитФормы». Если мы произвели какие-то действия с данными СправочникОбъект, например, очистили табличную часть, то мы должны поместить наши изменения обратно в форму, иначе будет что?

Правильно, измененный СправочникОбъект будет уничтожен при возврате на клиента, а данные формы останутся неизменными. Цикл записи объекта будет построен на старых данных формы, а стало быть, изменение табличной части просто пропадет.

Не все так плохо ;)

Все эти сложности с преобразованием данных полезно знать, однако, они совсем не обязательны. Платформа прекрасно справляется с ними сама. В большинстве случаев достаточно никогда не вспоминать про СправочникОбъект, а работать непосредственно с данными формы. Грязную работу платформа сделает сама.

Команды формы

Про команды формы много говорить не получится, это довольно простая вещь. Команда олицетворяет некоторое действие, которое может запустить пользователь. Подобно тому, как поля ввода и флажки привязываются к реквизитам формы, кнопки и гиперссылки привязываются к командам. Каждая кнопка обязательно ссылается на какую-либо команду.

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

Каждая команда может быть размещена в командной панели, подменю, или на самой форме. Создаются команды на закладке «Команды» в редакторе формы.

Параметры формы

Параметры, это специальные переменные, которые можно передать в форму в момент ее открытия. Параметры задаются на закладке «Параметры» редактора формы и о них речь пойдет чуть ниже.

Управление открытием формы

Отображение форм на экране в управляемом режиме также претерпело некоторые изменения. Прежде всего, формы получили имена, по которым к ним можно обращаться. Например, форма списка справочника  «Контрагенты» будет называться «Справочник.Контрагенты1.ФормаСписка».

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

Поскольку открытие формы это интерактивная операция, то осуществляться она может только на клиенте. Однако, создание формы, как мы видели из ее жизненного цикла, происходит на сервере. Возникает резонный вопрос, а как открыть форму одной строчкой кода, если нужно каким-то образомпараметризовать ее создание, передать ей что-то, чтобы она знала, как именно ей нужно создаваться?

Для этого и нужны Параметры формы. Это структура, которую можно передать в метод «ОткрытьФорму». Переданные параметры можно проанализировать в серверном коде формы и предпринять какие-то действия на основании переданных параметров.

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

УсловияОтбора = Новый Структура;
УсловияОтбора.Вставить("ТипКонтрагента", "Поставщик");
УсловияОтбора.Вставить("Лояльность", "Надежный");
ОткрытьФорму("Справочник.Контрагенты.ФормаСписка", УсловияОтбора);

Существуют системные параметры, на которые реагирует сама платформа. Упомянутый параметр «Отбор» как раз является системным, если он передан форме списка, то платформа сама установит отбор на список.

Кроме того, параметры можно создавать произвольные, в редакторе формы. Более того, параметры можно передавать даже с теми именами, которые не объявлены в форме, они все равно будут видны в серверном коде формы.

Жизненный цикл параметров

Все параметры, переданные в форму в момент ее открытия видны только в процедуре «ПриСозданииНаСервере». После создания все параметры уничтожаются и более не доступны в форме.

Исключение составляют параметры, которые в редакторе формы объявлены с признаком «Ключевой параметр». Такой параметр будет существовать до тех пор, пока существует сама форма.

Процедура ПриСозданииНаСервере(Отказ)

    Если Параметры.ЗапуститьЯдерныйРеакторПриОткрытии Тогда
        УправлениеРеактором.Запустить();
    КонецЕсли;

КонецПроцедуры

 

Ключевые параметры определяют уникальность окна. В обычных формах был такой атрибут «Ключ уникальности». Если запрашивалась форма, то она сначала искалась в уже открытых. Ключ уникальности позволял варьировать условия при которых создавалась новая форма или возвращалась существующая. Ту же самую роль играют ключевые параметры. Если метод «ОткрытьФорму» вызван два раза подряд с одним и тем же значением ключевого параметра, то второй вызов не откроет новое окно, а активирует существующее.

Модуль формы

Управляемый интерфейс добавил несколько интересных вещей в модуль формы. Все эти вещи обеспечивают программирование клиент-серверного взаимодействия в явном виде. Я имею в видудирективы компиляции.

Справедливости ради стоит отметить, что они доступны не только в модулях форм, но, как правило, используются только там. Директива компиляции это строчка вида &НаКлиенте перед объявлением процедуры.

Для модуля форм возможны следующие директивы:

  • &НаКлиенте
  • &НаСервере
  • &НаСервереБезКонтекста
  • &НаКлиентеНаСервереБезКонтекста

Директива указывает компилятору, где нужно скомпилировать указанную процедуру. С директивами «НаКлиенте» и «НаСервере» все понятно, а вот с добавкой «БезКонтекста» нужно разобраться подробнее.

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