Основы офисного программирования и язык VBA

Документы и проекты

Разбить на страницы
Показывать лекцию целиком

Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: BookOne, BookTwo, BookThree, DocOne, DocFour.

Проектирование документов

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

  • Документ (система документов) содержит "пустой" программный проект. В большинстве случаев такие документы создают конечные пользователи, которые могут не иметь понятия о программировании и вообще не знать, что существует возможность присоединения программного проекта к документам Office 2000. В этом весьма типичном случае каждый документ представляет стандартный, предоставляемый по умолчанию документ Office 2000, без всякой его дополнительной настройки.
  • Документ служит обложкой для программного проекта. Такие документы создаются программистами. Это тоже возможная ситуация, когда программист хочет написать свое типично программистское приложение. Ему не требуются возможности офисной среды, все, что ему нужно, - это привычный язык VBA. Он мог бы написать это приложение на VB или другом языке программирования, но предпочитает оставаться в офисной среде.
  • Основная цель этой лекции рассмотреть, как связаны между собой программные проекты системы документов, какова структура программного проекта одного документа, его основные компоненты.

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

  • Разработка клиентской части "клиент - серверных" приложений, рассчитанных на работу большого числа пользователей с серьезной базой данных, такой как Microsoft SQL Server или Oracle. Заметим, что в Office Developer 2000 включены специальные средства разработки, облегчающие создание подобных приложений.
  • Разработка документов для совместного использования в Интернете. Во многом сложность этой задачи объясняется ее новизной. Опять - таки, в Office 2000 включены новые средства, облегчающие создание таких документов.
  • Разработка большой системы документов, обеспечивающей "полную" автоматизацию деятельности офиса.
  • Есть еще одно измерение в разработке документов Office 2000. Оно связано с разработкой документов специального вида. Сюда относится разработка: шаблонов, Мастеров (Wizards), компонент, расширяющих функциональные возможности других документов. Эти компоненты могут быть двух видов - AddIns и ComAddIns. Дадим краткую характеристику этим видам документов:

  • Шаблоны - это наиболее простой из специальных случаев. Шаблоны являются заготовками, на базе которых можно создать семейство близких документов. Шаблон задает общие свойства семейства и требуется лишь сравнительно небольшая настройка для получения конкретного документа. Как правило, рекомендуется создавать шаблоны и открывать документы на основе того или иного шаблона. Типичным и хорошо известным является шаблон Normal.dot, на базе которого открываются стандартные документы Word.
  • Мастера обеспечивают некоторый пошаговый процесс достижения цели. Они "ведут" пользователя шаг за шагом, пока конечная цель не будет достигнута. На каждом шаге, пользователь в процессе диалога с Мастером, задает информацию, необходимую для перехода к следующему шагу. Как правило, "хороший" Мастер облегчает и контролирует ввод данных, предоставляет необходимые справки, позволяет провести откат к предыдущему шагу, отменив ранее сделанные установки. Мастера широко используются в Office 2000 для решения тех или иных частных задач. Это средство становится привычным для любых офисных приложений. Мастера, которые решают общие задачи и могут быть полезными в разных документах, оформляются как компоненты.
  • AddIns являются компонентами, специфическими для того или иного приложения Office 2000. Один AddIn может использоваться, например, только в документах Word, другой - в документах Excel.
  • Com AddIns - то компоненты, которые могут использоваться в разных приложениях. Достаточно, чтобы эти приложения допускали использование Com - объектов, построенных на основе Com - модели компонентного программирования. Com AddIns отличается от ActiveX объектов тем, что с программной точки зрения представляет DLL - динамически загружаемую библиотеку, в то время как ActiveX - это исполняемые файлы. Заметим, что ранее эти компоненты не могли быть созданы в офисной среде, необходимо было использовать для их создания VC++, VB или другие языки программирования, допускающие создание подобных компонент. Теперь и в Office 2000 Developer включены средства, допускающие их разработку.
  • Прежде, чем перейти к рассмотрению вопроса о том, что такое проект документа, скажем несколько слов о проектировании документа. Хотя эти термины близки по звучанию, но различны по смыслу. Тема проектирования документа (системы документов), его "жизненного цикла" лежит вне нашего рассмотрения, но хоть несколько слов ей уделить нужно и мы решили, что это можно сделать здесь в разделе о проектах документах.

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

    Проектирование  <=> Разработка  <=> Отладка  <=> 
    Развертывание и Сопровождение  <=> Модификация

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

  • Уделите проектированию самое пристальное внимание. Успех дела во многом определяется первым этапом. Нет смысла торопиться с переходом на последующие этапы, пока не составлены ясные и четкие спецификации. Ошибки этого этапа самые дорогие и плохо исправляемые. Для большого проекта, который ведет коллектив исполнителей, должны быть предусмотрены специальные средства ведения проекта.
  • Еще одно важное правило этапа проектирования. Помните о тех, для кого разрабатывается программный продукт. Идите "в люди", чтобы понять, что нужно делать. Даже студент, выполняющий учебную работу должен знать вкусы преподавателя, которому он собирается эту работу сдавать. Что уж говорить о серьезных проектах.
  • Не начинайте разработку "с нуля". Офисная среда в этом отношении уникальна, она предоставляет разработчику уже готовые каркасы документов, если хотите, в принудительном порядке. Кроме того, Вы легко можете использовать в новых разработках, как свой собственный инструментарий, так и средства специальных фирм, производящих в большом числе различные шаблоны, Мастеров, AddIns, DLL, ActiveX и Com AddIns компоненты. Работая над одним проектом, думайте о будущем, - создавайте компоненты, допускающие их использование в других проектах.
  • Следующий важный этап - это отладка. Человек (коллектив), занимающийся отладкой, это оппонент разработчика. Даже, если всю работу ведет один человек, на этапе отладки он должен стать другим, поменять цель, - стараться разрушить проект, найти ситуации, в которых проект ведет себя не так, как это предусмотрено замыслами разработчика. Помните, не бывает проектов без ошибок и каждая последняя найденная ошибка является предпоследней.
  • Говоря об отладке, следует обратить Ваше внимание на то, что, зачастую, не так важно исправить найденную ошибку, как точно ее специфицировать. Знаете ли Вы "Закон Чечако", который гласит, что у "новичков" любая система работает неправильно, - или зависает или дает неверные результаты. Все дело в том, что "новички" плохо знакомы со спецификациями системы. Нарушая "законы жизни" системы, они попадают в неприятные ситуации. Сформулирую тезис, дополняющий основной закон отладки из предыдущего пункта. Не бывает системы с ошибками, бывают пользователи, не знающие спецификаций системы.
  • Создавайте как можно раньше прототип своей системы и передавайте его пользователям в опытную эксплуатацию. Это поможет устранить множество недостатков и ошибок в заключительной версии программного продукта.

    Замечание:

    Хочу привести одно из свежих впечатлений о процессе отладки. Будучи бета - тестером Office 2000, я поразился, сколь мощной является построенная Microsoft система отладки своих продуктов. Специальный узел в Интернете для тестеров, конференции для них, удобный для тестеров способ публикации своих отчетов о найденных ошибках, практически моментальная реакция разработчиков на каждую из найденных ошибок.

  • Говоря о последующих этапах, замечу только, что в Office 2000 входят специальные средства для создания собственного Setup' а, что Администратору Office 2000 предоставляются специальные средства для развертывания Office 2000 в сети. В общем, сопровождение обеспечивает долгую жизнь программному продукту, так что этим этапам необходимо уделять немалое внимание. Ну и, конечно, особый вопрос это обеспечение безопасности, надежности и защиты данных от несанкционированного доступа.
  • Документ и его программный проект

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

    Каждый вновь создаваемый документ обладает определенной архитектурой, поскольку строится на основе каркаса документов, заданного совокупностью библиотек Office 2000. Каркас каждого конкретного документа определяется, прежде всего, тем, в каком приложении создается документ, - корневым объектом Application. Частью архитектуры вновь создаваемого документа является и создаваемый по умолчанию программный проект. Здесь и в дальнейшем, если только не будет делаться специальных оговорок, под программным проектом понимается совокупность модулей. Модули, составляющие программный проект, могут быть следующих типов:

  • Модули, связанные с объектами приложения, реагирующими на события
  • Программные модули, создаваемые программистом, так называемые стандартные модули.
  • Модули классов, создаваемые программистом.
  • Модули макросов, создаваемые Macrorecorder.
  • Модули - обработчики событий

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

    Когда создается документ Word, то появляется один такой объект с именем ThisDocument, задающий сам документ. При создании документа Excel наряду с объектом ThisWorkbook, задающим рабочую книгу, появляется коллекция объектов Sheets, задающих каждую страницу этой книги. Все упомянутые объекты могут реагировать на события, они являются основными объектами, с ними и связываются модули. Но, заметьте, при создании презентации в приложении Power Point объект ThisPresentation, задающий презентацию, и связанный с ним модуль автоматически не появляется. В этот момент должен возникнуть вопрос, а почему? Дело в том, что объект класса Presentation не реагирует на события, следовательно, и модуль не может появиться. Это же касается и объектов класса Slide, - у них тоже нет событий.

    Есть и другие объекты, с которыми логично было бы связать модули, но по умолчанию этого не делается. В каждом из приложений, наряду с объектом, задающим сам объект - презентацию, документ, рабочую книгу, присутствует и корневой объект Application, задающий само приложение. Этот объект, как известно, также реагирует на события. Почему же нет модуля, ему соответствующего? Здесь есть одна тонкость. Создаваемые по умолчанию объекты Application не реагируют на события. На события реагируют объекты специального класса, - ApplicationWithEvents. Чуть позже мы рассмотрим, как создаются такие объекты и как появляются для них соответствующие модули.

    До сих пор мы говорили о модулях, создаваемых по умолчанию для новых документов. Но такие модули могут появляться и в процессе работы над существующим документом. Наиболее известным видом таких модулей являются модули форм (диалоговых окон), составляющие в проекте отдельную папку Forms. Всякий раз, когда Вы добавляете в проект новую форму, появляется модуль, связанный с этой формой. Есть и другие ситуации, при которых появляются новые модули проекта, например, при добавлении нового листа в книгу Excel, появляется и соответствующий ему модуль.

    Далеко не со всяким объектом, который может реагировать на события, связывается модуль. Для всех элементов управления ( Controls ), размещаемых в форме или непосредственно в документе Word, листе рабочей книги, слайде презентации, соответствующие обработчики появятся в модуле, связанном с "родительским объектом". Чуть выше мы сказали, что объекты класса Slide не имеют событий и потому с ними не связаны модули в момент создания презентации. Сейчас мы опровергнем это утверждение, уточним его. Если на слайде размещаются элементы управления - кнопки списки и прочее, то появляется модуль, связанный с этим слайдом, и обработчики событий, связанных с элементами управления размещаются в этом модуле.

    Есть еще одна группа объектов, имеющих обработчиков событий в обязательном порядке, но не имеющая "своих" модулей. Возможно, Вы уже догадались, что речь идет о командах меню, командных кнопках и других элементах управления, размещаемых на панелях - объектах класса CommandBar. Макросы, задающие обработку событий, связанных с этими элементами, помещаются в стандартные модули.

    Стандартные модули

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

    Стандартные модули - это основной вид модулей. Большую часть всех процедур и функций, которые пишет программист, следует размещать в стандартных модулях. Разумно иметь не один большой стандартный модуль, а некоторое множество относительно небольших модулей. В один модуль следует помещать набор функций, совместно вызывающих друг друга, связанных общей темой, общей функциональной направленностью. В модулях первого типа, связанных с объектами, следует помещать только те процедуры и функции, которые непосредственно обрабатывают события. Все общие процедуры следует выносить в стандартные модули. Вот три основные причины, по которым следует создавать небольшие стандартные модули:

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

    Замечание:

    Опцию "On demand" можно включить или выключить на вкладке General в меню VBE Tools|Options. Если временная эффективность критична для Вас, и Вам требуется уменьшить время работы, то следует провести соответствующие эксперименты с опциями компиляции на этой вкладке, а также подобрать наилучшую структуру модулей проекта.

  • Переиспользование (Reusability). Если стандартный модуль содержит процедуры общего назначения, то велика вероятность его дальнейшего использования и в других документах. Заметьте, что стандартные модули легко экспортировать и импортировать. Стандартный модуль VBA легко встроить не только в любой другой документ Office 2000, но и в чистый VB, поскольку экспорт - импорт модулей между проектами VBA и VB происходит также как и внутри VBA.
  • Понимание и Читабельность. Небольшие модули, решающие определенную задачу, гораздо легче подаются чтению с пониманием смысла данных и процедур их обработки. Конечно, для достижения этой важной цели следует использовать и другие известные приемы - мнемонические имена переменных, комментарии, структуризацию текста модуля.
  • Итак, подводя итог, заметим, что разумно иметь набор стандартных модулей, располагая в них все содержательные процедуры обработки данных документа. И, конечно, если таких процедур много, то модули должны быть специализированными, объединяя под одной крышей данные и функционально связанные с ними процедуры и функции. Хотя все модули имеют одинаковый уровень иерархии, реально всегда выделяется головной модуль, в котором следует сосредоточить описание глобальных переменных, используемых всеми модулями документа. Во многих ситуациях стандартный модуль следует оформить как модуль класса.

    Модули классов

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

    Модуль макросов

    Этот модуль создается автоматически при первом вызове Macrorecoder. В приложении Word все созданные Macrorecoder макросы размещаются в модуле с именем NewMacros. При создании макроса требуется указать, в модуль какого из проектов - Normal.dot или проекта, связанного с документом, должен быть записан макрос. В приложении Excel каждый новый макрос записывается в отдельный модуль. Это не всегда удобно и, зачастую, руками макросы объединяются в одном модуле, особенно, если все они решают некоторую общую задачу. Единственное отличие модулей макросов от стандартных модулей состоит в том, что они создаются системой, а не программистом. Поскольку это не столь существенно, то мы не будем в дальнейшем выделять этот тип модулей, и будем рассматривать их далее как стандартные модули.

    Структура модуля. Окно проекта и Окно кода

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

  • Раздел объявлений переменных уровня модуля. Этот раздел идет первым и автоматически отделяется чертой от раздела методов. Всегда можно добавить новое объявление переменной в этот раздел. Область действия таких переменных распространяется на весь модуль, но она может быть и расширена. Подробно об этом мы поговорим чуть позже.
  • Раздел методов модуля. В этом разделе располагаются процедуры и функции. С точки зрения синтаксиса ничего другого кроме процедур и функций в этом разделе быть не может. Конечно, есть, в том числе, и синтаксическая разница между макросом, методом - обработчиком события и, например, методом, представляющим процедуру с параметрами общего назначения. Тем не менее, метод это всегда либо процедура ( Sub ) либо функция ( Function ).
  • Но коль скоро зашла речь о классификации методов, то давайте уточним определения некоторых, часто встречающихся понятий. Начнем с понятия "макрос". Под макросом мы будем понимать любую процедуру без параметров. Отсутствие параметров - это главный отличительный признак макроса. Чаще всего, о макросах идет речь в следующих контекстах:

  • Макрос - это результат работы Macrorecoder.
  • Макрос - это обработчик события элементов командной панели - команд меню, командных кнопок, всего того, что входит в коллекцию элементов объекта CommandBar.
  • Особый случай составляют макросы Access. Наше определение к ним не относится, и мы всегда будем специально оговариваться, когда речь будет идти о таких макросах.
  • Элементы командной панели имеют по существу одно событие и один метод, вызываемый в ответ на появление события. Этот метод, обрабатывающий событие, мы называем макросом. Но есть и другие объекты, у которых много событий и соответственно много методов, вызываемых в ответ на возникновение того или иного события. Такие методы мы называем обработчиками событий. Обработчики событий, как правило, макросы, но иногда они могут иметь параметры. Есть еще одно существенное ограничение, накладываемое на правило построения имени обработчика. Имя является двойным и составляется из имени объекта и имени события. Два имени разделяются (объединяются) знаком подчеркивания.

    Окно проекта

    Давайте немного поговорим о том, как выглядит работа с модулями в среде Редактора VBE. Взгляните на рисунок 2.1, где показаны два основных окна редактора, о которых сейчас пойдет речь: окно проекта и окно кода.

    (рис 2.1) Окно проекта и окно кода

    Окно проекта или, как еще говорят, Проводник Проекта (Project Explorer) отображает структуру проекта в привычном для всех Проводников виде - дерева раскрывающихся папок. В этом окне отображается программные проекты всех открытых документов. Так что, если Вы работаете с семейством документов, то Проводник покажет полностью программный проект всего семейства. Специальная папка References покажет взаимные связи проектов отдельных документов.

    Давайте разберемся в структуре проекта, показанного на рисунке 2.1. Этот проект связан с рабочей книгой Excel с именем MasterFNew. В данном случае мы имеем дело с единственным документом, хотя в окне показаны два проекта. Дело в том, что наш документ работает с Решателем ( Solver ). В Office 2000 Решатель представляет AddIn, ссылку на который надо включить в меню References, что и приводит к появлению соответствующего проекта в Проводнике. Заметьте, проект Solver закрыт паролем от просмотра, что не позволяет просмотреть его структуру и тем более код его модулей. Что же касается проекта нашего документа, то его структура раскрыта полностью. Он содержит:

  • 5 модулей, связанных с такими объектами, как рабочая книга, задающая сам документ, и четырьмя страницами этой книги. В окне отображаются названия этих страниц.
  • 3 модуля, связанных с формами - объектами класса Form.
  • 7 стандартных модулей. Точнее, шесть стандартных модулей и модуль Others, содержащий макросы. Обратите внимание, число стандартных модулей в этом проекте достаточно велико. Модуль Main играет роль основного модуля проекта и cодержит описание глобальных переменных всего проекта.
  • Папка References содержит ссылку на проект Solver.
  • Проект данного документа довольно типичен и содержит все типы модулей за исключением модулей классов.

    Свойства проекта

    Проект имеет несколько терминальных свойств, которые можно задать в окне "Project Properties" (Свойства Проекта), оно выводится после щелчка правой кнопкой мыши по названию проекта или одной из его папок в окне проектов и последующего выбора из контекстного меню команды Project Properties.

    (рис 2.2) Окно свойств проекта

    В окне две вкладки General и Protection. На вкладке General задается имя проекта, его описание, указываются файлы справки и константы условной компиляции, относящиеся ко всему проекту. На рисунке можно видеть, как заполнены некоторые из полей, задающие свойства проекта.

    Имя проекта

    Имя проекта используется не только для идентификации проекта в окне просмотра. Оно играет важную роль в тех случаях, когда документ является частью общей системы документов или используется, как компонент, вызываемый в других документах ( COM объект). В последнем случае имя проекта идентифицирует компонент в реестре Windows. Во всех случаях проект можно рассматривать как компонент, отвечающий требованиям COM модели, Согласно принятой в компонентном программировании терминологии, проект экспонирует, то есть позволяет использовать, свои открытые Public - элементы. Имя проекта играет важную роль в этом процессе, - оно является именем библиотеки типов TypeLib. Эта библиотека содержит описание объектов и интерфейсов, которые поддерживает компонент. У имени проекта есть еще одна важная роль, оно используется также при построении полного имени открытых компонентов проекта. Всякий раз, когда в проекте A надо сослаться на Public переменную или Public метод проекта B, необходимо использовать полное имя открытого компонента:

    имя_проекта.имя_модуля.имя_открытого_компонента

    Заметьте, при именовании используется имя проекта, а не имя документа, содержащего проект.

    Защита проекта

    Вкладка Protection позволяет защитить проект от просмотра и редактирования. В большинстве случаев при передаче документа пользователям, проект должен быть защищен как от несанкционированного просмотра его модулей, так и коррекции программного текста. Включив флажок "Lock project for viewing", Вы закрываете проект, его структура будет недоступна для просмотра, если неизвестен пароль. Мы описывали такую ситуацию ранее, когда говорили о проекте Solver, связанном с Решателем, вызываемым в Excel. Для всех нас, конечных пользователей, отсутствует возможность просмотреть реализацию модулей Решателя и тем более пытаться корректировать тексты. Чтобы получить доступ к закрытым проектам, нужно знать пароль проекта. Этот пароль и его подтверждение задается в соответствующих окнах вкладки Protection.

    Окно кода

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

    Левый раскрывающийся список окна кода показывает все объекты, выбранного модуля, позволяя понять, какие объекты вложены в соответствующий основной объект. На рисунке 2.1 зафиксирован момент выбора модуля, связанного с объектом Sheet2 ("ПостановкаЗадачи"). Можно видеть, что на странице расположено довольно много элементов управления. Если выбрать в левом раскрывающемся списке один из объектов, как показано на рисунке, то правый раскрывающийся список отобразит список всех возможных событий данного объекта. Если теперь в правом списке выбрать одно из возможных событий, то в окне кода автоматически будет создана заготовка обработчика этого события. Ее можно наполнить содержанием. Все выбранные таким образом события считаются активными, в правом списке событий они выделены полужирным шрифтом, при их возникновении будет послано сообщение объекту, обработчик будет выполняться, даже если он пустой, - заготовка не была наполнена содержанием.

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

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

    Еще раз о "переиспользовании" модулей

    Пора, пожалуй, прервать плавный ход повествования и привести хоть небольшой фрагмент кода, иллюстрирующий высказанные в этой лекции утверждения. Мы отмечали роль стандартных модулей, одно из достоинств которых состоит в возможности их многократного использования. Рассмотрим следующую, часто возникающую на практике задачу. Пусть есть форма, содержащая два списка - элементы управления типа ListBox. При открытии формы пользователь может выбирать некоторые элементы в одном списке и переносить их в другой. Возникает вопрос, как организовать работу по переносу элементов, какие должны быть обработчики событий, где их разместить, где должна быть сосредоточена вся выполняемая работа по переносу элементов из списка в список в ответ на выбор пользователя. Решение кажется естественным и единственным, - все обработчики и вся обработка должна быть сосредоточена в модуле, связанном с формой. Действительно, для обработчиков событий это единственная возможность и поэтому кажется естественным, что и вся обработка должна быть сосредоточена в этом же модуле. Но, может быть, более эффективно, чтобы формальный обработчик события, находящийся в модуле формы, вызывал обычную процедуру с параметрами, которая может находиться в стандартном модуле общего назначения. Такая процедура может многократно использоваться разными формами, где есть списки и где возникает подобная задача.

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

    (рис 2.3) Форма TwoListsForm документа Excel

    В форме есть два списка и 4 командные кнопки. Перенос элементов из одного списка в другой можно осуществлять тремя разными способами:

  • Двойным щелчком по элементу.
  • Выделить предварительно один или несколько элементов списка и затем щелкнуть командную кнопку со значком ">" ("<").
  • Для переноса всех элементов списка нет необходимости в их выделении, - достаточно щелкнуть командную кнопку со значком ">>" ("<<").
  • Направление переноса задают командные кнопки. Знак на кнопке меняется в зависимости от того, какой из списков выбран - левый или правый. Кнопка "OK" на форме переносит данные из списка на лист Excel, кнопка "Cancel" завершает работу с формой без переноса данных.

    Работу обработчиков событий, возникающих при работе с объектами формы, можно было бы без сомнения полностью описать в модуле, связанном с формой. Но мы поступили по-иному и создали стандартный модуль с именем ToolsMod, в который и вынесли содержательную часть обработки. Так что у нас появилось два модуля, - в одном находятся обработчики события, в другом - стандартном сосредоточен содержательный код, выполняющий обработку. Вот код модуля TwoLists, связанного с нашей формой, и содержащий обработчики событий:

    Option Explicit
    
    Private Sub CommandButton1_Click()
    	'Обработчик события Click кнопки "> <"
        'Выборочный обмен данными между n- колоночными списками:
        'ListBox1  <--> ListBox2
    
    If CommandButton1.Caption = ">" Then
        Call MoveSelectedItems(ListBox1.ColumnCount, ListBox1, ListBox2)
    Else
        Call MoveSelectedItems(ListBox2.ColumnCount, ListBox2, ListBox1)
    End If
    End Sub
    
    Private Sub CommandButton2_Click()
        'Обработчик события Click кнопки ">> <<"
        'Перенос всех данных из одного n-колоночного списка
        'в конец другого, возможно, не пустого списка: ListBox1 <--> ListBox2
    
    If CommandButton2.Caption = ">>" Then
        Call MoveAllItems(ListBox1.ColumnCount, ListBox1, ListBox2)
    Else
        Call MoveAllItems(ListBox2.ColumnCount, ListBox2, ListBox1)
    End If
    End Sub
    
    Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
        'Обработчик события DblClick левого списка ListBox1 (имеет параметры!)
        'При двойном щелчке выбранный элемент одного n-колоночного списка
        'переносится в конец другого списка
        
        Call MoveSelectedItems(ListBox1.ColumnCount, ListBox1, ListBox2)
    End Sub
    
    Private Sub ListBox2_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
        'Обработчик события DblClick правого списка ListBox2
        'При двойном щелчке выбранный элемент одного n-колоночного списка
        'переносится в конец другого списка
        
        Call MoveSelectedItems(ListBox2.ColumnCount, ListBox2, ListBox1)
    End Sub
    
    Private Sub CommandButton5_Click()
        'Обработчик события Click кнопки "OK"
        'Перенос данных из списка на лист Excel
        'В область, заданную ячейкой с именем "Dom"
        
        Call MoveListToRange(ListBox2.ColumnCount, ListBox2, "Dom")
        Me.Hide
    End Sub
    
    Private Sub CommandButton6_Click()
        'Обработчик события Click кнопки "Cancel"
        Me.Hide
    End Sub
    
    Private Sub ListBox1_Enter()
        'Обработчик события Enter, возникающего при получении фокуса
        
        CommandButton1.Caption = ">"
        CommandButton2.Caption = ">>"
    End Sub
    
    Private Sub ListBox2_Enter()
        'Обработчик события Enter, возникающего при получении фокуса
        
        CommandButton1.Caption = "<"
        CommandButton2.Caption = "<<"
    End Sub
    
    Private Sub UserForm_Initialize()
        'Обработчик события Initialize формы TwoListsForm
        'Заполнение списка ListBox1
        
        Dim MyArray(1 To 5, 1 To 2) As String
    
        MyArray(1, 1) = "Петров"	: 	MyArray(1, 2) = "Музыкант"
        MyArray(2, 1) = "Сергеев"	:	 MyArray(2, 2) = "Учитель"
        MyArray(3, 1) = "Гурина"	: 	MyArray(3, 2) = "Актриса"
        MyArray(4, 1) = "Водкин"	: 	MyArray(4, 2) = "Художник"
        MyArray(5, 1) = "Козина"	: 	MyArray(5, 2) = "Геолог"
    
        ListBox1.ColumnCount = 2	: 	ListBox2.ColumnCount = 2
        ListBox1.List() = MyArray
    End Sub

    Модуль TwoListsForm, связанный с формой, содержит 9 обработчиков событий, возникающих при работе с самой формой - ее инициализации, так и при работе пользователя с объектами, населяющими форму. Каждый из обработчиков выполняет свои специфические задачи. Когда пользователь переключается на работу с элементами левого или правого списка, то в тот момент, когда список получает фокус, возникает событие Enter. Обработчик события Enter изменяет заголовок у соответствующих командных кнопок, подготавливая, тем самым, передачу данных в нужном направлении. Такое автоматическое изменение направления передачи позволяет уберечь пользователя от ошибочных действий. Но, обратите внимание на главное в этом примере. Для удобства пользователя ему предоставлены три разных способа передачи данных из одного списка в другой. Поскольку обмен данными может вестись в двух направлениях, то при лобовом программировании нужно было бы написать 6 различных макросов. Мы же свели задачу к двум процедурам, вызываемых с разными значениями параметров. Тексты этих процедур мы поместили в стандартный модуль с именем ToolsMod, предполагая возможность его дальнейшего использования. Вот эти тексты:

    Option Explicit
    Public Sub MoveSelectedItems(ByVal n As Byte, ByVal ListBox1 As Object,  _
    			ByVal ListBox2 As Object)
        'Перемещает выделенные элементы первого списка в конец второго
        'с одновременным удалением данных из первого списка.
        'Оба списка имеют n столбцов.
    
    	Dim RowIndex1 As Byte, RowIndex2 As Byte, i As Byte, j As Byte
    
        'Выборочный обмен данными между списками: ListBox1 -> ListBox2
        With ListBox1
            RowIndex2 = ListBox2.ListCount
            RowIndex1 = 0
            For i = 0 To .ListCount - 1
            If .Selected(RowIndex1) Then
               'Создается элемент нового списка и заполняется его первый столбец
                ListBox2.AddItem .List(RowIndex1)
                'Заполняются остальные столбцы элемента списка
                For j = 1 To n - 1
                    ListBox2.Column(j, RowIndex2) = .Column(j, RowIndex1)
                Next j
                'Перемещенный элемент удаляется из списка
                .RemoveItem RowIndex1
                RowIndex2 = RowIndex2 + 1
             Else
                RowIndex1 = RowIndex1 + 1
             End If
            Next i
          End With
    End Sub
    
    Public Sub MoveAllItems(ByVal n As Byte, ByVal ListBox1 As Object,  _
    			ByVal ListBox2 As Object)
        ' Перемещает все элементы первого списка в конец второго, 
        ' возможно, не пустого списка с одновременным удалением данных из     
        ' первого списка. ListBox1 -> ListBox2
    
    Dim RowIndex1 As Integer, RowIndex2 As Integer, i As Byte
           RowIndex2 = ListBox2.ListCount
            For RowIndex1 = 0 To ListBox1.ListCount - 1
            With ListBox1
                ListBox2.AddItem .List(0)
                For i = 1 To n - 1
                    ListBox2.Column(i, RowIndex2) = .Column(i, 0)
                Next i
                RowIndex2 = RowIndex2 + 1
                'Перемещенный,- это всегда первый элемент,удаляется из списка
                .RemoveItem 0
            End With
            Next RowIndex1
    End Sub
    
    Public Sub MoveListToRange(ByVal n As Byte, List1 As Object, Dom As String)
        'List1 - объект типа ListBox, состоящий из n столбцов. 
        ' Его элементы переносятся в прямоугольную область активного листа,    
        ' Dom - задает имя ячейки, расположенной в левом верхнем углу этой         
        ' области.
    
    	Dim myr As Range
    	Dim i As Byte, j As Byte
    
        Set myr = Range(Dom)
        'Цикл по числу элементов списка. 
        For i = 0 To List1.ListCount - 1
            'Цикл по числу столбцов списка. 
    		 For j = 0 To n - 1
                myr.Offset(j, i) = List1.Column(j, i)
            Next j
        Next i
    End Sub
    
    Public Sub ClearRange(Dom As String)
        'Эта процедура очищает содержимое области листа рабочей книги,
        'заданной ячейкой с именем Dom
        
    Dim myr As Range, Row As Byte, Col As Byte
    
    Set myr = Range(Dom)
    Col = 0: Row = 0
      While myr.Offset(Row, Col) <> ""
        While myr.Offset(Row, Col) <> ""
            'Чистка содержимого
            myr.Offset(Row, Col).ClearContents
            Col = Col + 1
        Wend
        Row = Row + 1
        Col = 0
      Wend
    End Sub

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

    Завершая разговор о переиспользовании стандартных модулей, предлагаем взглянуть на форму, взятую из совсем другого документа - документа Word.

    (рис 2.4) Форма TwoLists, взятая из документа Word

    Хотя форма и похожа на форму, связанную с рабочей книгой Excel, но, обратите внимание, списки в ней другие, они состоят из одного столбца, а не из двух, как в прошлом случае. Тем не менее, мы спокойно выполнили операции экспорта - импорта стандартного модуля из Excel в Word и без всяких изменений использовали его процедуры MoveSelectedItems и MoveAllItems. Конечно, думая о возможности многократного использования этих процедур, мы заранее побеспокоились о возможности работы с произвольным числом столбцов в списках.

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

    Замечание:

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

    Public Sub MoveAllItems(ByVal n As Byte, ByVal ListBox1 As Object, _

    ByVal ListBox2 As Object)

    Вы видите, что тип формальных параметров для ListBox1 и ListBox2 задан как Object и, следовательно, является нетипизированным указателем. Это не очень удобно, так как лишает нас подсказки в момент написания процедуры. Возникает вопрос, можно ли указывать точный тип для подобных формальных параметров. Ответ зависит от приложения. При работе в приложении Word разрешается указывать настоящий, правильный тип ListBox и при этом передача параметров производится корректно. В приложении Excel при написании процедуры также можно указать тип ListBox, но возникнет ошибка в момент передачи параметров.

    В процессе тестирования Office 2000 мы указали разработчикам на это рассогласование, оно должно быть учтено в последующих версиях. Будем надеяться, что в будущем мы сможем в процедурах при объявлении формальных параметров, являющихся объектами, указывать их настоящий тип.

    Проект и область видимости

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

    Первое правило видимости гласит:

    "Все компоненты модуля видимы в пределах этого модуля".

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

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

    Второе правило видимости гласит:

    "Область видимости компонент модуля расширяется на весь проект, если компонент объявлен со спецификатором Public ".

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

    Если при объявлении переменных модуля спецификатор области видимости опущен и указано только ключевое слово Dim, то такие переменные считаются закрытыми, - действует спецификатор Private. Для методов спецификатор области видимости можно опускать. В этом случае действует следующее правило. Все методы стандартных модулей имеют по умолчанию спецификатор Public и являются доступными во всем проекте. Методы модулей - классов и модулей, связанных с объектами, по умолчанию являются закрытыми и имеют статус Private.

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

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

    Система документов и ее проект

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

    Оставим пока в стороне создание и использование AddIns, Com AddIns, DLL, ActiveX, Internet документов. Им будет посвящен особый разговор. Пока поговорим о более понятных случаях использования совокупности документов. Прежде всего, заметим, что корневой объект Application любого из приложения Office 2000 содержит коллекцию документов. Так что изначально предполагается возможность открытия в одном приложении нескольких документов и совместной работы с ними. Но совместная работа не ограничивается однотипными документами, принадлежащими одному приложению. Уже в первой лекции мы подробно, с примерами рассмотрели возможность создания и одновременной работы с несколькими объектами Application. Следовательно, всегда есть возможность открытия совокупности документов разного типа и совместной работы с ними, предоставляя пользователю возможность переключаться по ходу дела от одного документа к другому.

    Совместная работа с несколькими документами, возможно, разного типа типична для Office 2000. Программным проектом системы документов будем называть совокупность программных проектов всех документов, входящих в систему.

    Остается ответить на главный вопрос, - могут ли взаимодействовать между собой проекты отдельных документов. Можно ли из одного проекта вызывать процедуры стандартного модуля другого проекта, можно ли пользоваться объектами класса другого проекта? Можно ли иметь глобальные переменные уровня системы документов для передачи информации от одного документа к другому? На все эти вопросы существует один ответ, - Да! С программным проектом системы документов в Office 2000 достаточно просто работать, достаточно просто организовать нужные связи между программными проектами отдельных документов, нужно только уметь это делать.

    Организация системы документов

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

  • Связи документов "по вызову".
  • Связи проектов документов "по ссылке"
  • Два документа A и B связаны по вызову, если в одном из документов, например A, вызывается документ B. Документ A называют родителем, а документ B, соответственно, потомком. Совокупность связанных по вызову документов образует дерево связанных документов. Корень дерева - это тот первичный документ, из которого и начинаются вызовы других документов. Связи в дереве направлены от корня к потомкам.

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

    Проекты A и B связаны по ссылке, если один из проектов, например B, ссылается на проект A. Проект A называют родителем, а проект B, соответственно, потомком. Совокупность связанных по ссылке проектов образует дерево связанных проектов. Корень дерева - это тот первичный проект, на который ссылаются остальные проекты. Связи в дереве ссылок направлены от потомков к корню. Вообще говоря, проекты, связанные по ссылке, также могли бы организовывать более сложную структуру, чем дерево. Действительно, взаимные ссылки кажутся допустимыми. Но это не так. Помните, циклические ссылки проектов VBA документов не разрешены. Так что при разработке проекта системы документов Office 2000 исходите из древесной структуры организации связей между проектами. На рисунке 2.5 показаны три возможные структуры организации системы документов Office 2000.

    (рис 2.5) Три структуры организации системы документов

    Как организуются ссылки между проектами

    Рассмотрим теперь технический вопрос, как организовать ссылку из проекта B на проект A. Для этого нужно сделать две вещи, во-первых, дать собственные имена проектам, во-вторых, - включить ссылку в проекте B на проект A. Имя нужно дать, по крайней мере, родительскому проекту A. О том, как это делается, мы уже рассказали в разделе "Свойства проекта" этой лекции. На рис. 2.2 показано соответствующее диалоговое окно.

    Задание ссылки выполняется также стандартным образом. В окне Редактора Visual Basic нужно выбрать соответствующий проект, в нашем примере проект B, и из меню Tools выбрать команду References. Если документ, содержащий проект A открыт, то в окне ссылок наряду с другими возможными для ссылок компонентами появится флажок с именем проекта A, останется только его включить.

    Обмен информацией между документами

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

  • Выделим один из проектов, который будем называть основным, и в стандартном модуле этого проекта зададим множество Public переменных, в совокупности и определяющих общую для всей системы информацию. Систему документов спроектируем так, чтобы их проекты были связаны по ссылке, и корнем дерева был основной документ, хранящий глобальную информацию. В этом случае каждый проект будет иметь доступ к этой информации. Напомним, циклические ссылки между проектами не разрешены, отсюда следует, что нельзя часть информации поместить в проект А, часть в проект В и сделать эту информацию общедоступной для обоих проектов. Возможна только древесная организация хранения общей информации. Самая общая информация хранится в корневом проекте, в "дочерних" проектах хранится информация необходимая потомкам этих дочерей, и так до терминальных проектов.
  • Второй способ состоит в том, что общая для всех проектов информация размещается в виде списков Excel, в ячейках выделенной рабочей книги, играющей роль основного документа. Этот способ основан на том, что не только VBA обеспечивает взаимодействие проектов, возможно взаимодействие самих документов, ссылки между ними. Известно, что ссылки на ячейки книги Excel допустимы и широко распространены. Так что страницы Excel часто используются как удобное средство для передачи информации между документами. Чуть ниже на примере мы продемонстрируем этот способ передачи информации. Достоинство этого способа состоит и в том, что проекты могут и не быть связанными ссылками, более того, возможен взаимный обмен информацией.
  • Классическим способом хранения общей информации, особенно, в больших объемах, являются базы данных. Поэтому базу данных Access также можно использовать для организации информационного пула системы проектов.
  • Для полноты картины упомянем обычный текстовый файл.
  • Заканчивая обсуждение этого вопроса, еще раз подчеркнем, что способ передачи информации между проектами, основанный на общем информационном пуле, как и во всех случаях работы с глобальными переменными, является опасным. Поэтому объем этой информации не должен быть большим и легко контролируемым, так что по этим соображениям первые два способа являются наиболее предпочтительными, а программисты, скорее всего, выберут первый из них. В заключение, напомним, что передача информации между проектами через общую область важный, но не единственный способ организации взаимодействия. Основным и надежным способом передачи данных для программистов является вызов Public методов проекта, - передача им аргументов и получение результатов, то, что называется аппаратом формальных - фактических параметров.

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

    (рис 2.6) Открытие рабочей книги, содержащей ссылки

    Поскольку в данной рабочей книге есть ссылки на другие рабочие книги, то при открытии документа появляется сообщение о том, что, возможно, следует обновить документ, поскольку после его закрытия могли измениться документы, на которые он ссылается. На листе рабочей книги нашего примера расположена командная кнопка с надписью " ChooseBook ". Эту кнопку пользователь должен нажать в тот момент, когда он стоит на распутье, и должен выбрать, с какой следующей книгой он хочет работать. Вот текст обработчика события " Click ":

    Private Sub CommandButton5_Click()
        ChooseBook
    End Sub
    
    Public Sub ChooseBook()
     'Выбор книги, с которой будет работать пользователь
     Const PathDir As String = "e:\O2000\CD2000\Ch2\"
       Dim Answer As Variant
        Dim CurrentBook As String
        Dim Book As Workbook
        Dim Found As Boolean
        
        Answer = InputBox(prompt:= _
    	"Выберите книгу, с которой будете работать (1/2/3)", Default:=1)
        Select Case Answer
            Case 1
                CurrentBook = "BookOne.xls"
            Case 2
                CurrentBook = "BookTwo.xls"
            Case 3
                CurrentBook = "BookThree.xls"
            Case Else
                CurrentBook = "I don't know such book"
        End Select
        MsgBox ("Your choose is "  CurrentBook)
        'Проверяем есть ли уже книга в коллекции
        Found = False
        For Each Book In Workbooks
            If Book.Name = CurrentBook Then
                Found = True
                Exit For
            End If
        Next Book
        If Found Then
        'Активизируем выбранную книгу
            Workbooks(CurrentBook).Activate
            ElseIf CurrentBook = "I don't know such book" Then
                MsgBox ("I don't know such book")
            Else   'Добавляем книгу в коллекцию
                Workbooks.Open PathDir  CurrentBook
                Workbooks(CurrentBook).Activate
        End If
    End Sub

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

    Public Sub PrintGlobal()
        Dim MyrOne As Range, MyrTwo As Range, MyrThree As Range
        
        Debug.Print ("Работает процедура PrintOne")
        Debug.Print ("Печать глобальных переменных")
        Debug.Print (One  "-"  Two  "-"  Three)
    
        Debug.Print ("Допустимы ссылки на ячейки различных рабочих книг!")
        Set MyrOne = Range("[BookOne.xls]Sheet1!D1")
        Debug.Print MyrOne
        Set MyrTwo = Range("[BookTwo.xls]Sheet1!D1")
        Debug.Print MyrTwo
        Set MyrThree = Range("[BookThree.xls]Sheet1!D1")
        Debug.Print MyrThree
        MsgBox ("Everything is OK! Look at Immediate Window")
    End Sub

    Вот результаты отладочной печати в конце работы этой процедуры:

    Работает процедура PrintOne
    Печать глобальных переменных
    My value is One -My value is One -My value is One 
    Допустимы ссылки на ячейки различных рабочих книг!
    BookOne
    BookTwo
    BookThree

    Всем глобальным переменным предварительно было присвоено одно и то же значение. Книги BookOne, BookTwo, BookThree.xls на первой странице в ячейке D1 хранят свои названия, их и печатает наша процедура, вызываемая в проекте одной из этих книг.

    Система документов One - Two - Three

    Рассмотрим теперь систему документов из тех же трех книг BookOne, BookTwo, BookThree.xls, проекты которых связаны ссылками. Наша основная цель, - продемонстрировать на примере работу трех проектов с общей информацией и общими методами, вызываемыми во всех проектах.

    Вначале несколько слов об интерфейсе нашей системы документов. Мы сделали его достаточно простым. На первом рабочем листе каждой из трех книг расположены три командные кнопки: ChooseBook, ChangeGlobal, PrintGlobal.

    (рис 2.7) Командные кнопки управления системой документов

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

    Option Explicit
    'Глобальные переменные системы документов One - Two - Three
    Public One As String, Two As String, Three As String

    Три обработчика события Click для трех командных кнопок вызывают три общих метода ChooseBook, ChangeGlobal, PrintGlobal:

    Private Sub CommandButton5_Click()
        ChooseBook
    End Sub
    
    Private Sub CommandButton4_Click()
        ChangeGlobal
    End Sub
    
    Private Sub CommandButton3_Click()
        PrintGlobal
    End Sub

    Назначение процедуры ChooseBook и ее текст уже приведены. Заметим только, что эта общая процедура вызывается соответствующими обработчиками во всех трех модулях. Мы поместили ее не в стандартный модуль проекта BookOne, а в модуль, связанный с листом Sheet1 книги BookOne. Это, однако, ничуть не мешает ее вызову из других проектов. Процедура ChangeGlobal, помещена в стандартный модуль с именем ModuleOne. Она позволяет работать с глобальными переменными, и поскольку в каждом проекте работа с ними выполняется по-своему, то она не является общей процедурой. В каждом проекте есть свой вариант этой процедуры, который и вызывается обработчиком Click командной кнопки ChangeGlobal. Заметим, что когда речь идет об основном проекте, где описаны глобальные переменные, при обращении к ним не требуется использовать полные имена:

    Public Sub ChangeGlobal()
        One = "My value is One "
        Two = "My value is One "
        Three = "My value is One "
    End Sub

    В стандартный модуль ModuleOne мы поместили и процедуру PrintGlobal. Текст ее тоже уже приведен. Заметим, что в каждом проекте будет свой вариант этой процедуры, но в каждом из них на последнем этапе работы будет вызываться процедура PrintGlobal проекта BookOne. В стандартном модуле находится функция Plus1, которая будет вызываться из других проектов. Это очень простая функция, но она выполняет свою важную роль, демонстрируя передачу информации от проекта к проекту через аппарат формальных - фактических параметров процедур и функций. Вот ее текст:

    Public Function Plus1(ByVal X As Integer) As Integer
        Plus1 = X + 1
    End Function

    Проект BookTwo ссылается на проект BookOne, использует его общие переменные и вызывает его методы - ChooseBook, PrintGlobal, Plus1. Он является хранителем общей информации для проектов BookTwo и BookThree:

    Option Explicit
    'Глобальная переменная документов BookTwo, BookThree
    Public TwoThree As String

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

    Private Sub CommandButton1_Click()
        BookOne.Sheet1.ChooseBook
    End Sub 
    
    Public Sub ChangeGlobal()
    'Изменение значений глобальных переменных системы документов
        BookOne.ModuleOne.Two = "My value is Two "
        TwoThree = "My value is Two - Two "
    End Sub
    
    Public Sub PrintGlobal()
        Dim Number As Integer
        Debug.Print ("Работает процедура PrintTwo")
        Debug.Print ("Печать глобальных переменных")
        Debug.Print TwoThree
        'Вызов процедур и функций проекта BookOne
        Number = BookOne.ModuleOne.PLus1(1)
        Debug.Print ("Это книга "  Number)
        ChangeGlobal
        BookOne.ModuleOne.PrintGlobal
    End Sub

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

    Private Sub CommandButton1_Click()
        BookOne.Sheet1.ChooseBook
    End Sub
    
    Public Sub ChangeGlobal()
        'Изменение значений глобальных переменных системы
        BookOne.ModuleOne.Three = "My value is Three "
        BookTwo.ModuleTwo.TwoThree = "My value: Two-Three"
    End Sub
    
    Public Sub PrintGlobal()
        Dim Number As Integer
        Debug.Print ("Работает процедура PrintThree")
        Number = BookOne.ModuleOne.Plus1(2)
        Debug.Print ("Это книга "  Number)
        ChangeGlobal
        BookTwo.ModuleTwo.PrintGlobal
    End Sub

    Приведем результаты отладочной печати, полученные при нажатии кнопки PrintGlobal в книге BookThree:

    Работает процедура PrintThree
    Это книга 3
    Работает процедура PrintTwo
    Печать глобальных переменных
    My value: Two-Three
    Это книга 2
    Работает процедура PrintOne
    Печать глобальных переменных
    My value is One -My value is Two -My value is Three 
    Допустимы ссылки на ячейки различных рабочих книг!
    BookOne
    BookTwo
    BookThree

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

    (рис 2.8) Три проекта One - Two - Three
    Страницы:

    Программный код большинства примеров данной лекции можно найти в проектах, доступных для просмотра: BookOne, BookTwo, BookThree, DocOne, DocFour.

    Проектирование документов

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

  • Документ (система документов) содержит "пустой" программный проект. В большинстве случаев такие документы создают конечные пользователи, которые могут не иметь понятия о программировании и вообще не знать, что существует возможность присоединения программного проекта к документам Office 2000. В этом весьма типичном случае каждый документ представляет стандартный, предоставляемый по умолчанию документ Office 2000, без всякой его дополнительной настройки.
  • Документ служит обложкой для программного проекта. Такие документы создаются программистами. Это тоже возможная ситуация, когда программист хочет написать свое типично программистское приложение. Ему не требуются возможности офисной среды, все, что ему нужно, - это привычный язык VBA. Он мог бы написать это приложение на VB или другом языке программирования, но предпочитает оставаться в офисной среде.
  • Основная цель этой лекции рассмотреть, как связаны между собой программные проекты системы документов, какова структура программного проекта одного документа, его основные компоненты.

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

  • Разработка клиентской части "клиент - серверных" приложений, рассчитанных на работу большого числа пользователей с серьезной базой данных, такой как Microsoft SQL Server или Oracle. Заметим, что в Office Developer 2000 включены специальные средства разработки, облегчающие создание подобных приложений.
  • Разработка документов для совместного использования в Интернете. Во многом сложность этой задачи объясняется ее новизной. Опять - таки, в Office 2000 включены новые средства, облегчающие создание таких документов.
  • Разработка большой системы документов, обеспечивающей "полную" автоматизацию деятельности офиса.
  • Есть еще одно измерение в разработке документов Office 2000. Оно связано с разработкой документов специального вида. Сюда относится разработка: шаблонов, Мастеров (Wizards), компонент, расширяющих функциональные возможности других документов. Эти компоненты могут быть двух видов - AddIns и ComAddIns. Дадим краткую характеристику этим видам документов:

  • Шаблоны - это наиболее простой из специальных случаев. Шаблоны являются заготовками, на базе которых можно создать семейство близких документов. Шаблон задает общие свойства семейства и требуется лишь сравнительно небольшая настройка для получения конкретного документа. Как правило, рекомендуется создавать шаблоны и открывать документы на основе того или иного шаблона. Типичным и хорошо известным является шаблон Normal.dot, на базе которого открываются стандартные документы Word.
  • Мастера обеспечивают некоторый пошаговый процесс достижения цели. Они "ведут" пользователя шаг за шагом, пока конечная цель не будет достигнута. На каждом шаге, пользователь в процессе диалога с Мастером, задает информацию, необходимую для перехода к следующему шагу. Как правило, "хороший" Мастер облегчает и контролирует ввод данных, предоставляет необходимые справки, позволяет провести откат к предыдущему шагу, отменив ранее сделанные установки. Мастера широко используются в Office 2000 для решения тех или иных частных задач. Это средство становится привычным для любых офисных приложений. Мастера, которые решают общие задачи и могут быть полезными в разных документах, оформляются как компоненты.
  • AddIns являются компонентами, специфическими для того или иного приложения Office 2000. Один AddIn может использоваться, например, только в документах Word, другой - в документах Excel.
  • Com AddIns - то компоненты, которые могут использоваться в разных приложениях. Достаточно, чтобы эти приложения допускали использование Com - объектов, построенных на основе Com - модели компонентного программирования. Com AddIns отличается от ActiveX объектов тем, что с программной точки зрения представляет DLL - динамически загружаемую библиотеку, в то время как ActiveX - это исполняемые файлы. Заметим, что ранее эти компоненты не могли быть созданы в офисной среде, необходимо было использовать для их создания VC++, VB или другие языки программирования, допускающие создание подобных компонент. Теперь и в Office 2000 Developer включены средства, допускающие их разработку.
  • Прежде, чем перейти к рассмотрению вопроса о том, что такое проект документа, скажем несколько слов о проектировании документа. Хотя эти термины близки по звучанию, но различны по смыслу. Тема проектирования документа (системы документов), его "жизненного цикла" лежит вне нашего рассмотрения, но хоть несколько слов ей уделить нужно и мы решили, что это можно сделать здесь в разделе о проектах документах.

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

    Проектирование  <=> Разработка  <=> Отладка  <=> 
    Развертывание и Сопровождение  <=> Модификация

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

  • Уделите проектированию самое пристальное внимание. Успех дела во многом определяется первым этапом. Нет смысла торопиться с переходом на последующие этапы, пока не составлены ясные и четкие спецификации. Ошибки этого этапа самые дорогие и плохо исправляемые. Для большого проекта, который ведет коллектив исполнителей, должны быть предусмотрены специальные средства ведения проекта.
  • Еще одно важное правило этапа проектирования. Помните о тех, для кого разрабатывается программный продукт. Идите "в люди", чтобы понять, что нужно делать. Даже студент, выполняющий учебную работу должен знать вкусы преподавателя, которому он собирается эту работу сдавать. Что уж говорить о серьезных проектах.
  • Не начинайте разработку "с нуля". Офисная среда в этом отношении уникальна, она предоставляет разработчику уже готовые каркасы документов, если хотите, в принудительном порядке. Кроме того, Вы легко можете использовать в новых разработках, как свой собственный инструментарий, так и средства специальных фирм, производящих в большом числе различные шаблоны, Мастеров, AddIns, DLL, ActiveX и Com AddIns компоненты. Работая над одним проектом, думайте о будущем, - создавайте компоненты, допускающие их использование в других проектах.
  • Следующий важный этап - это отладка. Человек (коллектив), занимающийся отладкой, это оппонент разработчика. Даже, если всю работу ведет один человек, на этапе отладки он должен стать другим, поменять цель, - стараться разрушить проект, найти ситуации, в которых проект ведет себя не так, как это предусмотрено замыслами разработчика. Помните, не бывает проектов без ошибок и каждая последняя найденная ошибка является предпоследней.
  • Говоря об отладке, следует обратить Ваше внимание на то, что, зачастую, не так важно исправить найденную ошибку, как точно ее специфицировать. Знаете ли Вы "Закон Чечако", который гласит, что у "новичков" любая система работает неправильно, - или зависает или дает неверные результаты. Все дело в том, что "новички" плохо знакомы со спецификациями системы. Нарушая "законы жизни" системы, они попадают в неприятные ситуации. Сформулирую тезис, дополняющий основной закон отладки из предыдущего пункта. Не бывает системы с ошибками, бывают пользователи, не знающие спецификаций системы.
  • Создавайте как можно раньше прототип своей системы и передавайте его пользователям в опытную эксплуатацию. Это поможет устранить множество недостатков и ошибок в заключительной версии программного продукта.

    Замечание:

    Хочу привести одно из свежих впечатлений о процессе отладки. Будучи бета - тестером Office 2000, я поразился, сколь мощной является построенная Microsoft система отладки своих продуктов. Специальный узел в Интернете для тестеров, конференции для них, удобный для тестеров способ публикации своих отчетов о найденных ошибках, практически моментальная реакция разработчиков на каждую из найденных ошибок.

  • Говоря о последующих этапах, замечу только, что в Office 2000 входят специальные средства для создания собственного Setup' а, что Администратору Office 2000 предоставляются специальные средства для развертывания Office 2000 в сети. В общем, сопровождение обеспечивает долгую жизнь программному продукту, так что этим этапам необходимо уделять немалое внимание. Ну и, конечно, особый вопрос это обеспечение безопасности, надежности и защиты данных от несанкционированного доступа.
  • Документ и его программный проект

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

    Каждый вновь создаваемый документ обладает определенной архитектурой, поскольку строится на основе каркаса документов, заданного совокупностью библиотек Office 2000. Каркас каждого конкретного документа определяется, прежде всего, тем, в каком приложении создается документ, - корневым объектом Application. Частью архитектуры вновь создаваемого документа является и создаваемый по умолчанию программный проект. Здесь и в дальнейшем, если только не будет делаться специальных оговорок, под программным проектом понимается совокупность модулей. Модули, составляющие программный проект, могут быть следующих типов:

  • Модули, связанные с объектами приложения, реагирующими на события
  • Программные модули, создаваемые программистом, так называемые стандартные модули.
  • Модули классов, создаваемые программистом.
  • Модули макросов, создаваемые Macrorecorder.
  • Модули - обработчики событий

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

    Когда создается документ Word, то появляется один такой объект с именем ThisDocument, задающий сам документ. При создании документа Excel наряду с объектом ThisWorkbook, задающим рабочую книгу, появляется коллекция объектов Sheets, задающих каждую страницу этой книги. Все упомянутые объекты могут реагировать на события, они являются основными объектами, с ними и связываются модули. Но, заметьте, при создании презентации в приложении Power Point объект ThisPresentation, задающий презентацию, и связанный с ним модуль автоматически не появляется. В этот момент должен возникнуть вопрос, а почему? Дело в том, что объект класса Presentation не реагирует на события, следовательно, и модуль не может появиться. Это же касается и объектов класса Slide, - у них тоже нет событий.

    Есть и другие объекты, с которыми логично было бы связать модули, но по умолчанию этого не делается. В каждом из приложений, наряду с объектом, задающим сам объект - презентацию, документ, рабочую книгу, присутствует и корневой объект Application, задающий само приложение. Этот объект, как известно, также реагирует на события. Почему же нет модуля, ему соответствующего? Здесь есть одна тонкость. Создаваемые по умолчанию объекты Application не реагируют на события. На события реагируют объекты специального класса, - ApplicationWithEvents. Чуть позже мы рассмотрим, как создаются такие объекты и как появляются для них соответствующие модули.

    До сих пор мы говорили о модулях, создаваемых по умолчанию для новых документов. Но такие модули могут появляться и в процессе работы над существующим документом. Наиболее известным видом таких модулей являются модули форм (диалоговых окон), составляющие в проекте отдельную папку Forms. Всякий раз, когда Вы добавляете в проект новую форму, появляется модуль, связанный с этой формой. Есть и другие ситуации, при которых появляются новые модули проекта, например, при добавлении нового листа в книгу Excel, появляется и соответствующий ему модуль.

    Далеко не со всяким объектом, который может реагировать на события, связывается модуль. Для всех элементов управления ( Controls ), размещаемых в форме или непосредственно в документе Word, листе рабочей книги, слайде презентации, соответствующие обработчики появятся в модуле, связанном с "родительским объектом". Чуть выше мы сказали, что объекты класса Slide не имеют событий и потому с ними не связаны модули в момент создания презентации. Сейчас мы опровергнем это утверждение, уточним его. Если на слайде размещаются элементы управления - кнопки списки и прочее, то появляется модуль, связанный с этим слайдом, и обработчики событий, связанных с элементами управления размещаются в этом модуле.

    Есть еще одна группа объектов, имеющих обработчиков событий в обязательном порядке, но не имеющая "своих" модулей. Возможно, Вы уже догадались, что речь идет о командах меню, командных кнопках и других элементах управления, размещаемых на панелях - объектах класса CommandBar. Макросы, задающие обработку событий, связанных с этими элементами, помещаются в стандартные модули.

    Стандартные модули

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

    Стандартные модули - это основной вид модулей. Большую часть всех процедур и функций, которые пишет программист, следует размещать в стандартных модулях. Разумно иметь не один большой стандартный модуль, а некоторое множество относительно небольших модулей. В один модуль следует помещать набор функций, совместно вызывающих друг друга, связанных общей темой, общей функциональной направленностью. В модулях первого типа, связанных с объектами, следует помещать только те процедуры и функции, которые непосредственно обрабатывают события. Все общие процедуры следует выносить в стандартные модули. Вот три основные причины, по которым следует создавать небольшие стандартные модули:

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

    Замечание:

    Опцию "On demand" можно включить или выключить на вкладке General в меню VBE Tools|Options. Если временная эффективность критична для Вас, и Вам требуется уменьшить время работы, то следует провести соответствующие эксперименты с опциями компиляции на этой вкладке, а также подобрать наилучшую структуру модулей проекта.

  • Переиспользование (Reusability). Если стандартный модуль содержит процедуры общего назначения, то велика вероятность его дальнейшего использования и в других документах. Заметьте, что стандартные модули легко экспортировать и импортировать. Стандартный модуль VBA легко встроить не только в любой другой документ Office 2000, но и в чистый VB, поскольку экспорт - импорт модулей между проектами VBA и VB происходит также как и внутри VBA.
  • Понимание и Читабельность. Небольшие модули, решающие определенную задачу, гораздо легче подаются чтению с пониманием смысла данных и процедур их обработки. Конечно, для достижения этой важной цели следует использовать и другие известные приемы - мнемонические имена переменных, комментарии, структуризацию текста модуля.
  • Итак, подводя итог, заметим, что разумно иметь набор стандартных модулей, располагая в них все содержательные процедуры обработки данных документа. И, конечно, если таких процедур много, то модули должны быть специализированными, объединяя под одной крышей данные и функционально связанные с ними процедуры и функции. Хотя все модули имеют одинаковый уровень иерархии, реально всегда выделяется головной модуль, в котором следует сосредоточить описание глобальных переменных, используемых всеми модулями документа. Во многих ситуациях стандартный модуль следует оформить как модуль класса.

    Модули классов

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

    Модуль макросов

    Этот модуль создается автоматически при первом вызове Macrorecoder. В приложении Word все созданные Macrorecoder макросы размещаются в модуле с именем NewMacros. При создании макроса требуется указать, в модуль какого из проектов - Normal.dot или проекта, связанного с документом, должен быть записан макрос. В приложении Excel каждый новый макрос записывается в отдельный модуль. Это не всегда удобно и, зачастую, руками макросы объединяются в одном модуле, особенно, если все они решают некоторую общую задачу. Единственное отличие модулей макросов от стандартных модулей состоит в том, что они создаются системой, а не программистом. Поскольку это не столь существенно, то мы не будем в дальнейшем выделять этот тип модулей, и будем рассматривать их далее как стандартные модули.

    Структура модуля. Окно проекта и Окно кода

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

  • Раздел объявлений переменных уровня модуля. Этот раздел идет первым и автоматически отделяется чертой от раздела методов. Всегда можно добавить новое объявление переменной в этот раздел. Область действия таких переменных распространяется на весь модуль, но она может быть и расширена. Подробно об этом мы поговорим чуть позже.
  • Раздел методов модуля. В этом разделе располагаются процедуры и функции. С точки зрения синтаксиса ничего другого кроме процедур и функций в этом разделе быть не может. Конечно, есть, в том числе, и синтаксическая разница между макросом, методом - обработчиком события и, например, методом, представляющим процедуру с параметрами общего назначения. Тем не менее, метод это всегда либо процедура ( Sub ) либо функция ( Function ).
  • Но коль скоро зашла речь о классификации методов, то давайте уточним определения некоторых, часто встречающихся понятий. Начнем с понятия "макрос". Под макросом мы будем понимать любую процедуру без параметров. Отсутствие параметров - это главный отличительный признак макроса. Чаще всего, о макросах идет речь в следующих контекстах:

  • Макрос - это результат работы Macrorecoder.
  • Макрос - это обработчик события элементов командной панели - команд меню, командных кнопок, всего того, что входит в коллекцию элементов объекта CommandBar.
  • Особый случай составляют макросы Access. Наше определение к ним не относится, и мы всегда будем специально оговариваться, когда речь будет идти о таких макросах.
  • Элементы командной панели имеют по существу одно событие и один метод, вызываемый в ответ на появление события. Этот метод, обрабатывающий событие, мы называем макросом. Но есть и другие объекты, у которых много событий и соответственно много методов, вызываемых в ответ на возникновение того или иного события. Такие методы мы называем обработчиками событий. Обработчики событий, как правило, макросы, но иногда они могут иметь параметры. Есть еще одно существенное ограничение, накладываемое на правило построения имени обработчика. Имя является двойным и составляется из имени объекта и имени события. Два имени разделяются (объединяются) знаком подчеркивания.

    Окно проекта

    Давайте немного поговорим о том, как выглядит работа с модулями в среде Редактора VBE. Взгляните на рисунок 2.1, где показаны два основных окна редактора, о которых сейчас пойдет речь: окно проекта и окно кода.

    (рис 2.1) Окно проекта и окно кода

    Окно проекта или, как еще говорят, Проводник Проекта (Project Explorer) отображает структуру проекта в привычном для всех Проводников виде - дерева раскрывающихся папок. В этом окне отображается программные проекты всех открытых документов. Так что, если Вы работаете с семейством документов, то Проводник покажет полностью программный проект всего семейства. Специальная папка References покажет взаимные связи проектов отдельных документов.

    Давайте разберемся в структуре проекта, показанного на рисунке 2.1. Этот проект связан с рабочей книгой Excel с именем MasterFNew. В данном случае мы имеем дело с единственным документом, хотя в окне показаны два проекта. Дело в том, что наш документ работает с Решателем ( Solver ). В Office 2000 Решатель представляет AddIn, ссылку на который надо включить в меню References, что и приводит к появлению соответствующего проекта в Проводнике. Заметьте, проект Solver закрыт паролем от просмотра, что не позволяет просмотреть его структуру и тем более код его модулей. Что же касается проекта нашего документа, то его структура раскрыта полностью. Он содержит:

  • 5 модулей, связанных с такими объектами, как рабочая книга, задающая сам документ, и четырьмя страницами этой книги. В окне отображаются названия этих страниц.
  • 3 модуля, связанных с формами - объектами класса Form.
  • 7 стандартных модулей. Точнее, шесть стандартных модулей и модуль Others, содержащий макросы. Обратите внимание, число стандартных модулей в этом проекте достаточно велико. Модуль Main играет роль основного модуля проекта и cодержит описание глобальных переменных всего проекта.
  • Папка References содержит ссылку на проект Solver.
  • Проект данного документа довольно типичен и содержит все типы модулей за исключением модулей классов.

    Свойства проекта

    Проект имеет несколько терминальных свойств, которые можно задать в окне "Project Properties" (Свойства Проекта), оно выводится после щелчка правой кнопкой мыши по названию проекта или одной из его папок в окне проектов и последующего выбора из контекстного меню команды Project Properties.

    (рис 2.2) Окно свойств проекта

    В окне две вкладки General и Protection. На вкладке General задается имя проекта, его описание, указываются файлы справки и константы условной компиляции, относящиеся ко всему проекту. На рисунке можно видеть, как заполнены некоторые из полей, задающие свойства проекта.

    Имя проекта

    Имя проекта используется не только для идентификации проекта в окне просмотра. Оно играет важную роль в тех случаях, когда документ является частью общей системы документов или используется, как компонент, вызываемый в других документах ( COM объект). В последнем случае имя проекта идентифицирует компонент в реестре Windows. Во всех случаях проект можно рассматривать как компонент, отвечающий требованиям COM модели, Согласно принятой в компонентном программировании терминологии, проект экспонирует, то есть позволяет использовать, свои открытые Public - элементы. Имя проекта играет важную роль в этом процессе, - оно является именем библиотеки типов TypeLib. Эта библиотека содержит описание объектов и интерфейсов, которые поддерживает компонент. У имени проекта есть еще одна важная роль, оно используется также при построении полного имени открытых компонентов проекта. Всякий раз, когда в проекте A надо сослаться на Public переменную или Public метод проекта B, необходимо использовать полное имя открытого компонента:

    имя_проекта.имя_модуля.имя_открытого_компонента

    Заметьте, при именовании используется имя проекта, а не имя документа, содержащего проект.

    Защита проекта

    Вкладка Protection позволяет защитить проект от просмотра и редактирования. В большинстве случаев при передаче документа пользователям, проект должен быть защищен как от несанкционированного просмотра его модулей, так и коррекции программного текста. Включив флажок "Lock project for viewing", Вы закрываете проект, его структура будет недоступна для просмотра, если неизвестен пароль. Мы описывали такую ситуацию ранее, когда говорили о проекте Solver, связанном с Решателем, вызываемым в Excel. Для всех нас, конечных пользователей, отсутствует возможность просмотреть реализацию модулей Решателя и тем более пытаться корректировать тексты. Чтобы получить доступ к закрытым проектам, нужно знать пароль проекта. Этот пароль и его подтверждение задается в соответствующих окнах вкладки Protection.

    Окно кода

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

    Левый раскрывающийся список окна кода показывает все объекты, выбранного модуля, позволяя понять, какие объекты вложены в соответствующий основной объект. На рисунке 2.1 зафиксирован момент выбора модуля, связанного с объектом Sheet2 ("ПостановкаЗадачи"). Можно видеть, что на странице расположено довольно много элементов управления. Если выбрать в левом раскрывающемся списке один из объектов, как показано на рисунке, то правый раскрывающийся список отобразит список всех возможных событий данного объекта. Если теперь в правом списке выбрать одно из возможных событий, то в окне кода автоматически будет создана заготовка обработчика этого события. Ее можно наполнить содержанием. Все выбранные таким образом события считаются активными, в правом списке событий они выделены полужирным шрифтом, при их возникновении будет послано сообщение объекту, обработчик будет выполняться, даже если он пустой, - заготовка не была наполнена содержанием.

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

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

    Еще раз о "переиспользовании" модулей

    Пора, пожалуй, прервать плавный ход повествования и привести хоть небольшой фрагмент кода, иллюстрирующий высказанные в этой лекции утверждения. Мы отмечали роль стандартных модулей, одно из достоинств которых состоит в возможности их многократного использования. Рассмотрим следующую, часто возникающую на практике задачу. Пусть есть форма, содержащая два списка - элементы управления типа ListBox. При открытии формы пользователь может выбирать некоторые элементы в одном списке и переносить их в другой. Возникает вопрос, как организовать работу по переносу элементов, какие должны быть обработчики событий, где их разместить, где должна быть сосредоточена вся выполняемая работа по переносу элементов из списка в список в ответ на выбор пользователя. Решение кажется естественным и единственным, - все обработчики и вся обработка должна быть сосредоточена в модуле, связанном с формой. Действительно, для обработчиков событий это единственная возможность и поэтому кажется естественным, что и вся обработка должна быть сосредоточена в этом же модуле. Но, может быть, более эффективно, чтобы формальный обработчик события, находящийся в модуле формы, вызывал обычную процедуру с параметрами, которая может находиться в стандартном модуле общего назначения. Такая процедура может многократно использоваться разными формами, где есть списки и где возникает подобная задача.

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

    (рис 2.3) Форма TwoListsForm документа Excel

    В форме есть два списка и 4 командные кнопки. Перенос элементов из одного списка в другой можно осуществлять тремя разными способами:

  • Двойным щелчком по элементу.
  • Выделить предварительно один или несколько элементов списка и затем щелкнуть командную кнопку со значком ">" ("<").
  • Для переноса всех элементов списка нет необходимости в их выделении, - достаточно щелкнуть командную кнопку со значком ">>" ("<<").
  • Направление переноса задают командные кнопки. Знак на кнопке меняется в зависимости от того, какой из списков выбран - левый или правый. Кнопка "OK" на форме переносит данные из списка на лист Excel, кнопка "Cancel" завершает работу с формой без переноса данных.

    Работу обработчиков событий, возникающих при работе с объектами формы, можно было бы без сомнения полностью описать в модуле, связанном с формой. Но мы поступили по-иному и создали стандартный модуль с именем ToolsMod, в который и вынесли содержательную часть обработки. Так что у нас появилось два модуля, - в одном находятся обработчики события, в другом - стандартном сосредоточен содержательный код, выполняющий обработку. Вот код модуля TwoLists, связанного с нашей формой, и содержащий обработчики событий:

    Option Explicit
    
    Private Sub CommandButton1_Click()
    	'Обработчик события Click кнопки "> <"
        'Выборочный обмен данными между n- колоночными списками:
        'ListBox1  <--> ListBox2
    
    If CommandButton1.Caption = ">" Then
        Call MoveSelectedItems(ListBox1.ColumnCount, ListBox1, ListBox2)
    Else
        Call MoveSelectedItems(ListBox2.ColumnCount, ListBox2, ListBox1)
    End If
    End Sub
    
    Private Sub CommandButton2_Click()
        'Обработчик события Click кнопки ">> <<"
        'Перенос всех данных из одного n-колоночного списка
        'в конец другого, возможно, не пустого списка: ListBox1 <--> ListBox2
    
    If CommandButton2.Caption = ">>" Then
        Call MoveAllItems(ListBox1.ColumnCount, ListBox1, ListBox2)
    Else
        Call MoveAllItems(ListBox2.ColumnCount, ListBox2, ListBox1)
    End If
    End Sub
    
    Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
        'Обработчик события DblClick левого списка ListBox1 (имеет параметры!)
        'При двойном щелчке выбранный элемент одного n-колоночного списка
        'переносится в конец другого списка
        
        Call MoveSelectedItems(ListBox1.ColumnCount, ListBox1, ListBox2)
    End Sub
    
    Private Sub ListBox2_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
        'Обработчик события DblClick правого списка ListBox2
        'При двойном щелчке выбранный элемент одного n-колоночного списка
        'переносится в конец другого списка
        
        Call MoveSelectedItems(ListBox2.ColumnCount, ListBox2, ListBox1)
    End Sub
    
    Private Sub CommandButton5_Click()
        'Обработчик события Click кнопки "OK"
        'Перенос данных из списка на лист Excel
        'В область, заданную ячейкой с именем "Dom"
        
        Call MoveListToRange(ListBox2.ColumnCount, ListBox2, "Dom")
        Me.Hide
    End Sub
    
    Private Sub CommandButton6_Click()
        'Обработчик события Click кнопки "Cancel"
        Me.Hide
    End Sub
    
    Private Sub ListBox1_Enter()
        'Обработчик события Enter, возникающего при получении фокуса
        
        CommandButton1.Caption = ">"
        CommandButton2.Caption = ">>"
    End Sub
    
    Private Sub ListBox2_Enter()
        'Обработчик события Enter, возникающего при получении фокуса
        
        CommandButton1.Caption = "<"
        CommandButton2.Caption = "<<"
    End Sub
    
    Private Sub UserForm_Initialize()
        'Обработчик события Initialize формы TwoListsForm
        'Заполнение списка ListBox1
        
        Dim MyArray(1 To 5, 1 To 2) As String
    
        MyArray(1, 1) = "Петров"	: 	MyArray(1, 2) = "Музыкант"
        MyArray(2, 1) = "Сергеев"	:	 MyArray(2, 2) = "Учитель"
        MyArray(3, 1) = "Гурина"	: 	MyArray(3, 2) = "Актриса"
        MyArray(4, 1) = "Водкин"	: 	MyArray(4, 2) = "Художник"
        MyArray(5, 1) = "Козина"	: 	MyArray(5, 2) = "Геолог"
    
        ListBox1.ColumnCount = 2	: 	ListBox2.ColumnCount = 2
        ListBox1.List() = MyArray
    End Sub

    Модуль TwoListsForm, связанный с формой, содержит 9 обработчиков событий, возникающих при работе с самой формой - ее инициализации, так и при работе пользователя с объектами, населяющими форму. Каждый из обработчиков выполняет свои специфические задачи. Когда пользователь переключается на работу с элементами левого или правого списка, то в тот момент, когда список получает фокус, возникает событие Enter. Обработчик события Enter изменяет заголовок у соответствующих командных кнопок, подготавливая, тем самым, передачу данных в нужном направлении. Такое автоматическое изменение направления передачи позволяет уберечь пользователя от ошибочных действий. Но, обратите внимание на главное в этом примере. Для удобства пользователя ему предоставлены три разных способа передачи данных из одного списка в другой. Поскольку обмен данными может вестись в двух направлениях, то при лобовом программировании нужно было бы написать 6 различных макросов. Мы же свели задачу к двум процедурам, вызываемых с разными значениями параметров. Тексты этих процедур мы поместили в стандартный модуль с именем ToolsMod, предполагая возможность его дальнейшего использования. Вот эти тексты:

    Option Explicit
    Public Sub MoveSelectedItems(ByVal n As Byte, ByVal ListBox1 As Object,  _
    			ByVal ListBox2 As Object)
        'Перемещает выделенные элементы первого списка в конец второго
        'с одновременным удалением данных из первого списка.
        'Оба списка имеют n столбцов.
    
    	Dim RowIndex1 As Byte, RowIndex2 As Byte, i As Byte, j As Byte
    
        'Выборочный обмен данными между списками: ListBox1 -> ListBox2
        With ListBox1
            RowIndex2 = ListBox2.ListCount
            RowIndex1 = 0
            For i = 0 To .ListCount - 1
            If .Selected(RowIndex1) Then
               'Создается элемент нового списка и заполняется его первый столбец
                ListBox2.AddItem .List(RowIndex1)
                'Заполняются остальные столбцы элемента списка
                For j = 1 To n - 1
                    ListBox2.Column(j, RowIndex2) = .Column(j, RowIndex1)
                Next j
                'Перемещенный элемент удаляется из списка
                .RemoveItem RowIndex1
                RowIndex2 = RowIndex2 + 1
             Else
                RowIndex1 = RowIndex1 + 1
             End If
            Next i
          End With
    End Sub
    
    Public Sub MoveAllItems(ByVal n As Byte, ByVal ListBox1 As Object,  _
    			ByVal ListBox2 As Object)
        ' Перемещает все элементы первого списка в конец второго, 
        ' возможно, не пустого списка с одновременным удалением данных из     
        ' первого списка. ListBox1 -> ListBox2
    
    Dim RowIndex1 As Integer, RowIndex2 As Integer, i As Byte
           RowIndex2 = ListBox2.ListCount
            For RowIndex1 = 0 To ListBox1.ListCount - 1
            With ListBox1
                ListBox2.AddItem .List(0)
                For i = 1 To n - 1
                    ListBox2.Column(i, RowIndex2) = .Column(i, 0)
                Next i
                RowIndex2 = RowIndex2 + 1
                'Перемещенный,- это всегда первый элемент,удаляется из списка
                .RemoveItem 0
            End With
            Next RowIndex1
    End Sub
    
    Public Sub MoveListToRange(ByVal n As Byte, List1 As Object, Dom As String)
        'List1 - объект типа ListBox, состоящий из n столбцов. 
        ' Его элементы переносятся в прямоугольную область активного листа,    
        ' Dom - задает имя ячейки, расположенной в левом верхнем углу этой         
        ' области.
    
    	Dim myr As Range
    	Dim i As Byte, j As Byte
    
        Set myr = Range(Dom)
        'Цикл по числу элементов списка. 
        For i = 0 To List1.ListCount - 1
            'Цикл по числу столбцов списка. 
    		 For j = 0 To n - 1
                myr.Offset(j, i) = List1.Column(j, i)
            Next j
        Next i
    End Sub
    
    Public Sub ClearRange(Dom As String)
        'Эта процедура очищает содержимое области листа рабочей книги,
        'заданной ячейкой с именем Dom
        
    Dim myr As Range, Row As Byte, Col As Byte
    
    Set myr = Range(Dom)
    Col = 0: Row = 0
      While myr.Offset(Row, Col) <> ""
        While myr.Offset(Row, Col) <> ""
            'Чистка содержимого
            myr.Offset(Row, Col).ClearContents
            Col = Col + 1
        Wend
        Row = Row + 1
        Col = 0
      Wend
    End Sub

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

    Завершая разговор о переиспользовании стандартных модулей, предлагаем взглянуть на форму, взятую из совсем другого документа - документа Word.

    (рис 2.4) Форма TwoLists, взятая из документа Word

    Хотя форма и похожа на форму, связанную с рабочей книгой Excel, но, обратите внимание, списки в ней другие, они состоят из одного столбца, а не из двух, как в прошлом случае. Тем не менее, мы спокойно выполнили операции экспорта - импорта стандартного модуля из Excel в Word и без всяких изменений использовали его процедуры MoveSelectedItems и MoveAllItems. Конечно, думая о возможности многократного использования этих процедур, мы заранее побеспокоились о возможности работы с произвольным числом столбцов в списках.

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

    Замечание:

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

    Public Sub MoveAllItems(ByVal n As Byte, ByVal ListBox1 As Object, _

    ByVal ListBox2 As Object)

    Вы видите, что тип формальных параметров для ListBox1 и ListBox2 задан как Object и, следовательно, является нетипизированным указателем. Это не очень удобно, так как лишает нас подсказки в момент написания процедуры. Возникает вопрос, можно ли указывать точный тип для подобных формальных параметров. Ответ зависит от приложения. При работе в приложении Word разрешается указывать настоящий, правильный тип ListBox и при этом передача параметров производится корректно. В приложении Excel при написании процедуры также можно указать тип ListBox, но возникнет ошибка в момент передачи параметров.

    В процессе тестирования Office 2000 мы указали разработчикам на это рассогласование, оно должно быть учтено в последующих версиях. Будем надеяться, что в будущем мы сможем в процедурах при объявлении формальных параметров, являющихся объектами, указывать их настоящий тип.

    Проект и область видимости

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

    Первое правило видимости гласит:

    "Все компоненты модуля видимы в пределах этого модуля".

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

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

    Второе правило видимости гласит:

    "Область видимости компонент модуля расширяется на весь проект, если компонент объявлен со спецификатором Public ".

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

    Если при объявлении переменных модуля спецификатор области видимости опущен и указано только ключевое слово Dim, то такие переменные считаются закрытыми, - действует спецификатор Private. Для методов спецификатор области видимости можно опускать. В этом случае действует следующее правило. Все методы стандартных модулей имеют по умолчанию спецификатор Public и являются доступными во всем проекте. Методы модулей - классов и модулей, связанных с объектами, по умолчанию являются закрытыми и имеют статус Private.

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

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

    Система документов и ее проект

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

    Оставим пока в стороне создание и использование AddIns, Com AddIns, DLL, ActiveX, Internet документов. Им будет посвящен особый разговор. Пока поговорим о более понятных случаях использования совокупности документов. Прежде всего, заметим, что корневой объект Application любого из приложения Office 2000 содержит коллекцию документов. Так что изначально предполагается возможность открытия в одном приложении нескольких документов и совместной работы с ними. Но совместная работа не ограничивается однотипными документами, принадлежащими одному приложению. Уже в первой лекции мы подробно, с примерами рассмотрели возможность создания и одновременной работы с несколькими объектами Application. Следовательно, всегда есть возможность открытия совокупности документов разного типа и совместной работы с ними, предоставляя пользователю возможность переключаться по ходу дела от одного документа к другому.

    Совместная работа с несколькими документами, возможно, разного типа типична для Office 2000. Программным проектом системы документов будем называть совокупность программных проектов всех документов, входящих в систему.

    Остается ответить на главный вопрос, - могут ли взаимодействовать между собой проекты отдельных документов. Можно ли из одного проекта вызывать процедуры стандартного модуля другого проекта, можно ли пользоваться объектами класса другого проекта? Можно ли иметь глобальные переменные уровня системы документов для передачи информации от одного документа к другому? На все эти вопросы существует один ответ, - Да! С программным проектом системы документов в Office 2000 достаточно просто работать, достаточно просто организовать нужные связи между программными проектами отдельных документов, нужно только уметь это делать.

    Организация системы документов

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

  • Связи документов "по вызову".
  • Связи проектов документов "по ссылке"
  • Два документа A и B связаны по вызову, если в одном из документов, например A, вызывается документ B. Документ A называют родителем, а документ B, соответственно, потомком. Совокупность связанных по вызову документов образует дерево связанных документов. Корень дерева - это тот первичный документ, из которого и начинаются вызовы других документов. Связи в дереве направлены от корня к потомкам.

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

    Проекты A и B связаны по ссылке, если один из проектов, например B, ссылается на проект A. Проект A называют родителем, а проект B, соответственно, потомком. Совокупность связанных по ссылке проектов образует дерево связанных проектов. Корень дерева - это тот первичный проект, на который ссылаются остальные проекты. Связи в дереве ссылок направлены от потомков к корню. Вообще говоря, проекты, связанные по ссылке, также могли бы организовывать более сложную структуру, чем дерево. Действительно, взаимные ссылки кажутся допустимыми. Но это не так. Помните, циклические ссылки проектов VBA документов не разрешены. Так что при разработке проекта системы документов Office 2000 исходите из древесной структуры организации связей между проектами. На рисунке 2.5 показаны три возможные структуры организации системы документов Office 2000.

    (рис 2.5) Три структуры организации системы документов

    Как организуются ссылки между проектами

    Рассмотрим теперь технический вопрос, как организовать ссылку из проекта B на проект A. Для этого нужно сделать две вещи, во-первых, дать собственные имена проектам, во-вторых, - включить ссылку в проекте B на проект A. Имя нужно дать, по крайней мере, родительскому проекту A. О том, как это делается, мы уже рассказали в разделе "Свойства проекта" этой лекции. На рис. 2.2 показано соответствующее диалоговое окно.

    Задание ссылки выполняется также стандартным образом. В окне Редактора Visual Basic нужно выбрать соответствующий проект, в нашем примере проект B, и из меню Tools выбрать команду References. Если документ, содержащий проект A открыт, то в окне ссылок наряду с другими возможными для ссылок компонентами появится флажок с именем проекта A, останется только его включить.

    Обмен информацией между документами

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

  • Выделим один из проектов, который будем называть основным, и в стандартном модуле этого проекта зададим множество Public переменных, в совокупности и определяющих общую для всей системы информацию. Систему документов спроектируем так, чтобы их проекты были связаны по ссылке, и корнем дерева был основной документ, хранящий глобальную информацию. В этом случае каждый проект будет иметь доступ к этой информации. Напомним, циклические ссылки между проектами не разрешены, отсюда следует, что нельзя часть информации поместить в проект А, часть в проект В и сделать эту информацию общедоступной для обоих проектов. Возможна только древесная организация хранения общей информации. Самая общая информация хранится в корневом проекте, в "дочерних" проектах хранится информация необходимая потомкам этих дочерей, и так до терминальных проектов.
  • Второй способ состоит в том, что общая для всех проектов информация размещается в виде списков Excel, в ячейках выделенной рабочей книги, играющей роль основного документа. Этот способ основан на том, что не только VBA обеспечивает взаимодействие проектов, возможно взаимодействие самих документов, ссылки между ними. Известно, что ссылки на ячейки книги Excel допустимы и широко распространены. Так что страницы Excel часто используются как удобное средство для передачи информации между документами. Чуть ниже на примере мы продемонстрируем этот способ передачи информации. Достоинство этого способа состоит и в том, что проекты могут и не быть связанными ссылками, более того, возможен взаимный обмен информацией.
  • Классическим способом хранения общей информации, особенно, в больших объемах, являются базы данных. Поэтому базу данных Access также можно использовать для организации информационного пула системы проектов.
  • Для полноты картины упомянем обычный текстовый файл.
  • Заканчивая обсуждение этого вопроса, еще раз подчеркнем, что способ передачи информации между проектами, основанный на общем информационном пуле, как и во всех случаях работы с глобальными переменными, является опасным. Поэтому объем этой информации не должен быть большим и легко контролируемым, так что по этим соображениям первые два способа являются наиболее предпочтительными, а программисты, скорее всего, выберут первый из них. В заключение, напомним, что передача информации между проектами через общую область важный, но не единственный способ организации взаимодействия. Основным и надежным способом передачи данных для программистов является вызов Public методов проекта, - передача им аргументов и получение результатов, то, что называется аппаратом формальных - фактических параметров.

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

    (рис 2.6) Открытие рабочей книги, содержащей ссылки

    Поскольку в данной рабочей книге есть ссылки на другие рабочие книги, то при открытии документа появляется сообщение о том, что, возможно, следует обновить документ, поскольку после его закрытия могли измениться документы, на которые он ссылается. На листе рабочей книги нашего примера расположена командная кнопка с надписью " ChooseBook ". Эту кнопку пользователь должен нажать в тот момент, когда он стоит на распутье, и должен выбрать, с какой следующей книгой он хочет работать. Вот текст обработчика события " Click ":

    Private Sub CommandButton5_Click()
        ChooseBook
    End Sub
    
    Public Sub ChooseBook()
     'Выбор книги, с которой будет работать пользователь
     Const PathDir As String = "e:\O2000\CD2000\Ch2\"
       Dim Answer As Variant
        Dim CurrentBook As String
        Dim Book As Workbook
        Dim Found As Boolean
        
        Answer = InputBox(prompt:= _
    	"Выберите книгу, с которой будете работать (1/2/3)", Default:=1)
        Select Case Answer
            Case 1
                CurrentBook = "BookOne.xls"
            Case 2
                CurrentBook = "BookTwo.xls"
            Case 3
                CurrentBook = "BookThree.xls"
            Case Else
                CurrentBook = "I don't know such book"
        End Select
        MsgBox ("Your choose is "  CurrentBook)
        'Проверяем есть ли уже книга в коллекции
        Found = False
        For Each Book In Workbooks
            If Book.Name = CurrentBook Then
                Found = True
                Exit For
            End If
        Next Book
        If Found Then
        'Активизируем выбранную книгу
            Workbooks(CurrentBook).Activate
            ElseIf CurrentBook = "I don't know such book" Then
                MsgBox ("I don't know such book")
            Else   'Добавляем книгу в коллекцию
                Workbooks.Open PathDir  CurrentBook
                Workbooks(CurrentBook).Activate
        End If
    End Sub

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

    Public Sub PrintGlobal()
        Dim MyrOne As Range, MyrTwo As Range, MyrThree As Range
        
        Debug.Print ("Работает процедура PrintOne")
        Debug.Print ("Печать глобальных переменных")
        Debug.Print (One  "-"  Two  "-"  Three)
    
        Debug.Print ("Допустимы ссылки на ячейки различных рабочих книг!")
        Set MyrOne = Range("[BookOne.xls]Sheet1!D1")
        Debug.Print MyrOne
        Set MyrTwo = Range("[BookTwo.xls]Sheet1!D1")
        Debug.Print MyrTwo
        Set MyrThree = Range("[BookThree.xls]Sheet1!D1")
        Debug.Print MyrThree
        MsgBox ("Everything is OK! Look at Immediate Window")
    End Sub

    Вот результаты отладочной печати в конце работы этой процедуры:

    Работает процедура PrintOne
    Печать глобальных переменных
    My value is One -My value is One -My value is One 
    Допустимы ссылки на ячейки различных рабочих книг!
    BookOne
    BookTwo
    BookThree

    Всем глобальным переменным предварительно было присвоено одно и то же значение. Книги BookOne, BookTwo, BookThree.xls на первой странице в ячейке D1 хранят свои названия, их и печатает наша процедура, вызываемая в проекте одной из этих книг.

    Система документов One - Two - Three

    Рассмотрим теперь систему документов из тех же трех книг BookOne, BookTwo, BookThree.xls, проекты которых связаны ссылками. Наша основная цель, - продемонстрировать на примере работу трех проектов с общей информацией и общими методами, вызываемыми во всех проектах.

    Вначале несколько слов об интерфейсе нашей системы документов. Мы сделали его достаточно простым. На первом рабочем листе каждой из трех книг расположены три командные кнопки: ChooseBook, ChangeGlobal, PrintGlobal.

    (рис 2.7) Командные кнопки управления системой документов

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

    Option Explicit
    'Глобальные переменные системы документов One - Two - Three
    Public One As String, Two As String, Three As String

    Три обработчика события Click для трех командных кнопок вызывают три общих метода ChooseBook, ChangeGlobal, PrintGlobal:

    Private Sub CommandButton5_Click()
        ChooseBook
    End Sub
    
    Private Sub CommandButton4_Click()
        ChangeGlobal
    End Sub
    
    Private Sub CommandButton3_Click()
        PrintGlobal
    End Sub

    Назначение процедуры ChooseBook и ее текст уже приведены. Заметим только, что эта общая процедура вызывается соответствующими обработчиками во всех трех модулях. Мы поместили ее не в стандартный модуль проекта BookOne, а в модуль, связанный с листом Sheet1 книги BookOne. Это, однако, ничуть не мешает ее вызову из других проектов. Процедура ChangeGlobal, помещена в стандартный модуль с именем ModuleOne. Она позволяет работать с глобальными переменными, и поскольку в каждом проекте работа с ними выполняется по-своему, то она не является общей процедурой. В каждом проекте есть свой вариант этой процедуры, который и вызывается обработчиком Click командной кнопки ChangeGlobal. Заметим, что когда речь идет об основном проекте, где описаны глобальные переменные, при обращении к ним не требуется использовать полные имена:

    Public Sub ChangeGlobal()
        One = "My value is One "
        Two = "My value is One "
        Three = "My value is One "
    End Sub

    В стандартный модуль ModuleOne мы поместили и процедуру PrintGlobal. Текст ее тоже уже приведен. Заметим, что в каждом проекте будет свой вариант этой процедуры, но в каждом из них на последнем этапе работы будет вызываться процедура PrintGlobal проекта BookOne. В стандартном модуле находится функция Plus1, которая будет вызываться из других проектов. Это очень простая функция, но она выполняет свою важную роль, демонстрируя передачу информации от проекта к проекту через аппарат формальных - фактических параметров процедур и функций. Вот ее текст:

    Public Function Plus1(ByVal X As Integer) As Integer
        Plus1 = X + 1
    End Function

    Проект BookTwo ссылается на проект BookOne, использует его общие переменные и вызывает его методы - ChooseBook, PrintGlobal, Plus1. Он является хранителем общей информации для проектов BookTwo и BookThree:

    Option Explicit
    'Глобальная переменная документов BookTwo, BookThree
    Public TwoThree As String

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

    Private Sub CommandButton1_Click()
        BookOne.Sheet1.ChooseBook
    End Sub 
    
    Public Sub ChangeGlobal()
    'Изменение значений глобальных переменных системы документов
        BookOne.ModuleOne.Two = "My value is Two "
        TwoThree = "My value is Two - Two "
    End Sub
    
    Public Sub PrintGlobal()
        Dim Number As Integer
        Debug.Print ("Работает процедура PrintTwo")
        Debug.Print ("Печать глобальных переменных")
        Debug.Print TwoThree
        'Вызов процедур и функций проекта BookOne
        Number = BookOne.ModuleOne.PLus1(1)
        Debug.Print ("Это книга "  Number)
        ChangeGlobal
        BookOne.ModuleOne.PrintGlobal
    End Sub

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

    Private Sub CommandButton1_Click()
        BookOne.Sheet1.ChooseBook
    End Sub
    
    Public Sub ChangeGlobal()
        'Изменение значений глобальных переменных системы
        BookOne.ModuleOne.Three = "My value is Three "
        BookTwo.ModuleTwo.TwoThree = "My value: Two-Three"
    End Sub
    
    Public Sub PrintGlobal()
        Dim Number As Integer
        Debug.Print ("Работает процедура PrintThree")
        Number = BookOne.ModuleOne.Plus1(2)
        Debug.Print ("Это книга "  Number)
        ChangeGlobal
        BookTwo.ModuleTwo.PrintGlobal
    End Sub

    Приведем результаты отладочной печати, полученные при нажатии кнопки PrintGlobal в книге BookThree:

    Работает процедура PrintThree
    Это книга 3
    Работает процедура PrintTwo
    Печать глобальных переменных
    My value: Two-Three
    Это книга 2
    Работает процедура PrintOne
    Печать глобальных переменных
    My value is One -My value is Two -My value is Three 
    Допустимы ссылки на ячейки различных рабочих книг!
    BookOne
    BookTwo
    BookThree

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

    (рис 2.8) Три проекта One - Two - Three
    Вернуться к учебному плану