Управляемые формы - это интерфейсная часть Управляемого приложения, предназначенная главным образом для отображения на Клиенте данных, которые находятся на Сервере, поэтому эти данные, когда ими оперируют в форме на Клиенте несколько отличаются от того, как ими оперируют на Сервере.Управляемое приложение пришло на смену Обычному приложению, в котором программный код исполнялся в однородной среде, контролирующей одновременно интерфейс и данные приложения, что не позволяло реализовать эффективную удаленную и распределенную работу большого числа пользователей одновременно.
Эта публикация посвящена нюансам программного управления клиентским интерфейсом на управляемых формах.
Клиент-серверная архитектура
Первое и главное: Управляемые формы существуют в Управляемом приложении, реализованном в клиент-серверной архитектуре, при которой все данные и программный код разделяются между средой клиента и средой сервера даже в тех случаях, когда это происходит на одном физическом компьютере. Кроме того, выполнение запросов к базе данных выполняется в особой среде на сервере, поэтому в целом существует три среды, каждая из которых имеет собственные существенные отличия:
- На Клиенте
- На Сервере
- В Запросе
Диалоговые окна и Управляемые формы
Поскольку управляемые формы создавались для работы не только в оболочке 1С:Предприятие, но и в браузере, окно которого нельзя монопольно захватывать, то диалоговые окна и старый стиль разработки интерфейса, рассчитанный на использование диалогов, в управляемыми формами не приветствуется. Так, использование процедуры Сообщить() будет работать только в оболочке 1С:Предприятие, при этом напоминая о некорректности. В управляемых формах вместо процедуры Сообщить() следует использовать новую простую конструкцию:
1 2 3 4 5 |
Сообщение = Новый СообщениеПользователю(); Сообщение.Текст = "Мы больше не используем вызовы модальных окон"; Сообщение.Поле = "КнопкаКомандыЗавершить"; Сообщение.УстановитьДанные(ОбъектДанных); Сообщение.Сообщить(); |
И говоря о сообщениях, можно вскользь упомянуть функцию НСтр(), которая несколько упрощает задачу локализации сообщений:
1 |
НСтр("ua = 'Привіт, світ!'; ru = 'Привет, мир!'; en = 'Hello, world!'"); |
.
Простые конструкции
Проверка значений реквизитов формы
Проверка отсутствия значения в реквизите в модуле формы
1 2 3 4 5 |
Если ПустаяСтрока(РеквизитСтрока) Тогда // пустая строка Если РеквизитДата = Дата('00010101') Тогда // пустое значение даты Если Реквизит = Неопределено Тогда // отсутствие значения и типа для Произвольный Если РеквизитСсылка.Пустая() Тогда // пустая ссылка прикладного типа Если ЗначениеЗаполнено(Объект.Реквизит) Тогда // отсутствие значения любого типа |
Перечисления
Перечисления полноценно доступны только &НаСервере в коллекции объектов метаданных Перечисления, которое недоступно &НаКлиенте и в запросе непосредственно, а только через
1 2 3 4 5 6 |
&НаСервере Вид = Перечисления.Виды.Основной; &НаКлиенте Если Вид = ПредопределенноеЗначение("Перечисление.Виды.Основной") Тогда //в Запросе |ГДЕ Вид = ЗНАЧЕНИЕ(Перечисление.Виды.Основной) |
///
Изменения внесенные в данные формы программно не контролируются формой, поэтому для управления сохранением внесенных изменений необходимо устанавливать свойство Модифицированность формы
1 2 3 |
&НаКлиенте ... ЭтаФорма.Модифицированность = Истина; |
Формы
В конфигураторе набор отображаемых свойств формы существенно зависит от реквизита с установленным свойством Основной реквизит.
Передача данных управляемой формы между Сервером и Клиентом
Ограничения при передаче данных в управляемую форму
Подготовка данных необходимых форме при открытии как правило выполняется стандартным средствами сервера и проблем не вызывает. Но во время работы с формой, ей может понадобиться получить дополнительные данные с сервера, что реализуется серверным вызовом функции, которая подготовит данные на Сервере и вернет их на Клиент. Однако часто прикладной объект полученный на Сервере не удается вернуть на Клиент, т.к. это вызывает ошибку XDTO при попытке преобразовать данные неприменимые на Клиенте (например каскадная связь объектов справочника через реквизит .Родитель). Для обхода ошибки не следует возвращать на Клиент прикладные объект, следует создать и возвращать специально подготовленную структуру, в которой источник ошибки преобразования XDTO будет исключен:
1 2 3 4 5 6 7 8 9 |
&НаСервере Функция ПолучитьОбъектРеквизита(Реквизит) // код приводящий к ошибке Возврат Реквизит.ПолучитьОбъект(); // Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа. // по причине: Ошибка преобразования данных XDTO:... // рабочий код Возврат Новый Структура("Ссылка,Код,Наименование", Реквизит.Ссылка, Реквизит.Код, Реквизит.Наименование); |
Оптимизация ресурсоемкости передачи данных между Сервером и Клиентом
Когда форма выполняет серверный вызов процедуры &НаСервере, все данные формы упаковываются и передаются на Сервер, а в сложной форме таких данных может быть много, и это приводит к затратам ресурсов и Клиента, и Сервера. Для снижения затрат ресурсов при серверных вызовах везде, где это возможно, следует использовать процедуры &НаСервереБезКонтекста и параметры передаваемые по значению.
В обычном случае реквизит формы сложного типа обрабатывается на Сервере только после преобразования в значение, а затем значение необходимо преобразовать обратно к реквизиту. Выполняются эти преобразования доступными только &НаСервере процедурами:
- РеквизитФормыВЗначение()
- ЗначениеВРеквизитФормы(),
При оптимизации серверного вызова для выполнения процедур &НаСервереБезКонтекста используется другая пара процедур:
- ДанныеФормыВзначение()
- ЗначениеВДанныеФормы().
Важно учесть, что выполнять возврат из серверного вызова допустимо не для любого реквизита формы, для Диаграммы это допустимо, а для ТаблицыЗначений, ДереваЗначений нет и приведет к ошибке "Нельзя изменять поле, содержащее объект данных формы". В таких случаях для серверного вызова в параметре придется использовать копию реквизита, а после вызова для обновления реквизита придется воспользоваться функцией КопироватьДанныеФормы().
1 2 3 4 5 6 7 8 9 10 11 12 |
&НаКлиенте Процедура КомандаЗаполнитьРеквизитДерево(Команда) ПараметрДерево = РеквизитДерево; КомандаЗаполнитьРеквизитДеревоНаСервереБезКонтекста(Объект.Ссылка, ПараметрДерево, РеквизитДиаграмма); КопироватьДанныеФормы(ДеревоПараметр, РеквизитДерево); ... &НаСервереБезКонтекста Процедура КомандаЗаполнитьРеквизитДеревоНаСервереБезКонтекста(Знач ОбъектСсылка, ПараметрДерево, РеквизитДиаграмма) НовоеДерево = Новый ДеревоЗначений; ... ЗначениеВДанныеФормы(НовоеДерево, ПараметрДерево); КонецФункции |
///
Динамический список
Реквизит типа Динамический список используется в подавляющем числе форм отображающих прикладные объекты данных конфигурации, поскольку этот тип позволяет реквизиту установить свойство Основной реквизит, что существенно влияет на свойства и работу формы.
Режим отображения иерархии в динамическом списке
Пример кода открытия формы с установкой необходимого режима отображения списка:
1 2 3 |
ФормаВыбора = ПолучитьФорму("Справочник.Номенклатура.ФормаВыбора"); ФормаВыбора.Элементы.Список.Отображение = ОтображениеТаблицы.Список; ФормаВыбора.Открыть(); |
Это замечание имеет значение не только для управляемых форм. Любая форма с динамическим списком Подчиненного справочника без отбора элементов по владельцу отображает его в режиме Список, независимо от установленного программно или в конфигураторе режима отображения (иерархия групп отображаться корректно не будет)!
Программное управление порядком сортировки динамического списка
Код установки сортировки по ДатаРеализации и Клиент:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
&НаКлиенте ... Список.Порядок.Элементы.Очистить(); // элементПорядка = Список.Порядок.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных")); элементПорядка.Поле = Новый ПолеКомпоновкиДанных("ДатаРеализации"); элементПорядка.ТипУпорядочивания = НаправлениеСортировкиКомпоновкиДанных.Возр; элементПорядка.РежимОтображения = РежимОтображенияЭлементаНастройкиКомпоновкиДанных.Авто; элементПорядка.Использование = Истина; // элементПорядка = Список.Порядок.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных")); элементПорядка.Поле = Новый ПолеКомпоновкиДанных("Клиент"); элементПорядка.ТипУпорядочивания = НаправлениеСортировкиКомпоновкиДанных.Возр; элементПорядка.РежимОтображения = РежимОтображенияЭлементаНастройкиКомпоновкиДанных.Авто; элементПорядка.Использование = Истина; // Элементы.Список.Обновить(); |
Однако если в Настройка списка на закладке Порядок установлено Включать в пользовательские настройки, то программное назначение будет подавляться пользовательскими настройками формы, и в таком случае следует использовать другой код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
&НаКлиенте ... элементыПользовательскихНастроек = Список.КомпоновщикНастроек.ПользовательскиеНастройки.Элементы; Для Каждого элемент Из элементыПользовательскихНастроек Цикл Если ТипЗнч(элемент) = ТипЗнч(Список.КомпоновщикНастроек.Настройки.Порядок) Тогда элемент.Элементы.Очистить(); // элементПорядка = элемент.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных")); элементПорядка.Поле = Новый ПолеКомпоновкиДанных("ДатаРеализации"); элементПорядка.ТипУпорядочивания = НаправлениеСортировкиКомпоновкиДанных.Возр; элементПорядка.Использование = Истина; // элементПорядка = элемент.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных")); элементПорядка.Поле = Новый ПолеКомпоновкиДанных("Клиент"); элементПорядка.ТипУпорядочивания = НаправлениеСортировкиКомпоновкиДанных.Возр; элементПорядка.Использование = Истина; // Элементы.Список.Обновить(); КонецЕсли; КонецЦикла; |
///
Отбор данных в динамическом списке
Форма может иметь несколько реквизитов типа Динамический список, которые могут не быть основным реквизитом формы, поэтому необходимый отбор данных в них следует выполнять программно. Поскольку существует два принципиально отличных способа получения данных динамическим списком через свойство Основная таблица или из Произвольный запрос, то для каждого используется свой способ отбора
Отбор в основной таблице
Для отбора следует добавить элемент отбора в коллекцию .Отбор.Элементы, как показано на примере:
1 2 3 4 5 6 7 8 |
&НаСервере ... МойДинамическийСписок.Отбор.Элементы.Очистить(); ОтборВладелец = МойДинамическийСписок.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных")); ОтборВладелец.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Владелец"); ОтборВладелец.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно; ОтборВладелец.Использование = Истина; ОтборВладелец.ПравоеЗначение = Объект.Ссылка; |
Отбор в произвольном запросе
Отбор в произвольном запросе должен использовать параметр запроса, который передается через коллекцию .Параметры.Элементы, как показано в примере:
1 2 3 |
&НаСервере ... МойДинамическийСписок.Параметры.УстановитьЗначениеПараметра("Владелец", Объект.Ссылка); |
///
Таблица значений
Для отображения в управляемой форме табличных данных, в форме должен быть создан реквизит типа ТаблицаЗначений, который будет доступен и на Клиенте, и на Сервере, при этом функционально будет несколько отличаться.
Элементы.<элемент-таблица>.ОтборСтрок = Новый ФиксированнаяСтруктура(<строка реквизитов>, <значение реквизита>);
Важно! Строковое значение используется для поиска по подстроке
Дерево значений
Для отображения в управляемой форме иерархической структуры в форме должен быть создан реквизит типа ДеревоЗначений, который может отображаться на форме элементами типа Таблица. В списке реквизитов формы реквизит отображается типом (ДеревоЗначений) в скобках, потому что он проявляет амбивалентность. Реквизит дерева создается и обладает типом ДеревоЗначений на Сервере, но на Клиенте он имеет тип ДанныеФормыДерево, эти два типа отличаться по составу свойств, методов и типов хранимых данных.
При вызове методов формы &НаСервере xdto-преобразование реквизита от типа к типу выполняется прозрачно, но при передаче в возвращаемых параметра серверных вызовов необходимо применять преобразование функциями ЗначениеВРеквизитФормы(), ЗначениеВДанныеФормы(), РеквизитФормыВЗначение(), ДанныеФормыВЗначение().
Типы, свойства и методы для работы с ДеревоЗначений &НаКлиенте и &НаСервере
Таблица.
&НаКлиенте | &НаСервере | |
---|---|---|
Тип Дерева | ДанныеФормыДерево | ДеревоЗначений |
колонки | .Колонки | |
корневые строки | .ПолучитьЭлементы() | .Строки |
методы | Скопировать() ... |
|
Тип коллекции строк | ДанныеФормыКоллекцияЭлементовДерева | КоллекцияСтрокДереваЗначений |
Ссылка на владельца коллекции | . | .Родитель |
Изменение состава | . | .Добавить(), Вставить(), Сдвинуть(), Удалить(), Очистить() |
Управление порядком | . | .Сортировать() |
Анализ содержания | . | .Итог(), Количество() |
. | .ВыгрузитьКолонку(), ЗагрузитьКолонку() | |
. | . | |
. | . | |
. | . | |
Тип строки | ДанныеФормыЭлементДерева | СтрокаДереваЗначений |
Коллекция строк верхнего уровня | . | .Родитель |
Коллекция строк нижнего уровня | . | .Строки |
. | .Владелец() | |
Свойства строки | .ПолучитьИдентификатор()* | .Уровень()* |
Тип коллекции колонок | - | КоллекцияКолонокДереваЗначений |
. | . | |
. | . | |
. | . | |
. | . | |
Тип колонки | - | КолонкаДереваЗначений |
. | . | |
. | . | |
. | . | |
. | . | |
Тип элемента формы | - | |
. | ||
. | ||
. | ||
. |
&НаСервере
Для работы с Деревом значений на Сервере применяется более функциональная иерархия типов работы со строками и колонками:
- ДеревоЗначений
-
-
-
- Колонки
- Строки
- Скопировать()
-
-
- КоллекцияСтрокДереваЗначений
-
-
- Родитель
- Добавить(), Вставить(), Сдвинуть(), Удалить(), Очистить()
- Сортировать(),
- Итог(), Количество()
- ВыгрузитьКолонку(), ЗагрузитьКолонку()
- ...
-
- СтрокаДереваЗначений
-
- Родитель
- Строки
- Владелец()
- Уровень()
- ...
-
-
- КоллекцияКолонокДереваЗначений
-
-
- КолонкаДереваЗначений
-
-
-
Табличный документ
///
Диаграмма
!!!Для отображения в управляемой форме иерархической структуры, в форме должен быть создан реквизит типа ДеревоЗначений, который будет доступен и на Клиенте, и на Сервере, при этом функционально будет несколько отличаться.
///
Блокировка данных
///
ЗаблокироватьДанныеДляРедактирования()
ЗаблокироватьДанныеФормыДляРедактирования()
Открытие форм
Форма нового объекта с заполнением
Новый объект (справочника, документа) может быть создан в форме до того, как он будет записан в ИБ (а возможно, не будет записан вовсе). При этом форме можно передать данные для начального заполнения. Общие свойства создаваемого объекта задаются структурой ПараметрыФормы, с предопределенным набором свойств, среди которых есть свойство ЗначенияЗаполнения типа структура, задающая значения для заполнения реквизитов объекта (ключи свойств должны соответствовать именам реквизитов заполняемого объекта):
- ПараметрыФормы (Структура)
- Ключ - (ссылка) - определяемый ссылкой объект будет загружен в форму для изменения
- ЗакрыватьПриВыборе - (Булево) - в форме списка выбор в списке завершится закрытием формы списка
- ЗакрыватьПриЗакрытииВладельца (Булево) - если форма А владелец формы Б, то при закрытии А, закроется и Б.
- ЗначениеКопирования (ссылка) - объект, копию которого мы хотим открыть. копия будет не сохранена.
- ЗначенияЗаполнения (Структура) - Это мы отобразили в самом начале. Структура с данными заполнения
- Код, Наименование, ... - реквизиты объекта
- Владелец (ссылка) - на объект владельца
- Родитель (ссылка) - на объект родителя
ЭтоГруппа- признак группы заданный в значениях заполнения игнорируется и должен быть определено на уровень выше
- Основание (ссылка) - объект основание
- ПараметрыВыбора - Содержит значения, по которым проверяется возможность выбора для форм, находящихся в режиме выбора.
- РежимВыбора (Булево) - Истина, открывает форму в режиме выбора.
- ЭтоГруппа (Булево) - признак создания формой объекта группы справочника
Пример открытия формы создания группы в справочнике Модели, следующей за некоторой выбранной:
1 2 3 4 5 6 7 8 9 |
&НаКлиенте ... ЗначенияЗаполнения = Новый Структура( "Владелец, Родитель, РеквизитДопУпорядочивания", ТекущиеДанные.Владелец, ТекущиеДанные.Родитель, РеквизитДопУпорядочивания + 1); ПараметрыФормы = Новый Структура( "ЗначенияЗаполнения, ЭтоГруппа", ЗначенияЗаполнения, Истина); ОткрытьФорму("Справочник.Модели.ФормаГруппы", ПараметрыФормы); ... |
Для заполнения реквизитов объекта в модуле объекта необходимо создать предопределенную процедуру ОбработкаЗаполнения(). Для всех типов объектов эта процедура действует сходно, а для документов отличается отсутствием параметра ТекстЗаполнения основного представления:
1 2 3 4 5 6 7 8 9 |
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка) Если ДанныеЗаполнения = Неопределено Тогда Возврат; ИначеЕсли ТипЗнч(ДанныеЗаполнения) = Тип("Структура") Тогда ЗаполнитьЗначенияСвойств(ЭтотОбъект, ДанныеЗаполнения); ИначеЕсли Метаданные().ВводитсяНаОсновании.Содержит(ДанныеЗаполнения.Метаданные()) Тогда ЗаполнитьПоОснованию(ДанныеЗаполнения); КонецЕсли; КонецПроцедуры |
Процедура ОбработкаЗаполнения() выполняется в том числе при вызове метода <объект>.Заполнить()
Для заполнения табличной части в структуру ЗначенияЗаполнения можно добавить свойство с таблицей значений, которая будет доступна в обработчике ОбработкаЗаполнения модуля объекта в свойстве параметра ДанныеЗаполнения:
1 2 3 4 |
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка) ... ЭтотОбъект.МояТабличнаяЧасть.Загрузить(ДанныеЗаполнения.ТаблицаЗначенийДляЗаполнения); ... |
Следует учесть, что при вызове обработчика ОбработкаЗаполнения реквизиты объекта еще девственно чисты, поскольку он вызывается до стандартного заполнения реквизитов значениями.
Источник: Открыть форму нового объекта с заполнением //agency-sd.ru
Программное открытие внешней обработки
Приложение не работает непосредственно с локальными файлами, расположенными на компьютере. Файлы должны находиться на сервере, поэтому для открытия внешней обработки нужно выполнить следующую последовательность действий:
- Передать файл внешней обработки на сервер
- Подключить внешнюю обработку
- Открыть форму внешней обработки
1 2 3 4 5 6 7 8 9 10 11 12 |
&НаКлиенте Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды) АдресХранилища = ""; Результат = ПоместитьФайл(АдресХранилища, "C:\ВнешняяОбработка.epf", , Ложь); ИмяОбработки = ПодключитьВнешнююОбработку(АдресХранилища); ОткрытьФорму("ВнешняяОбработка."+ ИмяОбработки +".Форма"); КонецПроцедуры &НаСервере Функция ПодключитьВнешнююОбработку(АдресХранилища) Возврат ВнешниеОбработки.Подключить(АдресХранилища); КонецФункции |
По умолчанию внешние обработки запускаются в безопасном режиме исполнения программного кода. Это значит, что некоторые возможности встроенного языка будут для них недоступны. Для управления безопасностью подключения используется параметры <БезопасныйРежим> и <ЗащитаОтОпасныхДействий> метода Подключить() менеджера внешних обработок.
Источник: Как программно открыть внешнюю обработку? //its.1c.ru
Детектирование события создания, изменения, удаления !!! ЭТО НЕ ЗДЕСЬ ДОЛЖНО БЫТЬ!
Определение вида события объекта
1 2 3 4 5 6 7 |
Процедура ПередЗаписью(Отказ) Если Не ЗначениеЗаполнено(ЭтотОбъект.Ссылка) Тогда //запись нового ИначеЕсли ЭтотОбъект.Ссылка.ПометкаУдаления И Не ЭтотОбъект.ПометкаУдаления Тогда //отмена удаления ИначеЕсли ЭтотОбъект.ПометкаУдаления И Не ЭтотОбъект.Ссылка.ПометкаУдаления Тогда //удаление ИначеЕсли ЭтотОбъект.Ссылка.ПометкаУдаления И ЭтотОбъект.ПометкаУдаления Тогда //изменение удаленного Иначе //изменение ... |
Дополнительно надо сказать, что обработка события ПередУдаление(Отказ) часто не удается использовать по причине того, что она элементарно не вызывается.
Источники
- Открытие форм //its.1c.ru
- Управляемое приложение: правильное программное открытие форм //infostart.ru
- ПРОГРАММНОЕ ОТКРЫТИЕ ОБРАБОТОК В 1С //www.1s-up.ru
- 17 вопросов и ответов по СКД, проектированию форм, навигации, панелям //курсы-по-1с.рф
- 1С ДЛЯ НАЧИНАЮЩИХ //курсы-по-1с.ру
- Работа с файлами (картинками) в интерфейсе Такси //курсы-по-1с.рф/