LXF104:VBA

Материал из Linuxformat.

Перейти к: навигация, поиск
OOo Basic Помогаем перенести макросы VBA на свободную платформу

Содержание

Из VBA в OOo: проблемы первого дня

Не секрет, что объектные модели Microsoft Office и OpenOffice.org различаются, что представляет проблему при переносе готовых макросов. Александр Маджугин расскажет, как свести их к минимуму.

Миграция на новое программное обеспечение всегда связана с некоторыми проблемами, и смена офисного пакета – не исключение. Если при переходе от Microsoft Office к OpenOffice.org «обычное» использование компонентов особых трудностей не вызывает – OOo, в большинстве ситуаций, успешно справляется с открытием документов своего конкурента (в худшем случае, у вас нарушится форматирование), то попытки использовать макросы, написанные на VBA, чаще всего обречены на неудачу. И если вы собираетесь использовать макросы в документах ODF, придется переписать их заново.

Когда люди, освоившие в свое время макроязык офисного пакета от редмондовского гиганта, переходят на вариант Basic, встроенный в пакет от Sun, в большинстве случаев они сталкиваются с одними и теми же трудностями – эта статья как раз и посвящена им. Мы не ставим своей задачей ответить на весь тот сонм вопросов, которые может вызвать у вас миграция на OpenOffice.org: понятно, что в рамках статьи это невозможно. Задача данной статьи – решить «проблемы первого дня», то есть те, с которыми вы можете столкнуться при написании первого же макроса. Некоторые из этих проблем вызваны, скажем так, «особенностями» OpenOffice.org, но подавляющее большинство – неминуемыми отличиями между таким привычным MSO и OOo. А отличия начинаются еще до того, как вы написали первую строчку кода – например, в самом принципе сохранения макросов.

Библиотеки и модули

Рис. 1Рис. 1. Диалог Макрос OpenOffice.org Basic.

Процедуры и функции, логическую общность которых и представляют собой макросы, сохраняются в модулях. Модули, в свою очередь, хранятся в библиотеках, а библиотеки – в контейнерах библиотек. Контейнерами библиотек могут являться документы OpenOffice.org, кроме того, существует глобальный контейнер, который в окне Макрос OpenOffice.org Basic представлен двумя узлами дерева – Мои макросы и Макросы OpenOffice.org. Первый хранит макросы пользователя, а второй – макросы, поставляемые с дистрибутивом. Несмотря на то, что макросы этого контейнера сохранены в различных директориях, а для каждого пользователя набор ветви Мои макросы будет различен, глобальный контейнер логически является единым целым, напоминая скорее не папку файловой системы, а некоторый фильтр, как, например, фильтры сообщений в почтовом клиенте M2.

Открыть среду разработки OOo Basic саму по себе не представляется возможным: ее окно создается только для редактирования какого-либо модуля. Поэтому для начала работы откройте вышеупомянутое окно Макросы OpenOffice.org Basic (рис. 1), выбрав в меню Сервис > Макросы > Управление макросами > OpenOffice.org Бэйсик..., и создайте в какой-либо библиотеке новый модуль. Сразу же после этого откроется окно IDE (рис. 2).

Имейте в виду, что каждый контейнер библиотек всегда содержит как минимум одну библиотеку с именем Standard. Эту библиотеку удалить нельзя, пусть даже в ней нет ни одного модуля.

Рис. 2Рис. 2. Окно IDE.

Теперь перейдем непосредственно к IDE. Я думаю, даже самые фанатичные поклонники OpenOffice.org согласятся со мной – IDE OOo Basic, как минимум, аскетична. Лично мне больше всего не хватает кнопок «закомментировать/раскомментировать выделенный блок». Приходится делать это с каждой строкой в отдельности. Также, некоторые проблемы вначале может вызывать подсветка синтаксиса. Но только вначале: со временем вы поймете, что подсветка в OOo Basic гораздо более удобна, чем подсветка синтаксиса в стандартной IDE Microsoft Office.

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

Основы

Структурная часть языка практически не отличается от VBA и не готовит каких-то особых сюрпризов. Доступны практически все простые типы данных, к которым вы могли привыкнуть, работая с MS Office. Единственным заметным отличием может стать классическая ошибка определения високосного года, унаследованная в типе Data от электронных таблиц, когда, например, 1900 год считается високосным. С одной стороны, это несомненный недостаток языка, с другой – это повышает совместимость макросов с электронными таблицами, где данная особенность глубоко укоренилась еще со времен Lotus.

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

Sub VarIntExemple
           Print Int (52.3)
           Dim Int As Integer
           Int = 7
           Print Int (52.3)
End Sub

Поэтому, объявляя переменные, надо быть предельно осторожным.

Использовать кириллицу в именах процедур, функций и переменных напрямую, как в VBA, нельзя. Для этих целей можно использовать только Escape-идентификаторы:

Sub [Макрос]
           Dim [Переменная] As String
           [Переменная] = “Escape”
           Print [Переменная]
End Sub

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

Запуск уже существующего макроса обычно не вызывает проблем у тех, кто переходит на OOo Basic с VBA. Здесь все почти то же самое: можно связать макрос с объектом в документе, можно создать пункт меню или кнопку на панели инструментов, можно назначить горячие клавиши или определить запуск макроса по определенному событию.

Но все это только до тех пор, пока не потребуется вызвать процедуру из кода Basic по ее полному имени, включающему документ, в котором она сохранена, библиотеку и модуль. Здесь все начинают лихорадочно искать аналог VBA’шного Call, а его нет. Можно, конечно, попробовать использовать Shell (“LibraryName.ModuleName.MacroName”) и узнать, что такой файл не найден. Те, кто поупорнее, скоро обнаружат, что в пределах одного контейнера библиотек можно выполнить макрос и с помощью Shell, если передать ей параметр, не заключая его в кавычки: Shell (LibraryName.ModuleName.MacroName). Правда, при этом произойдет ошибка «Несовместимые типы», которую, конечно, можно перехватить. Но все это можно сделать и без ShellLibraryName.ModuleName.MacroName тоже дает неплохой результат.

Можно попытаться развить идею дальше так:

Shell (“soffice”,,“““macro :///LibraryName.ModuleName.MacroName”””)

или привлечь сервис SystemShellExecute:

oSSE = createUnoService(“com.sun.star.system.SystemShellExecute”)
oSSE.execute(“soffice”, “““macro:///LibraryName.ModuleName.
MacroName”””, 0)

фактически эмулируя запуск макроса из командной строки.

Однако это все обходные и не очень удобные и красивые пути, не позволяющие запустить макрос, сохраненный в определенном документе. Между тем, OpenOffice.org имеет специально предназначенный для этих целей интерфейс com.sun.star.script.provider.XScript. И если воспользоваться им, все получается относительно легко:

Sub RunMacroFromDocument (ByVal oDoc As Object)
         Dim aOutParamIndex (2)
         Dim aOutParam (2)
         ' Создаем СкриптПровайдер
         oSP = oDocA.ScriptProvider()
         ' Получаем скрипт
         oScript = oSP.getScript(“vnd.sun.star.script:Standard.Module1.Main?language=Basic&location=document”)
         ' Запускаем макрос
         oScript.invoke(Array(“1 параметр”, “2 параметр”, “3 параметр”),aOutParamIndex(),aOutParam())
End Sub

Примерно так может выглядеть запускаемый макрос:

Sub Main (a as String, ByVal b as string, c as string)
         print a
         print b
         print c
         a=b
         b = “b”
         c = “c”
End Sub

После выполнения этого кода массивы aOutParamIndex и aOutParam будут иметь следующее содержимое:

  • aOutParamIndex = {0,2} – позиции выходных параметров [out] или параметров, используемых как для входных, так и для выходных значений [inout] вызываемой функции. Сами аргументы передаются методу invoke() также в виде массива.
  • aOutParam = {«2 параметр»,»c»} – этот массив содержит выходные значения параметров. Как видно, параметр b макроса Main в нем отсутствует, так как он является входящим для процедуры Main. На этот факт указывает и содержимое aOutParamIndex.

Изучаем объектную модель

Естественно, что для применения подобных методов необходимо хотя бы немного знать API OpenOffice.org. К сожалению, в IDE OOo отсутствует автодополнение. Затруднить изучение API методом «научного тыка», на начальных этапах, могут и особенности макрорекордера. Поэтому о записи макросов поговорим подробнее.

Дело в том, что макрорекордер использует для записи макросов так называемый диспетчер (dispatcher), работающий через весьма специфический интерфейс UNO. UNO – некоторое подобие среды COM в Windows, предназначенное для обеспечения взаимодействия компонентов на любой платформе, так как понятно, что использовать COM в Linux или Mac OS не получится.

Если не вдаваться в подробности, то диспетчер эмулирует действия пользователя. Чтобы было понятнее, приведем простой пример. Допустим, нам надо перенести данные из ячейки A1 текущего листа книги Calc в ячейку A2. Вот какой код (за исключением комментариев, разумеется) будет сгенерирован макрорекордером, если запустить запись макроса и скопировать данные из первой ячейки во вторую:

sub Main
rem объявление переменных
dim document as object
dim dispatcher as object
rem Получение доступа к фрэйму документа
document = ThisComponent.CurrentController.Frame
rem Создание сервиса диспетчера
dispatcher = createUnoService(“com.sun.star.frame.DispatchHelper”)
rem Установка значения свойств
dim args1(0) as new com.sun.star.beans.PropertyValue
args1(0).Name = “ToPoint”
args1(0).Value = “$A$1”
rem Выбор ячейки
dispatcher.executeDispatch(document, “.uno:GoToCell”, ““, 0, args1())
rem Копирование данных в буфер
dispatcher.executeDispatch(document, “.uno:Copy”, ““, 0, Array())
rem Установка значения свойств
dim args3(0) as new com.sun.star.beans.PropertyValue
args3(0).Name = “ToPoint”
args3(0).Value = “$A$2”
rem Выбор ячейки
dispatcher.executeDispatch(document, “.uno:GoToCell”, ““, 0, args3())
rem Вставка
dispatcher.executeDispatch(document, “.uno:Paste”, ““, 0, Array())
end sub

Заметьте, что хотя для доступа к текущему документу используется глобальная переменная ThisComponent, содержащая ссылку на текущий компонент, лист книги не получается из нее напрямую: используется ссылка на фрейм, являющийся элементом GUI. То есть доступ осуществляется не непосредственно к листу книги, а к окну приложения. Затем выполняются стандартные операции копирования и вставки с использованием буфера обмена. Такие действия приводят к тому, что копируются не только данные, но и форматирование ячейки, а в случае, когда целевая ячейка уже содержит некоторые данные, пользователю будет выведено предупреждение о перезаписи. Кроме того, эти действия будут всегда производиться с текущим листом, а если будет необходимо выполнить то же действие с чем-то другим, этот макрос вам уже не поможет. Впрочем, это справедливо и для Excel.

Коренное же отличие от макрорекордера VBA – в том, что вместо прямого доступа к элементу используется команда диспетчеру UNO. Сравните способ выбора ячейки в этом макросе:

dispatcher.executeDispatch(document, “.uno:GoToCell”, ““, 0, args1())

и в аналогичном макросе, записанном в Excel,

Range(“A1”).Select

где используется объектная модель.

Таким образом, получить представление об API, изучая записанные макросы в OOo Basic, невозможно. Кроме того, вы не сможете использовать диспетчер, если запустите пакет без GUI, например, в режиме сервера. А между тем, только использование API позволяет писать сложные макросы, действительно облегчающие работу.

Посмотрим, как задачу выбора ячейки можно решить с использованием API, заодно ответив на один из самых часто задаваемых новичками вопросов: «Как получать и записывать значение ячейки книги Calc?».

Рассмотрим следующий код:

Sub Main
          Dim oDoc As Object ' это наш документ
          Dim oSheet As Object ' лист
          ' Ячейки
          Dim oCell1 As Object
          Dim oCell2 As Object
          oDoc = ThisComponent
          ' получаем активный (текущий) лист
          oSheet = oDoc.getCurrentController.ActiveSheet
          ' получаем ячейки листа
          oCell1 = oSheet.getCellRangeByName(“A1”)
          oCell2 = oSheet.getCellRangeByName(“A2”)
          ' Получаем значение ячейки A1
          ' и помещаем его в A2
          oCell2.setValue (oCell1.getValue)
End Sub

Как видите, здесь не используется диспетчер, а применен «чистый» API. Текущий лист в этом коде получается из объекта CurrentController, возвращаемого методом getCurrentController. Метод «знает» текущее состояние объекта, в частности, активный лист, что нам и нужно. Если необходим какой-то конкретный лист, то нужно сначала получить доступ к коллекции листов книги (Sheets) и далее выбирать листы с помощью методов getByIndex' и getByName, используя для идентификации листа его номер или имя:

oSheet = oDoc.getSheets.getByIndex(0) ' первый лист книги
oSheet = oDoc.getSheets.getByName(“Лист1”) ' лист с именем Лист1

«Отправной точкой» для объектов данного макроса можно назвать глобальную переменную ThisComponent, ссылающуюся на «этот документ». Заметим тут следующее: термин «этот документ» в OOo Basic несколько сложнее, чем просто активный документ. Активный документ можно получить из переменной StarDesktop методом getCurrentComponent. Однако это не всегда удобно, поскольку IDE тоже является компонентом StarDesktop (как и справочная система), и при запуске макроса из среды разработки вы будете получать ссылку именно на нее. Переменная же ThisComponent всегда ссылается на документ, причем на активный, только в том случае, если встречается в коде макроса, сохраненного в самом пакете OOo Basic (например, в группе Мои макросы). Если же макрос сохранен непосредственно в документе, то ThisComponent ссылается на этот документ, независимо от того, является ли он активным.

Вообще говоря, за редкими исключениями, все объекты в OOo Basic наследуются от двух глобальных объектов, описанных выше, т.е. ThisComponent и StarDesktop. Кроме них, есть еще две переменные, используемые не так часто – это BasicLibraries и DialogLibraries, которые ссылаются на коллекции библиотек макросов данного контейнера (например, документа). В числе прочего, они предоставляют интересную возможность переписывать код макроса программно – исключение составляет лишь код модуля, макрос которого выполняется в текущий момент. Таким образом вы можете изменять даже код той библиотеки, из которой выполняется ваш макрос. Это дает удивительные возможности сохранения настроек прямо в коде вашего приложения.

Чтобы получить библиотеки глобального контейнера из документа, необходимо обратиться к переменным BasicLibraries и DialogLibraries как к составляющим глобального обзора GlobalScope:

cGlobalBLibraries = GlobalScope.BasicLibraries

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

Рентген для OpenOffice.org

Рис. 3Рис. 3. Xray – незаменимый инструмент при разработке макросов для OpenOffice.org.

Вернемся к изучению API OpenOffice.org. При использовании API всегда встает вопрос о том, как узнать все свойства и методы того или иного объекта. И если со свойствами все более-менее понятно – их, вместе с их значениями, можно просматривать в стандартном окне наблюдения IDE, то методы обычно вызывают некоторые затруднения. Между тем, практически каждый объект объектной модели OpenOffice.org имеет методы и свойства – Dbg_Methods и Dbg_Properties, соответственно. На практике очень удобно использовать утилиту Xray, выводящую свойства и методы в удобной форме и предоставляющую дополнительные возможности для изучения объекта. Найти ее можно на сайте http://www.ooomacros.org/dev.php#101416 или на других ресурсах, посвященных OpenOffice.org.

Использование этого инструмента чрезвычайно просто – достаточно включить в ваш код вызов Xray, передав ему в качестве параметра интересующий вас объект:

xray VarObject

где VarObject – объект, который вы хотите изучить. Например:

 Sub Main
          xray ThisComponent
 End Sub

или

Sub Main
          Dim oDoc As Object
           oDoc = ThisComponent
          xray oDoc
End Sub

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

Вы можете копировать содержимое основного окна Xray как обычный текст. Двойной щелчок по свойству в этом окне позволит просматривать с помощью Xray объект, возвращаемый этим свойством – конечно, только в том случае, если это свойство не предназначено только для записи. То же касается и методов, просматриваемого объекта, в том случае, если методы возвращают некоторый объект.

Если на вашем компьютере установлен SDK OpenOffice.org, то вы можете переходить к описанию объекта в нем, нажав соответствующую кнопку на панели Xray. Выбрав необходимые пункты в списке, можно ознакомиться с интерфейсами и сервисами, поддерживаемыми изучаемым объектом.

Из всего вышеописанного можно сделать вывод, что в OpenOffice.org используется несколько необычная объектная модель. Фактически, можно сказать, что в ней не используется такое понятие, как классы, а стало быть, создавать свои классы тоже нельзя. Вместо них здесь используются родственные понятия: интерфейсы и сервисы.

Интерфейсы предоставляют объектам методы, причем один интерфейс может включать другой, «подчиненный» интерфейс, и соответственно предоставлять объекту методы этого «подчиненного». Естественно, что один объект не может содержать только часть методов какого-то интерфейса: он поддерживается непременно полностью. Таким образом, если мы знаем, что объект имеет метод getByIndex, то можем быть уверены, что этот объект имеет и метод getCount, предоставляемый тем же интерфейсом com.sun.star.container.XIndexAccess. Однако на практике подобные рассуждения опасны, и лучше знать наверняка, поддерживает ли объект нужный вам интерфейс, так как случается, что методы с одинаковыми именами предоставляются различными интерфейсами. Например, функция endProperty() может быть предоставлена и интерфейсом com::sun::star::configuration::backend::XlayerHandler, и интерфейсом com::sun::star::configuration::backend::XUpdateHandler. Узнать, какими именно интерфейсами обладает данный объект, помогает свойство Dbg_SupportedInterfaces, возвращающее список всех интерфейсов.

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

Такая организация объектной модели может вначале показаться несколько сложной и непривычной. Однако, поработав некоторое время с API OOo Basic, вы поймете, что она гораздо мощнее и гибче традиционной – пусть и нелегка в освоении, особенно на ранних этапах работы. Именно она делает OOo Basic намного более перспективным макроязыком, чем его прямые конкуренты. LXF

Полезные ссылки

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию