Введение Расширяем структуру файлов надстройки и удалённая отладка Автозаполнение для API и знакомимся с контролами  Разбираем структуру (эта статья) В заключительной части данного цикла, в котором я постарался на минимальном уровне создать более-менее удобную среду для начала (!) экспериментов по изучения возможностей перевода автоматизации работы с документами из Microsoft Office в «МойОфис».  Напомню, в прошлой части мы перечислили все контролы, которые нам доступны при создании форм. В этой закончим разбор того, как это всё может функционировать (насколько я это понял, конечно), при этом переключившись на работу уже с документами. Так же снова оговорюсь, что в Lua я нуб, и учу этот язык по ходу проработки данной темы. А для понимания написанных скриптов, основы данного языка всё  надо знать, так как я надеюсь читатель знаком с ними и особо акцентироваться на его особенностях не буду! Поскольку я хотел бы немного глубже рассмотреть структуру, то стоит вернуться к некоторым общим моментам в LuaAPI «МойОфис», чтобы было более ясно, как предлагают работать над настройками.  Под запуском я понимаю не работу с интерфейсом «МойОфис», а уже непосредственно листинг в стартовом скрипте (entryform.lua). Напомню, все (!) доступные в меню "Надстройки" приложения "МойОфис Текст" для данной надстройки пользователю команды, сконцентрированы в функции getCommands(), таблицы Actions. Соответственно, отсюда понятно, что можно наделать для каждого требуемого действия отдельную команду, и запускать для её выполнения, если это нужно для команды отдельную форму (но можно так и не делать, а использовать одну но хитро, о чем скажу немного в самом конце статьи). Соответственно, сама команда запуска это Actions.ShowControls():    Опять же, напомню, frm – загруженная тем или иным способом таблица "Forms", в которой описывается и форма и логика работы данной команды (подробнее смотрите 2 часть данной серии). Сейчас нас интересует глобальный объект contex. Это объект, который является «мостом» между самим приложением и надстройкой. Как гласит руководство по  API:    Параметр context функции обработчика события определяет уровень, на котором функция обработчика события взаимодействует с приложением редактора. Возможны следующие уровни взаимодействия: • на уровне приложения (context.doWithApplication(application)); • на уровне открытого документа (context.doWithDocument(document)); • на уровне выделенного фрагмента в открытом документе (context.doWithSelection(DocumentAPI.Range или DocumentAPI.CellRange)). Разработчик должен вызвать один из методов контекста, передавая ему в качестве параметра анонимную функцию для фактической обработки события. Аргументом анонимной функции является объект, представляющий приложение (глобальный объект application), документ (глобальный объект document) или выделенный фрагмент документа (selection). Подробно как работать с контекстом, я описывать не буду (хотя и запутанно, но объяснения лучше почитать в первоисточнике), отмечу лишь свою строку в коде: frm.context=context . С её помощью передаётся контекст внутрь загруженного модуля, а значит можно организовывать логику работы уже в нём! Ах да! Чуть не забыл! Это ещё и единственный (!)из доступных разработчику способ запустить свою форму надстройки: context.showDialog(frm.dlgBegin) !  Далее, чтобы уже было о чём-то более предметно обсуждать, давайте придумаем для надстройки какую-нибудь задачу. Например, пусть надстройка создаст текстовый документ и заполнит в нём некоторую структуру. А поскольку сейчас я хочу просто немного погрузиться в некоторые моменты работы, эту структуру документа возьмём простейшую. А значит и форму нам надо создать так же простейшую. Зададим в ней пару вопросов пользователю, по поводу названия документа и названия организации (понимаю, что пример надуманный и скучный, но и задача сейчас стоит не конкретная, а учебная). Должно получиться вот такое: И на основании ответов, запишем названия  в колонтитулах и создадим нечто типа заголовка (именно «нечто», так как, оказалось, что управлять стилем написания из под надстроек мы не можем(!),что  в очередной раз вызвало моё непонимание: почему?).  На самом деле, чем дальше пытаюсь разобраться с API надстроек, тем больше складывается впечатление, что те, кто занимался написанием API автоматизации, с его реальными задачами просто не сталкивался. Вот, например, как можно было не предусмотреть загрузку файла в сам редактор документа?! То есть, загрузить в оперативную память я его могу. Могу сделать манипуляции с ним. А вот отобразить его пользователю из надстройки в программе   - нет! То есть, потом только ручками ищем такой документ  средствами  самой программы и открываем. Видимо логика такая, что большинство рабочих действий в надстройках предпринимается только к уже загруженному документу. Но ведь  это не так! Очень не малая часть работ по автоматизации, это  либо генерация новых документов, и желательно сразу с их визуальным отображением, либо перенесение информации в (или из) нескольких разных документов по определённой бизнес-логике. Например: можно заполнить в форме макроса некоторый набор данных (ФИО, даты) для оформления отпуска, с созданием по шаблонам документов: служебка на отпуск за подписью непосредственного начальника, на имя генерального директора;    в отдел кадров – заявка на отпуск  (для оформления приказа, а можно и сам приказ сразу так же сформировать);    в бухгалтерию служебку с просьбой рассчитать положенные отпускные;    Если это сделать в MS Office, то там все сформированные документы без проблем сразу же загрузятся пакетом в редактор, и потом можно их после проверок и правок либо ручками разослать, либо точно так же сформировать автоматическую  рассылку адресатам. Иначе говоря, такой  макрос позволяет сделать «своими руками» что-то типа СЭД (Системы Электронного Документооборота) пусть и простейшего уровня, но вполне себе полезный и понятный, для чего он создаётся. А вот в «МойОфис» даже если и создать такой набор документов, сперва его  надо будет загрузить вручную (каждый документ!), и только потом продолжить что-то делать с каждым из документов. Странная логика! Лично мне она не ясна.    Листинг не буду убирать под спойлер, так как в нём сегодня основная суть: Не знаю, стоит ли сильно углубляться в данном случае в подробности скрипта? "Геометрию" расположения контролов на форме я пожалуй не буду разбирать, так как по этому я проходился уже ранее, во второй части. А вот по созданию и наполнению документов, я всё таки поясню, так как есть там несколько моментов и они могут вызвать недоумение при начальном знакомством с API. Сосредоточен интересующий код  в основном в функции Frm.dlgBegin:setOnDone(), которая является обработчиком нажатия на кнопки Ok или Cancel, но конечно можно и вынести весь код в отдельную локальную функцию.     1) Как я уже отметил ранее, связь  между редактором и нашим кодом в плане работы осуществляется через специализированную объектную переменную context. Поскольку я планирую создавать новый документ, то используется следующий формат Frm.context.doWithApplication. Используется Frm.context  а не context как в руководстве, так как я работаю в отдельном модуле, и в нём context просто окажется равным nil. Именно для этого, я ранее и сохранял значение context в модуле «entryform.lua» (смотрите в выше, и подробнее в прошлых частях).    2) Создать документ (application:createDocument()), или его загрузить для обработки можно только через глобальный объект application. Как-то иначе, кроме как получить его  через функцию контекста: context.doWithApplication(application), как я понял, мы  не можем. Поэтому, если хотим что то с ним дальше сделать, то надо сохранить его значение в отдельную локальную переменную всего скрипта (или таблицы "Form") внутри анонимной функции, создаваемой для обработки doWithApplication().    3) В основе внутренней структуры документа лежит понятие блока (DocumentAPI.Block). Блок является родительским для всех остальных типов отображаемых данных, и большинство работы с документом, так или иначе, начинается с поиска или получения блоков и дочерних типов данных, от него происходящих: параграфов (DocumentAPI. Paragraph), таблиц (DocumentAPI.Table), фигур (DocumentAPI. Shape) или полей (DocumentAPI.Field)  Как видно из диаграммы, блоки с помощью соответствующих функций, можно преобразовать в дочерние типы, а так же их удалить. 4)Страницы в понятиях API «МойОфис» это секции. Немного странное название, и по моему немного нелогичное, но какое есть! У каждой секции можно с помощью таблицы свойств DocumentAPI.PageProperties установить размеры и поля (DocumentAPI.Insets) . Можно так же дополнительно отдельно задавать ориентацию страницы.  Итак, для начала работы, нужно получить секцию  (страницу) и допустим, сменить ориентацию на «ландшафтную»: Для нахождения первой страницы по логике этого API нужно найти самый первый блок и по нему найти секцию (страницу), где он расположен (doc:getBlocks() возвращает таблицу со всеми блоками документа, который мы создали, а getBlock(0)  - самый первый блок, и он уже позволяет через getSection()  найти первую страницу). Далее найденной первой странице задаём горизонтальное расположение: section:setPageOrientation(DocumentAPI.PageOrientation_Landscape)  5) Колонтитулы это отдельные области на странице, которые в свою очередь так же состоят из блоков: Туда (в верхний и нижний колонтитулы) я подставлю значения, которые пользователь вводит в полях для типа документа (верхний колонтитул) и названия организации (нижний). Интерес здесь представляет разве что функция-итератор  enumerate() с помощью которой можно перебирать блоки соответствующего типа. Такие функции имеются почти у всех коллекция разных типов блоков (и не только их), что может облегчить жизнь при разных манипуляциях (особенно поиске) с данными разных типов. 6) Любая манипуляция по добавлению информации в документ, осуществляется с помощью таблицы DocumentAPI.Position, которая в свою очередь является границами диапазона DocumentAPI.Range:    Диаграмма Почти любая информация может сперва выбираться по диапазону, и уже только потом, из этого диапазона можно дополнительно выбрать, например, только параграфы. Что достаточно удобно. Но вот дальше логика работы  опять мне не ясна: я могу получить только начало диапазона, и его конец (Range:getBegin() и Range:getBegin()), но не могу в итоге двигаться (используя что-то типа next() итераторов) по позициям внутри этого диапазона, чтобы, например, вставить в третий параграф от начала документа новый параграф. Причем вставить я могу только исключительно через Position,  что в дальнейшем важно но непонятно.  Я должен найти именно третий параграф, задать его как диапазон, встать в его начало и только потом вставить через Position:insertText() текст, который при этом вставится после третьего блока как параграф. И сюрприз-сюрприз! В отличии от например функции Position:insertTable(), которая возвращает объект вставленной таблицы (чтобы я потом мог   если мне нужно продолжить работу с ней), Position:insertText()  не возвращает ничего! То есть, хочешь, что-то поменять во вставленном параграфе – ищи заново! Причины такого поведения к основному типу текстовых данных в API для меня не ясны, от слова совсем!  Ну ладно! Будем работать как нам предлагают. Итак, вставим «типа заголовок» ибо как я уже писал, стиль заголовка, мы из API  применить не можем (снова к вопросу о том, как там  вообще автоматизацию представляют, если в ней столько "нельзя" что-то сделать, при не очень многом, что можно)! В качестве текста заголовка будет просто соединённые две строки вводимые пользователем в форме (begin_pos:insertText(nameDoc .. " от " .. nameOrg)) и расположенные просто по центру документа (para_props.alignment = DocumentAPI.Alignment_Center). Как видно, параграфы так же имеют свои наборы свойств DocumentAPI.ParagraphProperties, которые мы можем менять. Но к сожалению, для стиля в форматировании места почему-то не нашлось, хотя, в самом офисе, они явно есть:    7) Далее просто сохраняем документ Сохраняем данный файл forms.lua (или init.lua если используется загрузка через require “Forms”, см прошлые выпуски). И если всё прошло "пучком", то после всех манипуляций в форме расширения, которая должна выглядеть как показано на самом первом скриншоте этой статьи, по нажатию на кнопку "Ок" , ничего внешне не произойдёт (не по моей лени, а просто потому, что в API не предусмотрена загрузка в редактор файла документа!). Но на диске d:,в его корне, должен создаться документ NewDocument.xodt, который для проверки результата надо будет загрузить в «МойОфис Текст» вручную, и выглядеть он должен примерно вот так:    Вот такой получился простой, учебный пример. Но и он потрепал мне не мало нервов, так как честно говоря, я привык к немного другому. Работать с API можно. Есть свои особенности работы Lua,  но к ним можно вполне привыкнуть, особенно если немного изучить принципы его работы. Если всё таки придётся писать надстройки - без этого никак! Само API "сырое" и явно недоделанное. Такое ощущение, что оно лепилось наспех, и а потом  просто было заброшено, ибо "есть же!". Судя по тому, как отвечает мне тех.поддрежка в ходе работы над статьями (почти на все мои вопросы: «этого нет, но передадим в отдел разработки»), автоматизацию отложили на лучшие времена, и особо прогресса ждать не приходится. Часть из того что меня, хм, скажем - удивило, я уже написал, а сколько осталось за рамками статьи мне просто лень писать…     Визуальная сторона форм вызывает недоумение. Мне с трудом верится, что я единственный кто озадачен тем, что в текстовых полях  ввода, например, нет wordwrap. Да и вообще, походу многострочного ввод там никто не предусматривал. Нельзя изменить в контролах ни сам шрифт, ни его размер, ни его начертание. Или, почему нет простых но классических контролов "Frame", для визуального и контекстного разделения зон работы с контролами? Нет полос прокрутки. Нет спиннеров. Нет возможности вставить картинки. Но и то что есть, в ряде случаев работает не всегда корректно. Так иногда  при нажатии на кнопки, размеры других контролов вдруг меняются. Да и формы в целом, тоже ещё тот квест! Сама логика построения дизайна с помощью табличного Layout  и  агрегаторов ui:Column и ui:Row, конечно современна, но  с точки зрения построения большинства форм, требует слишком немалых усилий чтобы правильно всё расположить! Особенно при том, что нет визуального редактора для GUI, а значит вместо того, чтобы выбрать контролы, быстро их расположить на форме и привязать к обработчикам событий функции для обработки бизнес-логики, прийдется  сперва раз н-цать позапускать надстройку, чтобы проверить как всё выглядит. А потом если вдруг, не дай бог, потребуется изменить текст внутри контролов на что то большее по размеру, нежели после такого проектирования! Надписи скорее всего"съедятся" и придётся снова мучатся, меняя расположения и размеры, чтобы привести форму снова  к более-менее нормальному виду. Также мне так же не ясно, почему разработчики,похоже не предусматривали сложные варианты надстроек, в которых может быть много форм, или одна форма, но с большим числом контролов, которые раскиданы по нескольким вкладкам? Отчасти такие ситуации можно решить с помощью большого числа предусматриваемых команд в надстройке:  Или как вариант, создать некий набор внутри одной формы: Но такие варианты «из коробки» не решают вопроса для сложного набора контролов (например в форме  для всех типов документов может быть один общий для всех типов набор контролов, и специфичный для каждого по отдельности, и просто так, сходу, такой вариант не решить). Но «голь на выдумки хитра», и я нашёл способы, позволяющий обойти такие проблемы, с помощью некоторой смекалки и опыта работы: Но такие хитрые схемы работы с формами надстроек сами по себе требуют отдельной серии статей для обсуждения! И честно говоря, как по мне, такие методы являются некоторым «извращением» над здравым смыслом, так как, судя по всему, изначально в API такая схема работы не предусмотрена!    Заканчивая эту вводную серию статей, хочу подвести итог:     Пользоваться надстройками можно безусловно и в том состоянии, каком сейчас предлагают разработчики. Да, работать сложно и требует переучивания на Lua и  пристального изучения  API надстроек SDK «МойОфис». Но, при определённых усилиях, вполне можно сделать что-то напоминающее макросы  VBA от Microsoft Office. Хотя, думаю что массовый перевод наработок в макросах будет, скорее всего, болезненным и тяжёлым. А  кое-что перевести не удастся совсем, поскольку пока API «МойОфис» не сильно приближён к API Microsoft Office. Ну, или, хотя бы, пока он не будет расширен до уровня, требуемой для типовых задач автоматизации офисной работы.  Очень надеюсь, что эта серия статей оказалась кому-то полезной! Если будет желание, то я могу сделать продолжение, в котором будут рассмотрены надстройки уже более приближенные к реальным задачам автоматизации типового документооборота, в частности в таблицах, которые я вообще не касался.     Программист широкого профиля