Программное взаимодействие проектов Office 2000

Андрей Колесов

Оглавление

Несмотря на то что в названии статьи указан Office 2000, мы будем рассматривать вопросы взаимодействия на примере Word 2000. Для других приложений пакета принципы такой работы cхожи, хотя и имеются некоторые различия. Тем не менее практически все, о чем мы здесь будем рассказывать, справедливо и для Office 97, и для Word 97 (имеющиеся различия мы постараемся выделить).

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

Как связать два документа Word

Постановка задачи выглядит следующим образом. Создайте документ Word с именем DocSub.doc. Теперь в среде VBA включите в его программный проект модуль кода (SubModule) и создайте в нем две простые процедуры:

Sub DocSubProc1 ()

          Msgbox "Работает процедура DocSubProc1"

End Sub



Sub DocSubProc2 (d%)

          Msgbox "Работает процедура DocSubProc2"

          d = d + 1

End Sub

Обратите внимание, что первая процедура автоматически попала в список макрокоманд проекта. В связи с этим подчеркнем: макрокомандой является процедура, которая удовлетворяет следующим условиям:

  • она имеет тип Public Sub или Sub (Public - по умолчанию);
  • не имеет передаваемых аргументов (данные в нее передаются в виде глобальных переменных, в том числе свойств глобальных объектов);
  • находится в модуле кода.

Теперь закроем DocSub и создадим еще один документ с названием DocMain, включим в него модуль кода MainModule, в котором напишем макрокоманду

Sub DocMainMacro()

End Sub

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

Тривиальное решение проблемы - написать Call DocSubProc1 - не поможет, так как в этом случае мы получим сообщение о том, что такой процедуры нет.

Принципы обращения к процедурам проекта

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

Call [ProjectName.][ ModuleName.]ProcedureName

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

Важное замечание. Обратите внимание, что в данном случае используется параметр ProjectName - имя проекта, что совсем не одно и то же, что имя документа. Имя документа является обычным именем файла, причем в общем случае для однозначной его идентификации - с указанием полного пути к нему. Имя проекта - это специальный внутренний идентификатор, с помощью которого осуществляется взаимодействие программной части документов и шаблонов. По умолчанию при создании нового документа его проекту присваивается стандартное имя - Project. Так что два созданных нами документа имеют одинаковые имена проектов, что никак не влияет на работу, если документы используются в автономном режиме. Но в случае программного взаимодействия часто необходимо, чтобы имена проектов были разными. Имена устанавливаются в окне Properties для единственного свойства проекта Name. Учитывая это, изменим имена проектов для наших документов на ProjMain и ProjSub (мы используем разные идентификаторы для проекта и документа, чтобы различать эти понятия). Отметим также, что шаблон Normal.dot имеет фиксированное имя файла и проекта, поэтому в окне Project он обозначен одним идентификатором - Normal.

В каких случаях необходимо использовать расширенный вариант обращения к процедурам? Для ответа на этот вопрос рассмотрим небольшой пример. Включите в документ DocMain еще один новый модуль кода (Module1) и создайте в нем процедуру:

Public Sub TestProc()

   MsgBox "Работает DocMain.Module1.TestProc"

End Sub

Теперь сделайте в DocMainMaсro обращение к этой процедуре Call TestProc и запустите макрокоманду - все сработает нормально. Включите в этот документ еще один модуль (Module2) с такой же процедурой (только поменяйте выдаваемое сообщение, чтобы различить работу процедур). Запустите на выполнение DocMainMacro, и вы получите сообщение об ошибке: двусмысленность имени процедуры. В этом случае для обращения к нужной подпрограмме нужно изменить формат обращения, указав имя модуля:

Call Module1.TestProc

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

Совет: Следите за появлением подсказок! После ввода имени модуля редактор выдает подсказку с перечнем доступных процедур. Отсутствие такой подсказки говорит о том, что вы ввели неверное имя модуля (или соответственно недоступного проекта). Попробуйте обратиться к процедуре другого проекта следующим образом:

Call ProjSub.SubModule.DocSubProc1

Еще при вводе этого кода вы увидите, что проект ProjSub является недоступным (не появится подсказка). При запуске макрокоманды DocMainMacro появится сообщение о не найденном идентификаторе.

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

Обращение к процедурам шаблона Normal

Теперь попробуем поработать с процедурами шаблона Normal.

Внимание! Я не рекомендую без особой нужды вносить изменения в шаблон Normal. Для пользовательских настроек лучше создать отдельный шаблон, который может автоматически загружаться при запуске Word в качестве глобального, если его записать в каталог C:\Windows\Appication Data\Microsoft\Word\StartUp\ или в каталог …\Office2000\StartUp.

При экспериментах с Normal.dot (чем мы сейчас и займемся) лучше сделать его исходную копию и при завершении исследований восстановить ее. Более того, иметь копию Normal всегда полезно...

Включите в Normal модуль Module1 с такой процедурой:

Sub NormalProc()

MsgBox "Работает Normal.Module.NormalProc"

End Sub

К ней можно обратиться из макрокоманды DocMainMacro с помощью любого из перечисленных ниже вариантов

Call Normal.Module1.NormalProc

Call Normal.NormalProc

Call NormalProc

Из этого можно сделать несколько промежуточных выводов:

  • программный код проекта Normal доступен для проекта ProjSub (DocSub);
  • имя модуля можно опустить, потому что процедура NormalProc является уникальной для проекта Normal. Имя модуля было бы обязательным, если бы имелась процедура с таким же именем в другом модуле.

Важное замечание. Само обращение к процедуре внешнего проекта можно сделать только потому, что ProjMain имеет ссылку на него в виде Normal: посмотрите раздел References в описании проекта в окне Project - она была установлена при создании документа, так как присоединенным шаблоном был указан Normal. Название же проекта можно опустить в обращении, так как имя NormalProc является уникальным для обоих проектов.

Создайте в Normal и в ProjSub две процедуры с одинаковыми именами CommonProc. Тогда для обращения к процедуре Normal из макрокоманды DocSubMacro нужно обязательно указать имя проекта:

Call Normal.CommonProc ‘ обращение из ProjSub к Normal

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

Call CommonProc ‘ обращение внутри проекта ProjSub

Еще один интересный момент. При записи Call Normal появится подсказка с перечнем допустимых элементов данного обращения. Здесь нужно обратить внимание на два момента:

  1. Доступными компонентами проекта являются только модули кода и ThisDocument. Классы и формы (точнее, их процедуры) являются недоступными для внешних проектов.
  2. В списке компонентов видны имена процедур, но только (!) - для уникальных идентификаторов. Если бы мы создали в Normal еще один модуль и записали в него процедуру NormalProc, то имя NormalProc исчезло бы из списка «второго поля», но при этом появилась бы строка Module2. Понятно, что опускать имя модуля можно только для тех процедур, которые появляются в списке.

Обращение к процедурам других шаблонов

Нам пока не очень понятно, как можно связать два документа Word, однако все мы знаем, что этот текстовый процессор предоставляет два варианта связи документов и шаблонов. Наверняка это может как-то помочь нам в организации их программного взаимодействия. Поэтому сейчас мы запишем на диск созданный ранее документ DocSub.doc, а также сохраним его же в формате шаблона с именем DocSub.dot и затем закроем этот шаблон. Теперь у нас в среде Word находится только один документ DocMain, и мы попробуем подключить к нему программные компоненты шаблона DocSub.dot.

Вариант 1. Подключение глобального шаблона

С помощью диалогового окна Templates and Add-ins подключим DocSub.dot к документу DocMain.doc в виде глобального шаблона. Затем перейдем в среду VBA (напомним, что код глобальных шаблонов не доступен для просмотра и редактирования).

Все попытки обратиться из макрокоманды DocMainMacro напрямую к процедуре DocSubProc1 опять завершатся неудачей - код проекта глобального шаблона является недоступным для документа. Но то, что нельзя сделать путем прямого обращения к процедуре с помощью оператора Call, можно выполнить с использованием метода Run:

Application.Run "DocSub.SubModule.DocSubProc1"

Подробнее об использовании метода Run читайте в этой статье ниже, а пока закройте этот глобальный шаблон.

Вариант 2. Подключение присоединенного шаблона

С помощью того же окна Templates and Add-ins подключите DocSub.dot в виде присоединенного шаблона. Теперь будет работать любой вариант обращения к DocSubProc1 из макрокоманды DocMainMacro:

Call ProjSub.SubModule.DocSubProc1

Call ProjSub.DocSubProc1

Call DocSubProc1

При этом вы можете с удивлением обнаружить, что все процедуры шаблона Normal стали недоступными для нашего проекта ProjMain - ссылку на Normal заменила ссылка на DocSub.dot.

Использование Object Browser

Однако все наши исследования по «доступности/недоступности» программных проектов были бы гораздо эффективнее, если бы мы сразу использовали команду View/Object Browser (F2).

Откройте документ DocMain.doc с присоединенным шаблоном DocSub.dot, выделите в среде VBA в качестве активного проекта ProjMain и нажмите F2. Раскройте в окне Object Browser список Project/Library - здесь приведен перечень всех проектов и библиотек, доступных в данный момент активному проекту ProjMain. Ниже в окне Classes приведен список компонентов выделенного проекта, а в окне Members - процедур.

Здесь нужно иметь в виду, что, хотя, например, для проекта ProjSub будут показаны включенные в него модули форм и классов (если бы мы их вставили), они будут недоступны для программного обращения для ProjMain.

Обратим внимание еще на один любопытный момент. Для его иллюстрации прямо в среде VBA измените для шаблона DocSub.dot имя проекта с ProjSub на ProjMain (то есть установите для двух проектов одинаковые имена), опять откройте окно Object Browser и сравните это изображение с рис. 7. Видно, что в списке вместо двух проектов ProjSub и ProjMain остался только последний, причем он относится к шаблону DocSub.dot (см. состав модулей в окне Classes). А документ DocMain.doc вообще не представлен в Object Browser. Однако если вы начнете вводить в макрокоманде DocMainMacro код Call ProjMain, то по появившейся подсказке легко догадаетесь, что в данном случае под ProjMain подразумевается как раз документ DocMain.

Из этого можно сделать два вывода. Первый - при подключении одноименного проекта будут доступны его процедуры только с уникальными именами (для одноименных процедур нужно обязательно указать название проекта). Второй - лучше, чтобы все проекты имели свои уникальные имена - у вас будет больше возможностей и меньше головной боли.

Использование окна References - решение исходной задачи

За разговорами «вокруг да около» мы вплотную подошли к проблеме, обозначенной в первом пункте статьи, - как связать два документа Word. Просмотрев состав доступных программных компонентов в окне Object Browser, вспомним, что для их подключения в среде VB используется команда References (меню Tools).

Давайте откроем это окно - в нем мы увидим ссылки именно на те проекты и библиотеки, что и в Object Browser. Отсюда становится очевидным универсальный механизм подключения проектов (как шаблонов, так и документов!) к документу: это можно сделать путем установки ссылок на нужные вам документы и шаблоны в окне References. (Чтобы не путать наш исходный документ DocSub.doc с шаблоном DocSub.dot, изменим имя проекта шаблона на DocSubTemplate.) Теперь отметим несколько особенностей подключения проектов с помощью References в среде VBA:

  • Мы не можем убрать ссылку на присоединенный шаблон, который был подключен с помощью окна Templates and Add-ins в среде Word.
  • В списке Available References представлены все проекты, загруженные в данный момент в среду Word. Однако для включения нового проекта в этот список можно также использовать кнопку Browse. В этом случае произойдет автоматическая загрузка документа. Используем эту возможность для загрузки документа DocSub.doc и обратим внимание на два момента. Первое: документ DocSub.doc можно редактировать и сохранять, но закрыть его автономно нельзя - это можно сделать только вместе с «базовым» документом DocMain.doc. Второе: если убрать ссылку на DocSub.doc, то он автоматически выгрузится из среды Word.
  • Используя References, к документу можно подключать сколько угодно проектов (с помощью Templates and Add-ins - только один шаблон). В данном случае мы подключим шаблоны Normal и DocSub.dot, а также документ DocSub.doc, и все это будет отражено в компоненте References в окне Project.

    Обратите внимание, что в окне Project представлена только часть этих ссылок - тех, которые являются дополнительными.

  • Все проекты должны иметь уникальные имена.

Итак, задача организации программной связи двух документов Word решена. Но тут появляется другая заманчивая идея - а нельзя ли устанавливать эти связи программным способом, чтобы, например, уже в ходе выполнения программы определять, какие проекты нужны, и динамически устанавливать необходимые ссылки? Эта задача решается легко!

Программное подключение проектов

Чтобы разобраться с этим вопросом, отключите все ссылки проекта ProjMain (документ DocMain), перезагрузите Word и откройте DocMain с присоединенным Normal (то есть все по минимуму). Перейдите в среду VBA.

Посмотреть программным способом полный список ссылок для активного проекта, который представлен в окне Tools/References, можно с помощью такого кода:

Sub DocMainMacroMyRef()

    ‘ распечатка ссылок активного документа

Dim I%

With ActiveDocument.VBProject.References

       For i = 1 To .Count

               Debug.Print "Имя проекта = " & .Item(i).Name

               Debug.Print "Полное имя файла = " & .Item(i).FullPath

          Next

    End With

End Sub

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

Хочу обратить внимание на то, что здесь мы применили программную конструкцию, которая решает некоторые практические проблемы разработчика (подробнее об этом см. статью «Особенности технологий раннего и позднего связывания в Visual Basic»).

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

Имя проекта = VBA

Полное имя файла = C:\Program Files\Common Files\Microsoft     Shared\VBA\VBA6\VBE6.DLL

Имя проекта = Word

Полное имя файла = F:\off2000\Office\MSWORD9.OLB

Имя проекта = stdole

Полное имя файла = C:\WINDOW95\SYSTEM\stdole2.tlb

Имя проекта = Office

Полное имя файла = F:\OFF2000\OFFICE\MSO9.DLL

Имя проекта = MSForms

Полное имя файла = C:\WINDOW95\SYSTEM\FM20.DLL

Имя проекта = NormalПолное имя файла = C:\WINDOW95\Application     Data\Microsoft\Templates\Normal

Для управления связью проектов нам также пригодится механизм доступа к списку загруженных в данный момент в среду Word документов и шаблонов

Sub DocMainMacroVBEProjects()

    ‘ распечатка перечня загруженных проектов

   Dim i%With VBE.VBProjects

      For i = 1 To .Count

         Debug.Print "Имя проекта = " & .Item(i).Name

        Debug.Print "Полное имя файла = " & .Item(i).FileName

       Next

    End With

End Sub

Результат выполнения кода:

Имя проекта = Normal

Полное имя файла = C:\My Documents\Normal

Имя проекта = ProjMain

Полное имя файла = D:\DocMain.doc

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

Здесь следует еще раз подчеркнуть, что каждый документ или шаблон характеризуется двумя важными идентификаторами - полным именем файла и именем проекта. Из приведенных выше фрагментов понятно, что по имени загруженного документа можно всегда программным образом узнать имя проекта. А по списку в окне References (здесь находятся проекты с уникальными именами) можно выполнить обратную операцию. Далее мы будем считать, что изначально нам известно только имя файла (с указанием полного пути!), для которого мы отведем переменную FileName$. Вычисляемое имя проекта будет храниться в ProjectName$.

Нам также понадобится функция, которая будет определять, находится ли указанный файл в списке ссылок активного документа:

Function TestReference(FileName$) As Object

  ‘ Проверка существования ссылки

  ‘ на указанный файл из активного документа

  ‘ ВХОД: FileName$ - полное имя файла

  ‘ ВЫХОД:

  ‘ TestReference = Nothing - нет

  ‘ TestReference = Not Nothing - да



TestReference = Nothing                                    ‘ если не найден

With ActiveDocument.VBProject.References

    For i = 1 To .Count                                    ‘ перебор всех элементов коллекции

         If FileName$ = .Item(i).FullPath Then             ‘ найден

           TestReference = .Item(i): Exit For

        End If

     Next

  End With

End Function

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

Function TestReferenceNew(ProjectName$) As Object

     On Error Resume Next ‘ в случае ошибки подключения

     TestReferenceNew = Nothing ‘ останется Nothing

     TestReferenceNew = _

           ActiveDocument.VBProject.References.Item(ProjectName$)

End Function

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

Далее нам понадобится еще пара процедур - (см. листинг) «Процедуры CreateReference и DeleteReference» - для установки и сброса ссылки. Здесь нужно обратить внимание, что в CreateReference требуется обязательно установить обработку ошибок, появление которых весьма вероятно, например, в случае задания несуществующего файла, неверного типа файла или дубликатного имени проекта.

Внимание при использовании объекта ActiveDocument! Здесь нужно ответить на важный вопрос - что мы понимаем (точнее, что понимает Word) под активным документом? При работе в среде VBA может сложиться впечатление, что активным документом является тот, с проектом которого мы работаем в данный момент. Но это не так!

Активный документ Word - тот, с которым мы работали в среде самого редактора перед переходом в VBA. Чтобы разобраться в этом вопросе, проведите такое исследование. Загрузите Word и откройте два документа - DocMain.doc и DocSub.doc. В данном случае проследите, чтобы первый документ не содержал ссылок на DocSub.doc.

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

Sub TestMacro()

   MsgBox "Имя файла активного проекта = " & _

                       VBE.ActiveVBProject.FileName & vbCrLf & _

                "Имя файла активного документа = " & _

                       ActiveDocument.FullName

End Sub

Перед ее запуском попробуйте ответить на вопрос - какой документ и какой проект является активным в данный момент? Ответ относительно проекта легко получить, взглянув на окно Project, - наверняка там выделен сейчас DocMain (мы создавали процедуру для него). При работе с Word 97 определить, какой документ является активным, можно - его имя будет видно на кнопке панели задач. Но в Word 2000 это сделать не удастся, так как здесь каждый открытый документ имеет собственную кнопку, а активной кнопкой сейчас является VBA.

Так вот, если вы выполняли действия в приведенной выше последовательности, то активным документом будет DocSub.doc, так как он загружался последним. Тем не менее на всякий случай активизируйте DocSub.doc в среде Word, перейдите в VBA, активизируйте проект ProjMain, запустите на выполнение макрокоманду TestMacro и убедитесь, что имена файлов для активного шаблона и активного документа не совпадают.

Если вы теперь подключите DocSub.doc к DocMain.doc с помощью окна References и попробуете запустить макрокоманду DocMainMacroMyRef, то увидите, что вы получили список ссылок DocSub, а не DocMain, как хотелось бы.

Обратим внимание, что путаница при запуске макрокоманды возможна только при работе в среде VBA. При обращении из среды Word с помощью команды Tools/Macro/Macros доступны макросы только активного документа, проект которого также автоматически становится активным.

Однако на этом сюрпризы не кончаются. Если вы закроете DocMain и DocSub, а потом опять откроете DocMain (к которому подключен DocSub), перейдете в среду VBA и в ней запустите TestMacro, то опять обнаружите несовпадение активного документа и активного проекта. Дело в том, что в момент запуска макрокоманды произойдет автоматическая загрузка присоединенного документа DocSub.doc, который и станет активным.

Аналогичные фокусы могут происходить при программном подключении DocSub к DocMain. При первом обращении

Code = CreateReference&("DocSub.doc", ProjectName$)

все сработает нормально - будет произведено подключение файла к DocMain. Однако при повторном обращении появится сообщение об ошибке. Причина этого заключается в том, что при подключении файла SubDoc выполнится его загрузка и он станет активным документом. При втором обращении поиск ссылок будет выполняться для DocSub и потом последует попытка подключить этот файл к самому себе.

Решение этой проблемы выглядит, казалось бы, тривиально - нужно во всех приведенных выше программах заменить ActiveDocument на VBE.ActiveVBProject, то есть забыть об активном документе и иметь дело только с активным проектом. Но проблема заключается в том, что при установке новой ссылки меняется и активный проект.

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

Если же вы работаете в среде VBA, то в стартовой макрокоманде можно установить такую принудительную активизацию документа:

Documents(VBE.ActiveVBProject.FileName).Activate

А для контроля за возможным изменением статуса активного документа при установке ссылок можно модернизировать код процедуры CreateReference следующим образом:

MyFile$ = ActiveDocument.FullName                       ‘ запоминаем имя активного файла

Set Ref = _

      ActiveDocument.VBProject.References.AddFromFile(FileName)

   Documents(MyFile).Activate                           ‘ восстанавливаем активный  документ  и одновременно - проект

Отметим, что установка активного документа автоматически делает активным его проект. Все работает.

Завершающий бросок. Казалось бы...

Теперь мы подошли вплотную к волнующему моменту - собственно к программному подключению документа DocSub.doc к документу DocMain и обращению к процедуре первого DocSubProc1. Это может выглядеть следующим образом:

Sub DocMainMacro()





   Dim FileName$, Code&, ProjectName$, Say$



   FileName$ = "D:\DocSub.doc"

   ‘ 1. Создание ссылки на указанный файл

   Code = CreateReference(FileName$, ProjectName$)

   If ProjectName$ <> "" Then           ‘ подключен

     If Code = 0 Then

       Say$ = " подключен сейчас"

     Else: Say$ = " был уже подключен"

     End If

     MsgBox "Файл " & ProjectName$ & Say$ & ", _

           проект = " & ProjectName$

   Else                                 ‘ не удалось подключить

     MsgBox "Файл " & FileName$ & " не удалось     подключить" _

           & vbCrLf & "Ошибка = " & Code

     Exit Sub

   End If

   ‘

   ‘ 2. Обращение к процедуре:

   Call DocSubProc1                      ‘ при динамическом подключении     ссылки 

                                         ‘ здесь будет ошибка "Sub not defined"

   ‘ 3. Удаление (или нет) ссылки

   If MsgBox("Удалить ссылку на проект " & _

                   ProjectName$, vbYesNo) = vbYes Then

     DeleteReference (FileName$)         ‘ удалить ссылку

   End If



End Sub

Казалось бы, задача решена, но тут мы столкнемся еще с одним препятствием. Такая конструкция будет работать, только если установка ссылки на DocSub уже была выполнена перед запуском макрокоманды (точнее, проекта DocMain). А при динамическом подключении файла будет выдана ошибка: не определена процедура DocSubProc1. Здесь можно применить хитрость, заменив прямое обращение к этой процедуре вызовом промежуточной подпрограммы:

 ‘ 2. Обращение к промежуточной процедуре:

Call MyDocSubProc1 

…

Sub MyDocSubProc1()

   Call DocSubProc1 

End Sub

Однако такой вариант будет работать только при установке режима Compile On Demand (вкладка General окна Tools/Options), что я как раз не очень советую делать, особенно при отладке программы (подробнее см. статью «Особенности технологий раннего и позднего связывания в Visual Basic» в этом номере).

Использование свойства Run для связи проектов

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

Call MyProject.MyModule.MyProcedure

и

Application.Run "MyProject.MyModule.MyProcedure"

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

  • Обращаться к процедурам с помощью Run можно только к активному документу или глобальному шаблону (в том числе Normal). Подчеркнем - наличие ссылок на проект совершенно необязательно.
  • Если имя процедуры является уникальным, то можно указать только его. В противном случае нужно обязательно указать и имя проекта, и имя модуля. Обратите также внимание, что при использовании Run можно использовать не только имя проекта, но и имя файла, например:
Application.Run "‘DocSub.doc’!.SubModule.DocSubProc1"

С учетом этого обращение к процедуре загруженного (!) документа выглядит следующим образом:

MyFile$ = ActiveDocument.FullName           ‘ запоминаем имя ‘ активного файла

   Documents(FileName$).Activate            ‘ новый активный документ

   Application.Run ProjectName$ + ".SubModule.DocSubProc1"

   Documents (My File). Activate            ‘ восстанавливаем активные ‘ документ и проект

Теперь о различиях Word 97 и 2000. В Word 97 можно было обращаться только к макрокомандам, в Word 2000 - к любым глобальным процедурам модулей кода, то есть передавать параметры в подпрограммы и функции. Так что в Word 2000 можно обратиться к процедуре DocSubProc2, которую мы написали в самом начале этой статьи. Но обращение это выглядит довольно странно:

d% = 2

k% = Application.Run("DocSubProc2", d)

То есть даже к подпрограмме нужно обращаться как к функции (это вполне допустимо и в обычных случаях, просто не очень понятно, что именно возвращается в качестве результата). Кроме того, у меня совершенно не получилось использовать имя проекта в качестве уточнения координат функции (то есть нельзя обращаться к функциям с дубликатными именами).

Достоинства метода Run представляются довольно очевидными - можно производить динамическую настройку вашего проекта для вызова любых проектов и их процедур в процессе работы программы, не опасаясь проблем с компиляцией кода и неопределенностью внешних ссылок. Например, пользователь может ввести с клавиатуры имя документа, модуля и процедуры:

FileName = Input Box ("Введите имя документа")

ModuleName = InputBox("Введите имя модуля")

ProcName = InputBox("Введите имя макрокоманды")

‘ 1. Если документ не загружен - загрузить

‘ 2. Сделать его активным

‘ 3. Определить имя его проекта

Application.Run ProjectName & "." & ModuleName & "." & ProcName

Обращения к VBA-проектам из VB-проекта

Если для VBA можно использовать два способа обращения к процедурам внешних проектов, то для VB-проектов вариант только один - использование метода Run. Вот как это может выглядеть при обращении к процедуре DocSubProc1 документа DocSub.doc в Visual Basic:

Dim oWord As Object





‘создаем экземпляр приложения

Set oWord = CreateObject("Word.Application") 

oWord.Documents.Open "d:\docsub.doc" ‘ открываем документ

‘ обращаемся к его макрокоманде

oWord.Run "ProjSub.SubModule.DocSubProc1" 

oWord.ActiveDocument.Close

oWord.Quit

Set oWord = Nothing

К сожалению, должен отметить, что обращение к подпрограмме DocSubProc2 с передачей параметров у меня не получилось.

Кстати, в среде VB также можно осуществлять динамическое подключение ActiveX DLL, то есть программным способом делать ссылки, обычно выполняемые в окне Project/References. Для этого нужно сначала вручную сделать ссылку на Microsoft Word 9.0 Object Library, чтобы получить доступ к объекту VBE (не очень понятно, почему такой глобальный объект находится в библиотеке отдельного приложения, а не в Office Object Library), и выполнить, например, такой код:

VBE.ActiveVBProject.References.AddFromFile "c:\window95\system\msbind.dll"

Заключение

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

  1. Прямое обращение к процедуре Call SomeProcedure. Для этого необходимо установить в основном документе ссылку на вспомогательный. Сделать это можно с помощью трех вариантов.
    1. Подключить шаблон с помощью окна Template and Add-Ins в среде Word.
    2. Подключить любой шаблон или документ (но с именем проекта) с помощью окна Tools/References в среде VBA.
    3. Программно подключить проект. Но в этом случае возникают проблемы с неопределенностью ссылок, которые можно решить, установив режим компиляции Compile On Demand (Tools/Options/General).
    4. Список всех доступных проектов для данного документа виден в его строке References в окне Project.
  2. Использование метода Run, с помощью которого можно обратиться к макрокомандам (в Word 2000 - по всем глобальным процедурам) активного документа (его можно динамически переустанавливать) и всех глобальных шаблонов. Этот же метод позволяет обращаться к функциям Word-проектов из VB-приложения.
  3. Я искренне благодарю читателей, которые смогли добраться до конца этой статьи. Ее появление было связано с попыткой ответа на вопрос читателя: «зачем нужен метод Run». И вот что из этого получилось...
  4. Понятно, что MS Office 2000 обладает значительными возможностями по созданию систем на основе работы взаимосвязанных документов и их проектов. Но все же пока этот механизм представляется довольно запутанным и не до конца отработанным. Например, я не упоминал, что некоторые свойства, появляющиеся в подсказках, просто не работают. И сейчас, после нескольких дней борьбы с объединением проектов, мне не понятен ответ на такой вопрос: зачем вообще нужен механизм программного подключения проектов, если в этом случае нельзя использовать прямое обращение к процедурам (если только не отключен режим Compile On Demand), а для применения более универсального метода Run создавать ссылку вообще не нужно?

Листинг. Процедуры CreateReference и Delete Reference

Function CreateReference&(FileName$, ProjectName$)

   ' Создание ссылки на указанный файл 

   ' ВХОД: FileName$ - имя файла 

   ' ВЫХОД: 

   ' CreateReference& = 0 - файл подключен 

   '                                 = -1 - был подключен ранее 

   '                                        ProjectName$ - имя проекта 

   '                                 = другое - код ошибки 

   '                                  (ProjectName$ = "" 

   Dim Ref As Object 

' On Error GoTo CreateReferenceError 

   Set Ref = TestReference(FileName$) 

   If Ref Is Nothing Then ' подключаем файл 

     Set Ref = ActiveDocument.VBProject.References.AddFromFile(FileName)  

     CreateReference = 0 

   Else ' уже подключен 

     CreateReference = -1 

   End If 

   ' определяем имя проекта 

   ProjectName$ = Ref.Name ' позднее связывание!!!

Exit Function 

  

CreateReferenceError: ' ошибка при подключении

  'например, нелегальный тип файла или дубликатное имя проекта

    CreateReference = Err

    ProjectName$ = ""

End Function



Function DeleteReference(FileName$) As Boolean

  ' Удаление ссылки на указанный файл

  ' ВХОД: FileName$ - имя файла

  ' ВЫХОД: DeleteReference = True - ссылка была

  '                        = False - ссылки не было

  Dim Ref As Object

  Set Ref = TestReference(FileName$)

  If Not (Ref Is Nothing) Then ' отключаем ссылку

    ActiveDocument.VBProject.References.Remove Ref

  End If

  DeleteReference = Not (Ref Is Nothing)

End Function

Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=3888